diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index bcc5acf83653..35f5aeecb5a4 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -30,7 +30,7 @@ function ArchivedReportFooter({report, reportClosedAction, personalDetails = {}} const styles = useThemeStyles(); const {translate} = useLocalize(); - const originalMessage = reportClosedAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED ? reportClosedAction.originalMessage : null; + const originalMessage = ReportActionsUtils.isClosedAction(reportClosedAction) ? ReportActionsUtils.getOriginalMessage(reportClosedAction) : null; const archiveReason = originalMessage?.reason ?? CONST.REPORT.ARCHIVE_REASON.DEFAULT; const actorPersonalDetails = personalDetails?.[reportClosedAction?.actorAccountID ?? -1]; let displayName = PersonalDetailsUtils.getDisplayNameOrDefault(actorPersonalDetails); diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index ae09757b66e6..c3b5acafc6fc 100644 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -594,7 +594,7 @@ export default withOnyx({ transaction: { key: ({report}) => { const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1'); - const transactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction?.originalMessage.IOUTransactionID ?? '-1' : '-1'; + const transactionID = ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID ?? '-1' : '-1'; return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`; }, }, diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachments.ts b/src/components/Attachments/AttachmentCarousel/extractAttachments.ts index 38abe075ef81..1e9c67cf84ac 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachments.ts +++ b/src/components/Attachments/AttachmentCarousel/extractAttachments.ts @@ -94,9 +94,9 @@ function extractAttachments( return; } - const decision = action?.message?.[0]?.moderationDecision?.decision; + const decision = ReportActionsUtils.getReportActionMessage(action)?.moderationDecision?.decision; const hasBeenFlagged = decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || decision === CONST.MODERATION.MODERATOR_DECISION_HIDDEN; - const html = (action?.message?.[0]?.html ?? '').replace('/>', `data-flagged="${hasBeenFlagged}" data-id="${action.reportActionID}"/>`); + const html = ReportActionsUtils.getReportActionHtml(action).replace('/>', `data-flagged="${hasBeenFlagged}" data-id="${action.reportActionID}"/>`); htmlParser.write(html); }); htmlParser.end(); diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 60d5bf7034cc..efd67d6c6b50 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -119,7 +119,9 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`]; const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? '-1']; const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`]; - const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID ?? '-1' : '-1'; + const transactionID = ReportActionsUtils.isMoneyRequestAction(itemParentReportAction) + ? ReportActionsUtils.getOriginalMessage(itemParentReportAction)?.IOUTransactionID ?? '-1' + : '-1'; const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; const hasDraftComment = DraftCommentUtils.isValidDraftComment(draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`]); const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(itemReportActions); @@ -128,8 +130,8 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio // Get the transaction for the last report action let lastReportActionTransactionID = ''; - if (lastReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { - lastReportActionTransactionID = lastReportAction.originalMessage?.IOUTransactionID ?? '-1'; + if (ReportActionsUtils.isMoneyRequestAction(lastReportAction)) { + lastReportActionTransactionID = ReportActionsUtils.getOriginalMessage(lastReportAction)?.IOUTransactionID ?? '-1'; } const lastReportActionTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${lastReportActionTransactionID}`] ?? {}; diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index c1fe4270d4e1..fa58b5cd5f5f 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -514,6 +514,7 @@ function MenuItem( ...(Array.isArray(wrapperStyle) ? wrapperStyle : [wrapperStyle]), !focused && (isHovered || pressed) && hoverAndPressStyle, shouldGreyOutWhenDisabled && disabled && styles.buttonOpacityDisabled, + isHovered && !pressed && styles.hoveredComponentBG, ] as StyleProp } disabledStyle={shouldUseDefaultCursorWhenDisabled && [styles.cursorDefault]} diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 90952157f179..a077ebb88d58 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -73,9 +73,9 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea if (!reportActions || !transactionThreadReport?.parentReportActionID) { return null; } - return reportActions.find((action) => action.reportActionID === transactionThreadReport.parentReportActionID); + return reportActions.find((action): action is OnyxTypes.ReportAction => action.reportActionID === transactionThreadReport.parentReportActionID); }, [reportActions, transactionThreadReport?.parentReportActionID]); - const isDeletedParentAction = ReportActionsUtils.isDeletedAction(requestParentReportAction as OnyxTypes.ReportAction); + const isDeletedParentAction = !!requestParentReportAction && ReportActionsUtils.isDeletedAction(requestParentReportAction); // Only the requestor can delete the request, admins can only edit it. const isActionOwner = @@ -151,7 +151,9 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const deleteTransaction = useCallback(() => { if (requestParentReportAction) { - const iouTransactionID = requestParentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? requestParentReportAction.originalMessage?.IOUTransactionID ?? '-1' : '-1'; + const iouTransactionID = ReportActionsUtils.isMoneyRequestAction(requestParentReportAction) + ? ReportActionsUtils.getOriginalMessage(requestParentReportAction)?.IOUTransactionID ?? '-1' + : '-1'; if (ReportActionsUtils.isTrackExpenseAction(requestParentReportAction)) { navigateBackToAfterDelete.current = IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '-1', iouTransactionID, requestParentReportAction, true); } else { @@ -166,7 +168,9 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea if (!requestParentReportAction) { return; } - const iouTransactionID = requestParentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? requestParentReportAction.originalMessage?.IOUTransactionID ?? '-1' : '-1'; + const iouTransactionID = ReportActionsUtils.isMoneyRequestAction(requestParentReportAction) + ? ReportActionsUtils.getOriginalMessage(requestParentReportAction)?.IOUTransactionID ?? '-1' + : '-1'; const reportID = transactionThreadReport?.reportID ?? '-1'; TransactionActions.markAsCash(iouTransactionID, reportID); diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index f14fe11f5d74..b10a09e4f7e8 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -20,7 +20,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; import type {Policy, Report, ReportAction} from '@src/types/onyx'; -import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import type IconAsset from '@src/types/utils/IconAsset'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import Button from './Button'; @@ -51,7 +50,11 @@ type MoneyRequestHeaderProps = { function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrowLayout = false, onBackButtonPress}: MoneyRequestHeaderProps) { const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`); - const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${(parentReportAction as ReportAction & OriginalMessageIOU)?.originalMessage?.IOUTransactionID ?? -1}`); + const [transaction] = useOnyx( + `${ONYXKEYS.COLLECTION.TRANSACTION}${ + ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID ?? -1 : -1 + }`, + ); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [session] = useOnyx(ONYXKEYS.SESSION); const [dismissedHoldUseExplanation, dismissedHoldUseExplanationResult] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {initialValue: true}); @@ -80,7 +83,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow const deleteTransaction = useCallback(() => { if (parentReportAction) { - const iouTransactionID = parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '-1' : '-1'; + const iouTransactionID = ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID ?? '-1' : '-1'; if (ReportActionsUtils.isTrackExpenseAction(parentReportAction)) { navigateBackToAfterDelete.current = IOU.deleteTrackExpense(parentReport?.reportID ?? '-1', iouTransactionID, parentReportAction, true); } else { @@ -104,7 +107,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow const canDeleteRequest = isActionOwner && (ReportUtils.canAddOrDeleteTransactions(moneyRequestReport) || ReportUtils.isTrackExpenseReport(report)) && !isDeletedParentAction; const changeMoneyRequestStatus = () => { - const iouTransactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '-1' : '-1'; + const iouTransactionID = ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID ?? '-1' : '-1'; if (isOnHold) { IOU.unholdRequest(iouTransactionID, report?.reportID); diff --git a/src/components/ReportActionItem/ChronosOOOListActions.tsx b/src/components/ReportActionItem/ChronosOOOListActions.tsx index 52e0c52873d6..460104a71d68 100644 --- a/src/components/ReportActionItem/ChronosOOOListActions.tsx +++ b/src/components/ReportActionItem/ChronosOOOListActions.tsx @@ -6,16 +6,17 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as Chronos from '@userActions/Chronos'; -import type {OriginalMessageChronosOOOList} from '@src/types/onyx/OriginalMessage'; -import type {ReportActionBase} from '@src/types/onyx/ReportAction'; +import type CONST from '@src/CONST'; +import type ReportAction from '@src/types/onyx/ReportAction'; type ChronosOOOListActionsProps = { /** The ID of the report */ reportID: string; /** All the data of the action */ - action: ReportActionBase & OriginalMessageChronosOOOList; + action: ReportAction; }; function ChronosOOOListActions({reportID, action}: ChronosOOOListActionsProps) { @@ -23,7 +24,7 @@ function ChronosOOOListActions({reportID, action}: ChronosOOOListActionsProps) { const {translate, preferredLocale} = useLocalize(); - const events = action.originalMessage?.events ?? []; + const events = ReportActionsUtils.getOriginalMessage(action)?.events ?? []; if (!events.length) { return ( diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 83fb8cd25292..952e66cb1154 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -36,7 +36,7 @@ import variables from '@styles/variables'; import * as PaymentMethods from '@userActions/PaymentMethods'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; -import type {IOUMessage} from '@src/types/onyx/OriginalMessage'; +import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {MoneyRequestPreviewProps, PendingMessageProps} from './types'; @@ -74,7 +74,8 @@ function MoneyRequestPreviewContent({ const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport); const {canUseViolations} = usePermissions(); - const participantAccountIDs = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && isBillSplit ? action.originalMessage.participantAccountIDs ?? [] : [managerID, ownerAccountID]; + const participantAccountIDs = + ReportActionsUtils.isMoneyRequestAction(action) && isBillSplit ? ReportActionsUtils.getOriginalMessage(action)?.participantAccountIDs ?? [] : [managerID, ownerAccountID]; const participantAvatars = OptionsListUtils.getAvatarsForAccountIDs(participantAccountIDs, personalDetails ?? {}); const sortedParticipantAvatars = lodashSortBy(participantAvatars, (avatar) => avatar.id); if (isPolicyExpenseChat && isBillSplit) { @@ -225,7 +226,7 @@ function MoneyRequestPreviewContent({ }; const getDisplayDeleteAmountText = (): string => { - const iouOriginalMessage: IOUMessage | EmptyObject = action?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? action.originalMessage : {}; + const iouOriginalMessage: OriginalMessageIOU | EmptyObject = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action) ?? {} : {}; const {amount = 0, currency = CONST.CURRENCY.USD} = iouOriginalMessage; return CurrencyUtils.convertToDisplayString(amount, currency); diff --git a/src/components/ReportActionItem/MoneyRequestPreview/index.tsx b/src/components/ReportActionItem/MoneyRequestPreview/index.tsx index b46e052f3420..c01206f83f55 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/index.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/index.tsx @@ -31,7 +31,7 @@ export default withOnyx( transaction: { key: ({action}) => { const isMoneyRequestAction = ReportActionsUtils.isMoneyRequestAction(action); - const transactionID = isMoneyRequestAction ? action?.originalMessage?.IOUTransactionID : 0; + const transactionID = isMoneyRequestAction ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : 0; return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`; }, }, diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index dfcc1d363e72..4714a683fac8 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -141,7 +141,7 @@ function MoneyRequestView({ const isCancelled = moneyRequestReport && moneyRequestReport.isCancelledIOU; // Used for non-restricted fields such as: description, category, tag, billable, etc. - const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction); + const canEdit = ReportActionsUtils.isMoneyRequestAction(parentReportAction) && ReportUtils.canEditMoneyRequest(parentReportAction); const canEditTaxFields = canEdit && !isDistanceRequest; const canEditAmount = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.AMOUNT); @@ -614,7 +614,8 @@ export default withOnyx { const parentReportAction = parentReportActions?.[report.parentReportActionID ?? '-1']; - const originalMessage = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage : undefined; + const originalMessage = + parentReportAction && ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction) : undefined; const transactionID = originalMessage?.IOUTransactionID ?? -1; return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`; }, @@ -622,7 +623,8 @@ export default withOnyx { const parentReportAction = parentReportActions?.[report.parentReportActionID ?? '-1']; - const originalMessage = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage : undefined; + const originalMessage = + parentReportAction && ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction) : undefined; const transactionID = originalMessage?.IOUTransactionID ?? -1; return `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`; }, diff --git a/src/components/ReportActionItem/RenameAction.tsx b/src/components/ReportActionItem/RenameAction.tsx index f2a3fead2336..e65fc68ecd42 100644 --- a/src/components/ReportActionItem/RenameAction.tsx +++ b/src/components/ReportActionItem/RenameAction.tsx @@ -4,7 +4,7 @@ import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalD import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import CONST from '@src/CONST'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import type {ReportAction} from '@src/types/onyx'; type RenameActionProps = WithCurrentUserPersonalDetailsProps & { @@ -20,7 +20,7 @@ function RenameAction({currentUserPersonalDetails, action}: RenameActionProps) { const userDisplayName = action.person?.[0]?.text; const actorAccountID = action.actorAccountID ?? '-1'; const displayName = actorAccountID === currentUserAccountID ? `${translate('common.you')}` : `${userDisplayName}`; - const originalMessage = action.actionName === CONST.REPORT.ACTIONS.TYPE.RENAMED ? action.originalMessage : null; + const originalMessage = ReportActionsUtils.isRenamedAction(action) ? ReportActionsUtils.getOriginalMessage(action) : null; const oldName = originalMessage?.oldName ?? ''; const newName = originalMessage?.newName ?? ''; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index daa7e24709c2..fd2427a4ddc3 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -25,6 +25,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; import * as ReceiptUtils from '@libs/ReceiptUtils'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; @@ -214,7 +215,7 @@ function ReportPreview({ // If iouReport is not available, get amount from the action message (Ex: "Domain20821's Workspace owes $33.00" or "paid ₫60" or "paid -₫60 elsewhere") let displayAmount = ''; - const actionMessage = action.message?.[0]?.text ?? ''; + const actionMessage = ReportActionsUtils.getReportActionText(action); const splits = actionMessage.split(' '); splits.forEach((split) => { diff --git a/src/components/ReportActionItem/TripRoomPreview.tsx b/src/components/ReportActionItem/TripRoomPreview.tsx index 3711965305a9..53c887f320ef 100644 --- a/src/components/ReportActionItem/TripRoomPreview.tsx +++ b/src/components/ReportActionItem/TripRoomPreview.tsx @@ -18,6 +18,7 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; +import {getReportActionText} from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TripReservationUtils from '@libs/TripReservationUtils'; import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; @@ -123,7 +124,7 @@ function TripRoomPreview({action, chatReportID, containerStyles, contextMenuAnch // If iouReport is not available, get amount from the action message (Ex: "Domain20821's Workspace owes $33.00" or "paid ₫60" or "paid -₫60 elsewhere") let displayAmountValue = ''; - const actionMessage = action.message?.[0]?.text ?? ''; + const actionMessage = getReportActionText(action) ?? ''; const splits = actionMessage.split(' '); splits.forEach((split) => { @@ -135,7 +136,7 @@ function TripRoomPreview({action, chatReportID, containerStyles, contextMenuAnch }); return displayAmountValue; - }, [action.message, iouReport?.currency, totalDisplaySpend]); + }, [action, iouReport?.currency, totalDisplaySpend]); return ( .filter(Boolean) .map((reportAction) => { const {reportActionID, actionName, errors = [], originalMessage} = reportAction; - const decision = reportAction.message?.[0]?.moderationDecision?.decision; + const message = ReportActionsUtils.getReportActionMessage(reportAction); + const decision = message?.moderationDecision?.decision; return { reportActionID, diff --git a/src/languages/en.ts b/src/languages/en.ts index 99ed3265f02b..6a9a6e7ccab9 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2086,7 +2086,7 @@ export default { [`${CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.VENDOR_BILL}Description`]: "We'll create an itemized vendor bill for each Expensify report with the date of the last expense, and add it to the account below. If this period is closed, we'll post to the 1st of the next open period.", - [`${CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.DEBIT_CARD}AccountDescription`]: 'Debit card transactions will export to the bank account below.”', + [`${CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.DEBIT_CARD}AccountDescription`]: 'Debit card transactions will export to the bank account below.', [`${CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.CREDIT_CARD}AccountDescription`]: 'Credit card transactions will export to the bank account below.', [`${CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.VENDOR_BILL}AccountDescription`]: 'Choose a vendor to apply to all credit card transactions.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 96346458af37..f007c1211190 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2115,7 +2115,7 @@ export default { 'Crearemos una factura de proveedor desglosada para cada informe de Expensify con la fecha del último gasto, y la añadiremos a la cuenta a continuación. Si este periodo está cerrado, lo contabilizaremos en el día 1 del siguiente periodo abierto.', [`${CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.DEBIT_CARD}AccountDescription`]: - 'Las transacciones con tarjeta de débito se exportarán a la cuenta bancaria que aparece a continuación.”', + 'Las transacciones con tarjeta de débito se exportarán a la cuenta bancaria que aparece a continuación.', [`${CONST.QUICKBOOKS_NON_REIMBURSABLE_EXPORT_ACCOUNT_TYPE.CREDIT_CARD}AccountDescription`]: 'Las transacciones con tarjeta de crédito se exportarán a la cuenta bancaria que aparece a continuación.', [`${CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.VENDOR_BILL}AccountDescription`]: 'Selecciona el proveedor que se aplicará a todas las transacciones con tarjeta de crédito.', diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index da781f6c0bc9..efcba4c23204 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -3,11 +3,11 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PolicyTagList, Report, ReportAction} from '@src/types/onyx'; -import type {ModifiedExpense} from '@src/types/onyx/OriginalMessage'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; import * as Localize from './Localize'; import * as PolicyUtils from './PolicyUtils'; +import * as ReportActionsUtils from './ReportActionsUtils'; import * as TransactionUtils from './TransactionUtils'; let allPolicyTags: OnyxCollection = {}; @@ -111,11 +111,11 @@ function getForDistanceRequest(newDistance: string, oldDistance: string, newAmou * ModifiedExpense::getNewDotComment in Web-Expensify should match this. * If we change this function be sure to update the backend as well. */ -function getForReportAction(reportID: string | undefined, reportAction: OnyxEntry | ReportAction | Record): string { - if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE) { +function getForReportAction(reportID: string | undefined, reportAction: OnyxEntry): string { + if (!ReportActionsUtils.isModifiedExpenseAction(reportAction)) { return ''; } - const reportActionOriginalMessage = reportAction?.originalMessage as ModifiedExpense | undefined; + const reportActionOriginalMessage = ReportActionsUtils.getOriginalMessage(reportAction); const policyID = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]?.policyID ?? '-1'; const removalFragments: string[] = []; diff --git a/src/libs/Notification/LocalNotification/BrowserNotifications.ts b/src/libs/Notification/LocalNotification/BrowserNotifications.ts index 4ba13418b29e..4c214a66c569 100644 --- a/src/libs/Notification/LocalNotification/BrowserNotifications.ts +++ b/src/libs/Notification/LocalNotification/BrowserNotifications.ts @@ -4,6 +4,7 @@ import type {ImageSourcePropType} from 'react-native'; import EXPENSIFY_ICON_URL from '@assets/images/expensify-logo-round-clearspace.png'; import * as AppUpdate from '@libs/actions/AppUpdate'; import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; +import {getTextFromHtml} from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import type {Report, ReportAction} from '@src/types/onyx'; import focusApp from './focusApp'; @@ -101,7 +102,12 @@ export default { const plainTextPerson = person?.map((f) => f.text).join() ?? ''; // Specifically target the comment part of the message - const plainTextMessage = message?.find((f) => f?.type === 'COMMENT')?.text ?? ''; + let plainTextMessage = ''; + if (Array.isArray(message)) { + plainTextMessage = getTextFromHtml(message?.find((f) => f?.type === 'COMMENT')?.html); + } else { + plainTextMessage = message?.type === 'COMMENT' ? getTextFromHtml(message?.html) : ''; + } if (isChatRoom) { const roomName = ReportUtils.getReportName(report); diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 569e12820b2d..ae1c1f795d82 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -561,7 +561,7 @@ function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry< !report?.parentReportID || !report?.parentReportActionID ? undefined : allReportActions?.[report.parentReportID ?? '-1']?.[report.parentReportActionID ?? '-1']; if (ReportActionUtils.wasActionTakenByCurrentUser(parentReportAction) && ReportActionUtils.isTransactionThread(parentReportAction)) { - const transactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction?.originalMessage?.IOUTransactionID : null; + const transactionID = ReportActionUtils.isMoneyRequestAction(parentReportAction) ? ReportActionUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID : null; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; if (TransactionUtils.hasMissingSmartscanFields(transaction ?? null) && !ReportUtils.isSettled(transaction?.reportID)) { reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage'); @@ -662,7 +662,8 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails if (ReportUtils.isArchivedRoom(report)) { const archiveReason = - (lastOriginalReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED && lastOriginalReportAction?.originalMessage?.reason) || CONST.REPORT.ARCHIVE_REASON.DEFAULT; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + (ReportActionUtils.isClosedAction(lastOriginalReportAction) && ReportActionUtils.getOriginalMessage(lastOriginalReportAction)?.reason) || CONST.REPORT.ARCHIVE_REASON.DEFAULT; switch (archiveReason) { case CONST.REPORT.ARCHIVE_REASON.ACCOUNT_CLOSED: case CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY: @@ -683,7 +684,7 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails } else if (ReportActionUtils.isReportPreviewAction(lastReportAction)) { const iouReport = getReportOrDraftReport(ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction)); const lastIOUMoneyReportAction = allSortedReportActions[iouReport?.reportID ?? '-1']?.find( - (reportAction, key) => + (reportAction, key): reportAction is ReportAction => ReportActionUtils.shouldReportActionBeVisible(reportAction, key) && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && ReportActionUtils.isMoneyRequestAction(reportAction), diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index a12e9ce61a63..8df34bfad89d 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1,4 +1,4 @@ -import {fastMerge} from 'expensify-common'; +import {ExpensiMark, fastMerge} from 'expensify-common'; import _ from 'lodash'; import lodashFindLast from 'lodash/findLast'; import type {OnyxCollection, OnyxCollectionInputValue, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; @@ -8,22 +8,11 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {OnyxInputOrEntry} from '@src/types/onyx'; -import type { - ActionName, - ChangeLog, - IOUMessage, - JoinWorkspaceResolution, - OriginalMessageActionableMentionWhisper, - OriginalMessageActionableReportMentionWhisper, - OriginalMessageActionableTrackedExpenseWhisper, - OriginalMessageDismissedViolation, - OriginalMessageIOU, - OriginalMessageJoinPolicyChangeLog, - OriginalMessageReimbursementDequeued, -} from '@src/types/onyx/OriginalMessage'; +import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage'; import type Report from '@src/types/onyx/Report'; -import type {Message, ReportActionBase, ReportActionMessageJSON, ReportActions} from '@src/types/onyx/ReportAction'; +import type {Message, OriginalMessage, ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; +import type ReportActionName from '@src/types/onyx/ReportActionName'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import DateUtils from './DateUtils'; @@ -33,7 +22,7 @@ import * as Localize from './Localize'; import Log from './Log'; import type {MessageElementBase, MessageTextElement} from './MessageElement'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; -import type {OptimisticIOUReportAction} from './ReportUtils'; +import type {OptimisticIOUReportAction, PartialReportAction} from './ReportUtils'; import StringUtils from './StringUtils'; import * as TransactionUtils from './TransactionUtils'; @@ -56,8 +45,6 @@ type MemberChangeMessageRoomReferenceElement = { type MemberChangeMessageElement = MessageTextElement | MemberChangeMessageUserMentionElement | MemberChangeMessageRoomReferenceElement; -const policyChangeActionsSet = new Set(Object.values(CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG)); - let allReports: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, @@ -109,63 +96,117 @@ function isCreatedAction(reportAction: OnyxInputOrEntry): boolean function isDeletedAction(reportAction: OnyxInputOrEntry): boolean { const message = reportAction?.message ?? []; + if (!Array.isArray(message)) { + return message?.html === '' ?? message?.deleted; + } + // A legacy deleted comment has either an empty array or an object with html field with empty string as value const isLegacyDeletedComment = message.length === 0 || message[0]?.html === ''; return isLegacyDeletedComment || !!message[0]?.deleted; } +function getReportActionMessage(reportAction: PartialReportAction) { + if (Array.isArray(reportAction?.message)) { + return reportAction?.message?.[0]; + } + return reportAction?.message; +} + function isDeletedParentAction(reportAction: OnyxInputOrEntry): boolean { - return (reportAction?.message?.[0]?.isDeletedParentAction ?? false) && (reportAction?.childVisibleActionCount ?? 0) > 0; + return (getReportActionMessage(reportAction)?.isDeletedParentAction ?? false) && (reportAction?.childVisibleActionCount ?? 0) > 0; } function isReversedTransaction(reportAction: OnyxInputOrEntry) { - return (reportAction?.message?.[0]?.isReversedTransaction ?? false) && ((reportAction as ReportAction)?.childVisibleActionCount ?? 0) > 0; + return (getReportActionMessage(reportAction)?.isReversedTransaction ?? false) && ((reportAction as ReportAction)?.childVisibleActionCount ?? 0) > 0; } function isPendingRemove(reportAction: OnyxInputOrEntry | EmptyObject): boolean { if (isEmptyObject(reportAction)) { return false; } - return reportAction?.message?.[0]?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_REMOVE; + return getReportActionMessage(reportAction)?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_REMOVE; +} + +function isMoneyRequestAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.IOU); +} + +function isReportPreviewAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW); +} + +function isSubmittedAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.SUBMITTED); +} + +function isModifiedExpenseAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE); +} + +function isPolicyChangeLogAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction> { + return isActionOfType(reportAction, ...Object.values(CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG)); } -function isMoneyRequestAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction & OriginalMessageIOU { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU; +function isChronosOOOListAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.CHRONOS_OOO_LIST); } -function isReportPreviewAction(reportAction: OnyxInputOrEntry): boolean { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW; +function isAddCommentAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT); } -function isReportActionSubmitted(reportAction: OnyxInputOrEntry): boolean { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.SUBMITTED; +function isCreatedTaskReportAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT) && !!getOriginalMessage(reportAction)?.taskReportID; } -function isModifiedExpenseAction(reportAction: OnyxInputOrEntry | ReportAction | Record): boolean { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE; +function isTripPreview(reportAction: OnyxInputOrEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.TRIPPREVIEW); +} + +function isActionOfType( + action: OnyxInputOrEntry, + ...actionNames: T +): action is { + [K in keyof T]: ReportAction; +}[number] { + return actionNames.includes(action?.actionName as T[number]); +} + +function getOriginalMessage(reportAction: OnyxInputOrEntry>): OriginalMessage | undefined { + if (!Array.isArray(reportAction?.message)) { + return reportAction?.message ?? reportAction?.originalMessage; + } + return reportAction.originalMessage; } /** * We are in the process of deprecating reportAction.originalMessage and will be setting the db version of "message" to reportAction.message in the future see: https://github.com/Expensify/App/issues/39797 * In the interim, we must check to see if we have an object or array for the reportAction.message, if we have an array we will use the originalMessage as this means we have not yet migrated. */ -function getWhisperedTo(reportAction: OnyxInputOrEntry | EmptyObject): number[] { - const originalMessage = reportAction?.originalMessage; +function getWhisperedTo(reportAction: OnyxInputOrEntry): number[] { + if (!reportAction) { + return []; + } + const originalMessage = getOriginalMessage(reportAction); const message = reportAction?.message; - if (!Array.isArray(message) && typeof message === 'object') { - return (message as ReportActionMessageJSON)?.whisperedTo ?? []; + if (!(originalMessage && 'whisperedTo' in originalMessage) && !(message && 'whisperedTo' in message)) { + return []; + } + + if (!Array.isArray(message) && typeof message === 'object' && 'whisperedTo' in message) { + return message?.whisperedTo ?? []; } - if (originalMessage) { - return (originalMessage as ReportActionMessageJSON)?.whisperedTo ?? []; + if (originalMessage && 'whisperedTo' in originalMessage) { + return originalMessage?.whisperedTo ?? []; } return []; } -function isWhisperAction(reportAction: OnyxInputOrEntry | EmptyObject): boolean { +function isWhisperAction(reportAction: OnyxInputOrEntry): boolean { return getWhisperedTo(reportAction).length > 0; } @@ -179,30 +220,47 @@ function isWhisperActionTargetedToOthers(reportAction: OnyxInputOrEntry) { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_QUEUED; +function isReimbursementQueuedAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_QUEUED); } -function isMemberChangeAction(reportAction: OnyxInputOrEntry) { - return ( - reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOM_CHANGE_LOG.INVITE_TO_ROOM || - reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOM_CHANGE_LOG.REMOVE_FROM_ROOM || - reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.INVITE_TO_ROOM || - reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.REMOVE_FROM_ROOM || - reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.LEAVE_POLICY +function isMemberChangeAction( + reportAction: OnyxInputOrEntry, +): reportAction is ReportAction> { + return isActionOfType( + reportAction, + CONST.REPORT.ACTIONS.TYPE.ROOM_CHANGE_LOG.INVITE_TO_ROOM, + CONST.REPORT.ACTIONS.TYPE.ROOM_CHANGE_LOG.REMOVE_FROM_ROOM, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.INVITE_TO_ROOM, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.REMOVE_FROM_ROOM, + CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.LEAVE_POLICY, ); } -function isInviteMemberAction(reportAction: OnyxEntry) { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOM_CHANGE_LOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.INVITE_TO_ROOM; +function isInviteMemberAction( + reportAction: OnyxEntry, +): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.ROOM_CHANGE_LOG.INVITE_TO_ROOM, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.INVITE_TO_ROOM); +} + +function isLeavePolicyAction(reportAction: OnyxEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.LEAVE_POLICY); +} + +function isReimbursementDeQueuedAction(reportAction: OnyxEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_DEQUEUED); +} + +function isClosedAction(reportAction: OnyxEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.CLOSED); } -function isLeavePolicyAction(reportAction: OnyxEntry) { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.LEAVE_POLICY; +function isRenamedAction(reportAction: OnyxEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.RENAMED); } -function isReimbursementDeQueuedAction(reportAction: OnyxEntry): reportAction is ReportActionBase & OriginalMessageReimbursementDequeued { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_DEQUEUED; +function isRoomChangeLogAction(reportAction: OnyxEntry): reportAction is ReportAction> { + return isActionOfType(reportAction, ...Object.values(CONST.REPORT.ACTIONS.TYPE.ROOM_CHANGE_LOG)); } /** @@ -230,9 +288,9 @@ function getParentReportAction(report: OnyxInputOrEntry | EmptyObject): */ function isSentMoneyReportAction(reportAction: OnyxEntry): boolean { return ( - reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && - (reportAction?.originalMessage as IOUMessage)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && - !!(reportAction?.originalMessage as IOUMessage)?.IOUDetails + isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.IOU) && + getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && + !!getOriginalMessage(reportAction)?.IOUDetails ); } @@ -241,11 +299,14 @@ function isSentMoneyReportAction(reportAction: OnyxEntry | EmptyObject): boolean { + if (isEmptyObject(parentReportAction) || !isMoneyRequestAction(parentReportAction)) { + return false; + } + const originalMessage = getOriginalMessage(parentReportAction); return ( - parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && - (parentReportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || - parentReportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK || - (parentReportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && !!parentReportAction.originalMessage.IOUDetails)) + originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || + originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK || + (originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && !!originalMessage?.IOUDetails) ); } @@ -324,7 +385,10 @@ function getCombinedReportActions(reportActions: ReportAction[], transactionThre const isSelfDM = report?.chatType === CONST.REPORT.CHAT_TYPE.SELF_DM; // Filter out request and send money request actions because we don't want to show any preview actions for one transaction reports const filteredReportActions = [...reportActions, ...filteredTransactionThreadReportActions].filter((action) => { - const actionType = (action as OriginalMessageIOU).originalMessage?.type ?? ''; + if (!isMoneyRequestAction(action)) { + return true; + } + const actionType = getOriginalMessage(action)?.type ?? ''; if (isSelfDM) { return actionType !== CONST.IOU.REPORT_ACTION_TYPE.CREATE && !isSentMoneyReportAction(action); } @@ -391,7 +455,17 @@ function getMostRecentIOURequestActionID(reportActions: ReportAction[] | null): CONST.IOU.REPORT_ACTION_TYPE.SPLIT, CONST.IOU.REPORT_ACTION_TYPE.TRACK, ]; - const iouRequestActions = reportActions?.filter((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && iouRequestTypes.includes(action.originalMessage.type)) ?? []; + const iouRequestActions = + reportActions?.filter((action) => { + if (!isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.IOU)) { + return false; + } + const actionType = getOriginalMessage(action)?.type; + if (!actionType) { + return false; + } + return iouRequestTypes.includes(actionType); + }) ?? []; if (iouRequestActions.length === 0) { return null; @@ -405,7 +479,7 @@ function getMostRecentIOURequestActionID(reportActions: ReportAction[] | null): * Returns array of links inside a given report action */ function extractLinksFromMessageHtml(reportAction: OnyxEntry): string[] { - const htmlContent = reportAction?.message?.[0]?.html; + const htmlContent = getReportActionHtml(reportAction); const regex = CONST.REGEX_LINK_IN_ANCHOR; @@ -478,13 +552,13 @@ function isConsecutiveActionMadeByPreviousActor(reportActions: ReportAction[] | return false; } - if (isReportActionSubmitted(currentAction)) { + if (isSubmittedAction(currentAction)) { const currentActionAdminAccountID = currentAction.adminAccountID; return currentActionAdminAccountID === previousAction.actorAccountID || currentActionAdminAccountID === previousAction.adminAccountID; } - if (isReportActionSubmitted(previousAction)) { + if (isSubmittedAction(previousAction)) { return typeof previousAction.adminAccountID === 'number' ? currentAction.actorAccountID === previousAction.adminAccountID : currentAction.actorAccountID === previousAction.actorAccountID; @@ -508,13 +582,13 @@ function isReportActionDeprecated(reportAction: OnyxEntry, key: st return true; } - const deprecatedOldDotReportActions: ActionName[] = [ + const deprecatedOldDotReportActions: ReportActionName[] = [ CONST.REPORT.ACTIONS.TYPE.DELETED_ACCOUNT, CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_REQUESTED, CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_SETUP_REQUESTED, CONST.REPORT.ACTIONS.TYPE.DONATION, ]; - if (deprecatedOldDotReportActions.includes(reportAction.actionName as ActionName)) { + if (deprecatedOldDotReportActions.includes(reportAction.actionName as ReportActionName)) { Log.info('Front end filtered out reportAction for being an older, deprecated report action', false, reportAction); return true; } @@ -523,7 +597,7 @@ function isReportActionDeprecated(reportAction: OnyxEntry, key: st } const {POLICY_CHANGE_LOG: policyChangelogTypes, ROOM_CHANGE_LOG: roomChangeLogTypes, ...otherActionTypes} = CONST.REPORT.ACTIONS.TYPE; -const supportedActionTypes: ActionName[] = [...Object.values(otherActionTypes), ...Object.values(policyChangelogTypes), ...Object.values(roomChangeLogTypes)]; +const supportedActionTypes: ReportActionName[] = [...Object.values(otherActionTypes), ...Object.values(policyChangelogTypes), ...Object.values(roomChangeLogTypes)]; /** * Checks if a reportAction is fit for display, meaning that it's not deprecated, is of a valid @@ -587,8 +661,8 @@ function shouldHideNewMarker(reportAction: OnyxEntry): boolean { * Checks whether an action is actionable track expense. * */ -function isActionableTrackExpense(reportAction: OnyxInputOrEntry): reportAction is ReportActionBase & OriginalMessageActionableTrackedExpenseWhisper { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_TRACK_EXPENSE_WHISPER; +function isActionableTrackExpense(reportAction: OnyxInputOrEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_TRACK_EXPENSE_WHISPER); } /** @@ -596,7 +670,7 @@ function isActionableTrackExpense(reportAction: OnyxInputOrEntry): * */ function isResolvedActionTrackExpense(reportAction: OnyxEntry): boolean { - const resolution = (reportAction?.originalMessage as OriginalMessageActionableMentionWhisper['originalMessage'])?.resolution; + const resolution = reportAction && 'resolution' in reportAction ? reportAction?.resolution : null; return isActionableTrackExpense(reportAction) && !!resolution; } @@ -628,7 +702,7 @@ function shouldReportActionBeVisibleAsLastAction(reportAction: OnyxInputOrEntry< * which includes a baseURL placeholder that's replaced in the client. */ function replaceBaseURLInPolicyChangeLogAction(reportAction: ReportAction): ReportAction { - if (!reportAction?.message || !policyChangeActionsSet.has(reportAction?.actionName)) { + if (!reportAction?.message || !isPolicyChangeLogAction(reportAction)) { return reportAction; } @@ -638,8 +712,8 @@ function replaceBaseURLInPolicyChangeLogAction(reportAction: ReportAction): Repo return updatedReportAction; } - if (updatedReportAction.message[0]) { - updatedReportAction.message[0].html = reportAction.message?.[0]?.html?.replace('%baseURL', environmentURL); + if (Array.isArray(updatedReportAction.message) && updatedReportAction.message[0]) { + updatedReportAction.message[0].html = getReportActionHtml(reportAction)?.replace('%baseURL', environmentURL); } return updatedReportAction; @@ -661,7 +735,7 @@ function getLastVisibleMessage( reportAction: OnyxInputOrEntry | undefined = undefined, ): LastVisibleMessage { const lastVisibleAction = reportAction ?? getLastVisibleAction(reportID, actionsToMerge); - const message = lastVisibleAction?.message?.[0]; + const message = getReportActionMessage(lastVisibleAction); if (message && isReportMessageAttachment(message)) { return { @@ -677,7 +751,7 @@ function getLastVisibleMessage( }; } - let messageText = message?.text ?? ''; + let messageText = getTextFromHtml(message?.html) ?? ''; if (messageText) { messageText = StringUtils.lineBreaksToSpaces(String(messageText)).substring(0, CONST.REPORT.LAST_MESSAGE_TEXT_MAX_LENGTH).trim(); } @@ -772,10 +846,10 @@ function getLatestReportActionFromOnyxData(onyxData: OnyxUpdate[] | null): NonNu */ function getLinkedTransactionID(reportActionOrID: string | OnyxEntry, reportID?: string): string | null { const reportAction = typeof reportActionOrID === 'string' ? allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]?.[reportActionOrID] : reportActionOrID; - if (!reportAction || reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { + if (!reportAction || !isMoneyRequestAction(reportAction)) { return null; } - return reportAction.originalMessage?.IOUTransactionID ?? null; + return getOriginalMessage(reportAction)?.IOUTransactionID ?? null; } function getReportAction(reportID: string, reportActionID: string): ReportAction | undefined { @@ -824,9 +898,10 @@ function getMostRecentReportActionLastModified(): string { /** * @returns The report preview action or `null` if one couldn't be found */ -function getReportPreviewAction(chatReportID: string, iouReportID: string): OnyxEntry { +function getReportPreviewAction(chatReportID: string, iouReportID: string): OnyxEntry> { return Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`] ?? {}).find( - (reportAction) => reportAction && reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW && reportAction.originalMessage.linkedReportID === iouReportID, + (reportAction): reportAction is ReportAction => + reportAction && isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) && getOriginalMessage(reportAction)?.linkedReportID === iouReportID, ); } @@ -834,18 +909,14 @@ function getReportPreviewAction(chatReportID: string, iouReportID: string): Onyx * Get the iouReportID for a given report action. */ function getIOUReportIDFromReportActionPreview(reportAction: OnyxEntry): string { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW ? reportAction.originalMessage.linkedReportID : '-1'; -} - -function isCreatedTaskReportAction(reportAction: OnyxEntry): boolean { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT && !!reportAction.originalMessage?.taskReportID; + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) ? getOriginalMessage(reportAction)?.linkedReportID ?? '-1' : '-1'; } /** * A helper method to identify if the message is deleted or not. */ function isMessageDeleted(reportAction: OnyxInputOrEntry): boolean { - return reportAction?.message?.[0]?.isDeletedParentAction ?? false; + return getReportActionMessage(reportAction)?.isDeletedParentAction ?? false; } /** @@ -855,16 +926,16 @@ function getNumberOfMoneyRequests(reportPreviewAction: OnyxEntry): return reportPreviewAction?.childMoneyRequestCount ?? 0; } -function isSplitBillAction(reportAction: OnyxInputOrEntry): boolean { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; +function isSplitBillAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.IOU) && getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; } -function isTrackExpenseAction(reportAction: OnyxEntry): boolean { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && (reportAction.originalMessage as IOUMessage).type === CONST.IOU.REPORT_ACTION_TYPE.TRACK; +function isTrackExpenseAction(reportAction: OnyxEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.IOU) && getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK; } -function isPayAction(reportAction: OnyxInputOrEntry): boolean { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && (reportAction.originalMessage as IOUMessage).type === CONST.IOU.REPORT_ACTION_TYPE.PAY; +function isPayAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.IOU) && getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY; } function isTaskAction(reportAction: OnyxEntry): boolean { @@ -901,20 +972,26 @@ function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEn CONST.IOU.REPORT_ACTION_TYPE.TRACK, ]; - const iouRequestActions = reportActionsArray.filter( - (action) => - action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && - (iouRequestTypes.includes(action.originalMessage.type) ?? []) && + const iouRequestActions = reportActionsArray.filter((action) => { + if (!isMoneyRequestAction(action)) { + return false; + } + const originalMessage = getOriginalMessage(action); + const actionType = originalMessage?.type; + return ( + actionType && + (iouRequestTypes.includes(actionType) ?? []) && action.childReportID && // Include deleted IOU reportActions if: // - they have an assocaited IOU transaction ID or // - they have visibile childActions (like comments) that we'd want to display // - the action is pending deletion and the user is offline - (!!action.originalMessage.IOUTransactionID || + (!!originalMessage?.IOUTransactionID || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing (isMessageDeleted(action) && action.childVisibleActionCount) || - (action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && (isOffline ?? isNetworkOffline))), - ); + (action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && (isOffline ?? isNetworkOffline))) + ); + }); // If we don't have any IOU request actions, or we have more than one IOU request actions, this isn't a oneTransaction report if (!iouRequestActions.length || iouRequestActions.length > 1) { @@ -923,7 +1000,7 @@ function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEn // If there's only one IOU request action associated with the report but it's been deleted, then we don't consider this a oneTransaction report // and want to display it using the standard view - if (((iouRequestActions[0] as OriginalMessageIOU).originalMessage?.deleted ?? '') !== '') { + if (isMoneyRequestAction(iouRequestActions[0]) && (getOriginalMessage(iouRequestActions[0])?.deleted ?? '') !== '') { return; } @@ -953,7 +1030,7 @@ function getAllReportActions(reportID: string): ReportActions { * */ function isReportActionAttachment(reportAction: OnyxInputOrEntry): boolean { - const message = reportAction?.message?.[0]; + const message = getReportActionMessage(reportAction); if (reportAction && ('isAttachment' in reportAction || 'attachmentInfo' in reportAction)) { return reportAction?.isAttachment ?? !!reportAction?.attachmentInfo ?? false; @@ -972,7 +1049,7 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea return false; } - const actions: ActionName[] = [CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, CONST.REPORT.ACTIONS.TYPE.IOU, CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE]; + const actions: ReportActionName[] = [CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, CONST.REPORT.ACTIONS.TYPE.IOU, CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE]; return actions.includes(reportAction.actionName); } @@ -981,6 +1058,10 @@ function getMemberChangeMessageElements(reportAction: OnyxEntry): const isInviteAction = isInviteMemberAction(reportAction); const isLeaveAction = isLeavePolicyAction(reportAction); + if (!isMemberChangeAction(reportAction)) { + return []; + } + // Currently, we only render messages when members are invited let verb = Localize.translateLocal('workspace.invite.removed'); if (isInviteAction) { @@ -991,7 +1072,7 @@ function getMemberChangeMessageElements(reportAction: OnyxEntry): verb = Localize.translateLocal('workspace.invite.leftWorkspace'); } - const originalMessage = reportAction?.originalMessage as ChangeLog; + const originalMessage = getOriginalMessage(reportAction); const targetAccountIDs: number[] = originalMessage?.targetAccountIDs ?? []; const personalDetails = PersonalDetailsUtils.getPersonalDetailsByIDs(targetAccountIDs, 0); @@ -1041,6 +1122,21 @@ function getMemberChangeMessageElements(reportAction: OnyxEntry): ]; } +function getReportActionHtml(reportAction: PartialReportAction): string { + return getReportActionMessage(reportAction)?.html ?? ''; +} + +function getReportActionText(reportAction: PartialReportAction): string { + const html = getReportActionHtml(reportAction); + const parser = new ExpensiMark(); + return html ? parser.htmlToText(html) : ''; +} + +function getTextFromHtml(html?: string): string { + const parser = new ExpensiMark(); + return html ? parser.htmlToText(html) : ''; +} + function getMemberChangeMessageFragment(reportAction: OnyxEntry): Message { const messageElements: readonly MemberChangeMessageElement[] = getMemberChangeMessageElements(reportAction); const html = messageElements @@ -1058,7 +1154,7 @@ function getMemberChangeMessageFragment(reportAction: OnyxEntry): return { html: `${html}`, - text: reportAction?.message?.[0] ? reportAction?.message?.[0]?.text : '', + text: getReportActionMessage(reportAction) ? getReportActionText(reportAction) : '', type: CONST.REPORT.MESSAGE.TYPE.COMMENT, }; } @@ -1101,7 +1197,10 @@ function isOldDotReportAction(action: ReportAction): boolean { * For now, we just concat all of the text elements of the message to create the full message. */ function getMessageOfOldDotReportAction(reportAction: OnyxEntry): string { - return reportAction?.message?.map((element) => element?.text).join('') ?? ''; + if (!Array.isArray(reportAction?.message)) { + return getReportActionText(reportAction); + } + return reportAction?.message?.map((element) => getTextFromHtml(element?.html)).join('') ?? ''; } function getMemberChangeMessagePlainText(reportAction: OnyxEntry): string { @@ -1133,16 +1232,16 @@ function hasRequestFromCurrentAccount(reportID: string, currentAccountID: number * Checks if a given report action corresponds to an actionable mention whisper. * @param reportAction */ -function isActionableMentionWhisper(reportAction: OnyxEntry): reportAction is ReportActionBase & OriginalMessageActionableMentionWhisper { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER; +function isActionableMentionWhisper(reportAction: OnyxEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER); } /** * Checks if a given report action corresponds to an actionable report mention whisper. * @param reportAction */ -function isActionableReportMentionWhisper(reportAction: OnyxEntry): reportAction is ReportActionBase & OriginalMessageActionableReportMentionWhisper { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_REPORT_MENTION_WHISPER; +function isActionableReportMentionWhisper(reportAction: OnyxEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_REPORT_MENTION_WHISPER); } /** @@ -1150,8 +1249,11 @@ function isActionableReportMentionWhisper(reportAction: OnyxEntry) * @param reportAction * @returns the actionable mention whisper message. */ -function getActionableMentionWhisperMessage(reportAction: OnyxEntry): string { - const originalMessage = reportAction?.originalMessage as OriginalMessageActionableMentionWhisper['originalMessage']; +function getActionableMentionWhisperMessage(reportAction: OnyxEntry>): string { + if (!reportAction) { + return ''; + } + const originalMessage = getOriginalMessage(reportAction); const targetAccountIDs: number[] = originalMessage?.inviteeAccountIDs ?? []; const personalDetails = PersonalDetailsUtils.getPersonalDetailsByIDs(targetAccountIDs, 0); const mentionElements = targetAccountIDs.map((accountID): string => { @@ -1197,8 +1299,8 @@ function isCurrentActionUnread(report: Report | EmptyObject, reportAction: Repor * Checks if a given report action corresponds to a join request action. * @param reportAction */ -function isActionableJoinRequest(reportAction: OnyxEntry): reportAction is ReportActionBase & OriginalMessageJoinPolicyChangeLog { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_JOIN_REQUEST; +function isActionableJoinRequest(reportAction: OnyxEntry): reportAction is ReportAction { + return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_JOIN_REQUEST); } /** @@ -1208,7 +1310,7 @@ function isActionableJoinRequest(reportAction: OnyxEntry): reportA function isActionableJoinRequestPending(reportID: string): boolean { const sortedReportActions = getSortedReportActions(Object.values(getAllReportActions(reportID))); const findPendingRequest = sortedReportActions.find( - (reportActionItem) => isActionableJoinRequest(reportActionItem) && reportActionItem.originalMessage.choice === ('' as JoinWorkspaceResolution), + (reportActionItem) => isActionableJoinRequest(reportActionItem) && getOriginalMessage(reportActionItem)?.choice === ('' as JoinWorkspaceResolution), ); return !!findPendingRequest; } @@ -1221,12 +1323,15 @@ function isApprovedOrSubmittedReportAction(action: OnyxEntry | Emp * Gets the text version of the message in a report action */ function getReportActionMessageText(reportAction: OnyxEntry | EmptyObject): string { - return reportAction?.message?.reduce((acc, curr) => `${acc}${curr?.text}`, '') ?? ''; + if (!Array.isArray(reportAction?.message)) { + return getReportActionText(reportAction); + } + return reportAction?.message?.reduce((acc, curr) => `${acc}${getTextFromHtml(curr?.html)}`, '') ?? ''; } -function getDismissedViolationMessageText(originalMessage: OriginalMessageDismissedViolation['originalMessage']): string { - const reason = originalMessage.reason; - const violationName = originalMessage.violationName; +function getDismissedViolationMessageText(originalMessage: ReportAction['originalMessage']): string { + const reason = originalMessage?.reason; + const violationName = originalMessage?.violationName; return Localize.translateLocal(`violationDismissal.${violationName}.${reason}` as TranslationPaths); } @@ -1244,13 +1349,6 @@ function wasActionTakenByCurrentUser(reportAction: OnyxInputOrEntry): boolean { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.TRIPPREVIEW; -} - export { extractLinksFromMessageHtml, getDismissedViolationMessageText, @@ -1316,11 +1414,23 @@ export { isCurrentActionUnread, isActionableJoinRequest, isActionableJoinRequestPending, + getReportActionText, + getReportActionHtml, + getReportActionMessage, + getOriginalMessage, + isActionOfType, isActionableTrackExpense, getAllReportActions, isLinkedTransactionHeld, wasActionTakenByCurrentUser, isResolvedActionTrackExpense, + isClosedAction, + isRenamedAction, + isRoomChangeLogAction, + isChronosOOOListAction, + isAddCommentAction, + isPolicyChangeLogAction, + getTextFromHtml, isTripPreview, }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a7ead4e100fa..0f6cbaad49fa 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -7,6 +7,7 @@ import lodashIntersection from 'lodash/intersection'; import lodashIsEqual from 'lodash/isEqual'; import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; +import type {OriginalMessageModifiedExpense} from 'src/types/onyx/OriginalMessage'; import type {TupleToUnion, ValueOf} from 'type-fest'; import type {FileObject} from '@components/AttachmentModal'; import {FallbackAvatar} from '@components/Icon/Expensicons'; @@ -38,22 +39,10 @@ import type { } from '@src/types/onyx'; import type {Participant} from '@src/types/onyx/IOU'; import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; -import type { - ChangeLog, - IOUMessage, - ModifiedExpense, - OriginalMessageActionName, - OriginalMessageApproved, - OriginalMessageCreated, - OriginalMessageDismissedViolation, - OriginalMessageReimbursementDequeued, - OriginalMessageRenamed, - OriginalMessageSubmitted, - PaymentMethodType, -} from '@src/types/onyx/OriginalMessage'; +import type {OriginalMessageChangeLog, PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import type {Status} from '@src/types/onyx/PersonalDetails'; import type {NotificationPreference, Participants, PendingChatMember, Participant as ReportParticipant} from '@src/types/onyx/Report'; -import type {Message, ReportActionBase, ReportActions, ReportPreviewAction} from '@src/types/onyx/ReportAction'; +import type {Message, ReportActions} from '@src/types/onyx/ReportAction'; import type {Comment, Receipt, TransactionChanges, WaypointCollection} from '@src/types/onyx/Transaction'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -101,7 +90,7 @@ type SpendBreakdown = { type ParticipantDetails = [number, string, AvatarSource, AvatarSource]; type OptimisticAddCommentReportAction = Pick< - ReportAction, + ReportAction, | 'reportActionID' | 'actionName' | 'actorAccountID' @@ -181,6 +170,8 @@ type OptimisticIOUReportAction = Pick< | 'childCommenterCount' >; +type PartialReportAction = OnyxInputOrEntry | Partial | OptimisticIOUReportAction | OptimisticApprovedReportAction | OptimisticSubmittedReportAction | undefined; + type ReportRouteParams = { reportID: string; isSubReportPageRoute: boolean; @@ -192,12 +183,12 @@ type ReportOfflinePendingActionAndErrors = { }; type OptimisticApprovedReportAction = Pick< - ReportAction & OriginalMessageApproved, + ReportAction, 'actionName' | 'actorAccountID' | 'automatic' | 'avatar' | 'isAttachment' | 'originalMessage' | 'message' | 'person' | 'reportActionID' | 'shouldShow' | 'created' | 'pendingAction' >; type OptimisticSubmittedReportAction = Pick< - ReportAction & OriginalMessageSubmitted, + ReportAction, | 'actionName' | 'actorAccountID' | 'adminAccountID' @@ -229,7 +220,7 @@ type OptimisticEditedTaskReportAction = Pick< >; type OptimisticClosedReportAction = Pick< - ReportAction, + ReportAction, 'actionName' | 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'message' | 'originalMessage' | 'pendingAction' | 'person' | 'reportActionID' | 'shouldShow' >; @@ -238,11 +229,15 @@ type OptimisticDismissedViolationReportAction = Pick< 'actionName' | 'actorAccountID' | 'avatar' | 'created' | 'message' | 'originalMessage' | 'person' | 'reportActionID' | 'shouldShow' | 'pendingAction' >; -type OptimisticCreatedReportAction = OriginalMessageCreated & - Pick; +type OptimisticCreatedReportAction = Pick< + ReportAction, + 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'message' | 'person' | 'reportActionID' | 'shouldShow' | 'pendingAction' | 'actionName' +>; -type OptimisticRenamedReportAction = OriginalMessageRenamed & - Pick; +type OptimisticRenamedReportAction = Pick< + ReportAction, + 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'message' | 'person' | 'reportActionID' | 'shouldShow' | 'pendingAction' | 'actionName' | 'originalMessage' +>; type OptimisticChatReport = Pick< Report, @@ -316,7 +311,7 @@ type OptimisticWorkspaceChats = { }; type OptimisticModifiedExpenseReportAction = Pick< - ReportAction, + ReportAction, 'actionName' | 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'isAttachment' | 'message' | 'originalMessage' | 'person' | 'pendingAction' | 'reportActionID' | 'shouldShow' > & {reportID?: string}; @@ -735,7 +730,7 @@ function isTaskReport(report: OnyxInputOrEntry): boolean { * In this case, we have added the key to the report itself */ function isCanceledTaskReport(report: OnyxInputOrEntry | EmptyObject = {}, parentReportAction: OnyxInputOrEntry | EmptyObject = {}): boolean { - if (!isEmptyObject(parentReportAction) && (parentReportAction?.message?.[0]?.isDeletedParentAction ?? false)) { + if (!isEmptyObject(parentReportAction) && (ReportActionsUtils.getReportActionMessage(parentReportAction)?.isDeletedParentAction ?? false)) { return true; } @@ -1549,9 +1544,9 @@ function canDeleteReportAction(reportAction: OnyxInputOrEntry, rep const isActionOwner = reportAction?.actorAccountID === currentUserAccountID; const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`] ?? null; - if (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { + if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { // For now, users cannot delete split actions - const isSplitAction = reportAction?.originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; + const isSplitAction = ReportActionsUtils.getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; if (isSplitAction) { return false; @@ -2176,9 +2171,12 @@ function getDeletedParentActionMessageForChatReport(reportAction: OnyxEntry, reportOrID: OnyxEntry | string, shouldUseShortDisplayName = true): string { + if (!ReportActionsUtils.isMoneyRequestAction(reportAction)) { + return ''; + } const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] : reportOrID; const submitterDisplayName = getDisplayNameForParticipant(report?.ownerAccountID, shouldUseShortDisplayName) ?? ''; - const originalMessage = reportAction?.originalMessage as IOUMessage | undefined; + const originalMessage = ReportActionsUtils.getOriginalMessage(reportAction); let messageKey: TranslationPaths; if (originalMessage?.paymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY) { messageKey = 'iou.waitingOnEnabledWallet'; @@ -2193,12 +2191,15 @@ function getReimbursementQueuedActionMessage(reportAction: OnyxEntry, + reportAction: OnyxEntry>, reportOrID: OnyxEntry | EmptyObject | string, isLHNPreview = false, ): string { + if (!ReportActionsUtils.isReimbursementDeQueuedAction(reportAction)) { + return ''; + } const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] : reportOrID; - const originalMessage = reportAction?.originalMessage; + const originalMessage = ReportActionsUtils.getOriginalMessage(reportAction); const amount = originalMessage?.amount; const currency = originalMessage?.currency; const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency); @@ -2404,7 +2405,7 @@ function getPolicyExpenseChatName(report: OnyxEntry, policy?: OnyxEntry< // of the account which was merged into the current user's account. Use the name of the policy as the name of the report. if (isArchivedRoom(report)) { const lastAction = ReportActionsUtils.getLastVisibleAction(report?.reportID ?? '-1'); - const archiveReason = lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED ? lastAction?.originalMessage?.reason : CONST.REPORT.ARCHIVE_REASON.DEFAULT; + const archiveReason = ReportActionsUtils.isClosedAction(lastAction) ? ReportActionsUtils.getOriginalMessage(lastAction)?.reason : CONST.REPORT.ARCHIVE_REASON.DEFAULT; if (archiveReason === CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED && policyExpenseChatRole !== CONST.POLICY.ROLE.ADMIN) { return getPolicyName(report, false, policy); } @@ -2621,28 +2622,25 @@ function getTransactionCommentObject(transaction: OnyxEntry): Comme * This is used in conjunction with canEditRestrictedField to control editing of specific fields like amount, currency, created, receipt, and distance. * On its own, it only controls allowing/disallowing navigating to the editing pages or showing/hiding the 'Edit' icon on report actions */ -function canEditMoneyRequest(reportAction: OnyxInputOrEntry): boolean { +function canEditMoneyRequest(reportAction: OnyxInputOrEntry>): boolean { const isDeleted = ReportActionsUtils.isDeletedAction(reportAction); if (isDeleted) { return false; } - // If the report action is not IOU type, return true early - if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { - return true; - } - const allowedReportActionType: Array> = [CONST.IOU.REPORT_ACTION_TYPE.TRACK, CONST.IOU.REPORT_ACTION_TYPE.CREATE]; + const originalMessage = ReportActionsUtils.getOriginalMessage(reportAction); + const actionType = originalMessage?.type; - if (!allowedReportActionType.includes(reportAction.originalMessage.type)) { + if (!actionType || !allowedReportActionType.includes(actionType)) { return false; } - const moneyRequestReportID = reportAction?.originalMessage?.IOUReportID ?? -1; + const moneyRequestReportID = originalMessage?.IOUReportID ?? -1; if (!moneyRequestReportID) { - return reportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK; + return actionType === CONST.IOU.REPORT_ACTION_TYPE.TRACK; } const moneyRequestReport = getReportOrDraftReport(String(moneyRequestReportID)); @@ -2683,7 +2681,7 @@ function canEditFieldOfMoneyRequest(reportAction: OnyxInputOrEntry CONST.EDIT_REQUEST_FIELD.DISTANCE, ]; - if (!canEditMoneyRequest(reportAction)) { + if (!ReportActionsUtils.isMoneyRequestAction(reportAction) || !canEditMoneyRequest(reportAction)) { return false; } @@ -2692,7 +2690,7 @@ function canEditFieldOfMoneyRequest(reportAction: OnyxInputOrEntry return true; } - const iouMessage = reportAction?.originalMessage as IOUMessage; + const iouMessage = ReportActionsUtils.getOriginalMessage(reportAction); const moneyRequestReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouMessage?.IOUReportID}`] ?? ({} as Report); const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${iouMessage?.IOUTransactionID}`] ?? ({} as Transaction); @@ -2733,12 +2731,13 @@ function canEditFieldOfMoneyRequest(reportAction: OnyxInputOrEntry */ function canEditReportAction(reportAction: OnyxInputOrEntry): boolean { const isCommentOrIOU = reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU; + const message = reportAction ? ReportActionsUtils.getReportActionMessage(reportAction) : undefined; return !!( reportAction?.actorAccountID === currentUserAccountID && isCommentOrIOU && - canEditMoneyRequest(reportAction) && // Returns true for non-IOU actions - !isReportMessageAttachment(reportAction?.message?.[0]) && + (!ReportActionsUtils.isMoneyRequestAction(reportAction) || canEditMoneyRequest(reportAction)) && // Returns true for non-IOU actions + !isReportMessageAttachment(message) && (isEmptyObject(reportAction.attachmentInfo) || !reportAction.isOptimisticAction) && !ReportActionsUtils.isDeletedAction(reportAction) && !ReportActionsUtils.isCreatedTaskReportAction(reportAction) && @@ -2747,11 +2746,11 @@ function canEditReportAction(reportAction: OnyxInputOrEntry): bool } function canHoldUnholdReportAction(reportAction: OnyxInputOrEntry): {canHoldRequest: boolean; canUnholdRequest: boolean} { - if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { + if (!ReportActionsUtils.isMoneyRequestAction(reportAction)) { return {canHoldRequest: false, canUnholdRequest: false}; } - const moneyRequestReportID = reportAction?.originalMessage?.IOUReportID ?? 0; + const moneyRequestReportID = ReportActionsUtils.getOriginalMessage(reportAction)?.IOUReportID ?? 0; const moneyRequestReport = getReportOrDraftReport(String(moneyRequestReportID)); if (!moneyRequestReportID || !moneyRequestReport) { @@ -2760,7 +2759,7 @@ function canHoldUnholdReportAction(reportAction: OnyxInputOrEntry) const isRequestSettled = isSettled(moneyRequestReport?.reportID); const isApproved = isReportApproved(moneyRequestReport); - const transactionID = moneyRequestReport ? reportAction?.originalMessage?.IOUTransactionID : 0; + const transactionID = moneyRequestReport ? ReportActionsUtils.getOriginalMessage(reportAction)?.IOUTransactionID : 0; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); const parentReport = getReportOrDraftReport(String(moneyRequestReport.parentReportID)); @@ -2788,17 +2787,17 @@ function canHoldUnholdReportAction(reportAction: OnyxInputOrEntry) } const changeMoneyRequestHoldStatus = (reportAction: OnyxEntry): void => { - if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { + if (!ReportActionsUtils.isMoneyRequestAction(reportAction)) { return; } - const moneyRequestReportID = reportAction?.originalMessage?.IOUReportID ?? 0; + const moneyRequestReportID = ReportActionsUtils.getOriginalMessage(reportAction)?.IOUReportID ?? 0; const moneyRequestReport = getReportOrDraftReport(String(moneyRequestReportID)); if (!moneyRequestReportID || !moneyRequestReport) { return; } - const transactionID = reportAction?.originalMessage?.IOUTransactionID ?? ''; + const transactionID = ReportActionsUtils.getOriginalMessage(reportAction)?.IOUTransactionID ?? ''; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); const isOnHold = TransactionUtils.isOnHold(transaction); const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${moneyRequestReport.policyID}`] ?? null; @@ -2844,8 +2843,8 @@ function areAllRequestsBeingSmartScanned(iouReportID: string, reportPreviewActio function getLinkedTransaction(reportAction: OnyxEntry): Transaction | EmptyObject { let transactionID = ''; - if (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { - transactionID = (reportAction?.originalMessage as IOUMessage)?.IOUTransactionID ?? '-1'; + if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { + transactionID = ReportActionsUtils.getOriginalMessage(reportAction)?.IOUTransactionID ?? '-1'; } return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? {}; @@ -2940,9 +2939,9 @@ function getReportPreviewMessage( originalReportAction: OnyxInputOrEntry | EmptyObject = iouReportAction, ): string { const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] : reportOrID; - const reportActionMessage = iouReportAction?.message?.[0]?.html ?? ''; + const reportActionMessage = ReportActionsUtils.getReportActionHtml(iouReportAction); - if (isEmptyObject(report) || !report?.reportID) { + if (isEmptyObject(report) || !report?.reportID || isEmptyObject(iouReportAction)) { // The iouReport is not found locally after SignIn because the OpenApp API won't return iouReports if they're settled // As a temporary solution until we know how to solve this the best, we just use the message that returned from BE return reportActionMessage; @@ -3020,7 +3019,7 @@ function getReportPreviewMessage( return Localize.translateLocal('iou.fieldPending'); } - const originalMessage = iouReportAction?.originalMessage as IOUMessage | undefined; + const originalMessage = ReportActionsUtils.isMoneyRequestAction(iouReportAction) ? ReportActionsUtils.getOriginalMessage(iouReportAction) : undefined; // Show Paid preview message if it's settled or if the amount is paid & stuck at receivers end for only chat reports. if (isSettled(report.reportID) || (report.isWaitingOnBankAccount && isPreviewMessageForParentChatReport)) { @@ -3093,8 +3092,8 @@ function getModifiedExpenseOriginalMessage( transactionChanges: TransactionChanges, isFromExpenseReport: boolean, policy: OnyxInputOrEntry, -): ModifiedExpense { - const originalMessage: ModifiedExpense = {}; +): OriginalMessageModifiedExpense { + const originalMessage: OriginalMessageModifiedExpense = {}; // Remark: Comment field is the only one which has new/old prefixes for the keys (newComment/ oldComment), // all others have old/- pattern such as oldCreated/created if ('comment' in transactionChanges) { @@ -3159,7 +3158,7 @@ function getModifiedExpenseOriginalMessage( * Check if original message is an object and can be used as a ChangeLog type * @param originalMessage */ -function isChangeLogObject(originalMessage?: ChangeLog): ChangeLog | undefined { +function isChangeLogObject(originalMessage?: OriginalMessageChangeLog): OriginalMessageChangeLog | undefined { if (originalMessage && typeof originalMessage === 'object') { return originalMessage; } @@ -3171,14 +3170,21 @@ function isChangeLogObject(originalMessage?: ChangeLog): ChangeLog | undefined { * @param parentReportAction * @param parentReportActionMessage */ -function getAdminRoomInvitedParticipants(parentReportAction: ReportAction | Record, parentReportActionMessage: string) { - if (!parentReportAction?.originalMessage) { +function getAdminRoomInvitedParticipants(parentReportAction: ReportAction | EmptyObject, parentReportActionMessage: string) { + if (isEmptyObject(parentReportAction)) { + return parentReportActionMessage || Localize.translateLocal('parentReportAction.deletedMessage'); + } + if (!ReportActionsUtils.getOriginalMessage(parentReportAction)) { return parentReportActionMessage || Localize.translateLocal('parentReportAction.deletedMessage'); } - const originalMessage = isChangeLogObject(parentReportAction.originalMessage); + if (!ReportActionsUtils.isPolicyChangeLogAction(parentReportAction) || !ReportActionsUtils.isRoomChangeLogAction(parentReportAction)) { + return parentReportActionMessage || Localize.translateLocal('parentReportAction.deletedMessage'); + } + + const originalMessage = isChangeLogObject(ReportActionsUtils.getOriginalMessage(parentReportAction)); const participantAccountIDs = originalMessage?.targetAccountIDs ?? []; - const participants = participantAccountIDs.map((id) => { + const participants = participantAccountIDs.map((id: number) => { const name = getDisplayNameForParticipant(id); if (name && name?.length > 0) { return name; @@ -3232,7 +3238,7 @@ function parseReportActionHtmlToText(reportAction: OnyxEntry, repo return cachedText; } - const {html, text} = reportAction?.message?.[0] ?? {}; + const {html, text} = ReportActionsUtils.getReportActionMessage(reportAction) ?? {}; if (!html) { return text ?? ''; @@ -3322,7 +3328,7 @@ function getReportName(report: OnyxEntry, policy?: OnyxEntry): s return formatReportLastMessageText(formattedName); } - if (parentReportAction?.message?.[0]?.isDeletedParentAction) { + if (ReportActionsUtils.getReportActionMessage(parentReportAction)?.isDeletedParentAction) { return Localize.translateLocal('parentReportAction.deletedMessage'); } @@ -3332,9 +3338,9 @@ function getReportName(report: OnyxEntry, policy?: OnyxEntry): s return `[${Localize.translateLocal('common.attachment')}]`; } if ( - parentReportAction?.message?.[0]?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || - parentReportAction?.message?.[0]?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_HIDDEN || - parentReportAction?.message?.[0]?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_REMOVE + ReportActionsUtils.getReportActionMessage(parentReportAction)?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || + ReportActionsUtils.getReportActionMessage(parentReportAction)?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_HIDDEN || + ReportActionsUtils.getReportActionMessage(parentReportAction)?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_REMOVE ) { return Localize.translateLocal('parentReportAction.hiddenMessage'); } @@ -3344,7 +3350,7 @@ function getReportName(report: OnyxEntry, policy?: OnyxEntry): s if (parentReportActionMessage && isArchivedRoom(report)) { return `${parentReportActionMessage} (${Localize.translateLocal('common.archived')})`; } - if (ReportActionsUtils.isModifiedExpenseAction(parentReportAction)) { + if (!isEmptyObject(parentReportAction) && ReportActionsUtils.isModifiedExpenseAction(parentReportAction)) { return ModifiedExpenseMessage.getForReportAction(report?.reportID, parentReportAction); } @@ -3680,6 +3686,10 @@ function buildOptimisticAddCommentReportAction( text: textForNewComment, }, ], + originalMessage: { + html: htmlForNewComment, + whisperedTo: [], + }, isFirstItem: false, isAttachment, attachmentInfo, @@ -3751,15 +3761,18 @@ function buildOptimisticTaskCommentReportAction( createdOffset = 0, ): OptimisticReportAction { const reportAction = buildOptimisticAddCommentReportAction(text, undefined, undefined, createdOffset, undefined, taskReportID); - if (reportAction.reportAction.message?.[0]) { + if (Array.isArray(reportAction.reportAction.message) && reportAction.reportAction.message?.[0]) { reportAction.reportAction.message[0].taskReportID = taskReportID; + } else if (!Array.isArray(reportAction.reportAction.message) && reportAction.reportAction.message) { + reportAction.reportAction.message.taskReportID = taskReportID; } // These parameters are not saved on the reportAction, but are used to display the task in the UI // Added when we fetch the reportActions on a report reportAction.reportAction.originalMessage = { - html: reportAction.reportAction.message?.[0]?.html, - taskReportID: reportAction.reportAction.message?.[0]?.taskReportID, + html: ReportActionsUtils.getReportActionHtml(reportAction.reportAction), + taskReportID: ReportActionsUtils.getReportActionMessage(reportAction.reportAction)?.taskReportID, + whisperedTo: [], }; reportAction.reportAction.childReportID = taskReportID; reportAction.reportAction.parentReportID = parentReportID; @@ -4099,7 +4112,7 @@ function buildOptimisticIOUReportAction( ): OptimisticIOUReportAction { const IOUReportID = iouReportID || generateReportID(); - const originalMessage: IOUMessage = { + const originalMessage: ReportAction['originalMessage'] = { amount, comment, currency, @@ -4283,9 +4296,9 @@ function buildOptimisticReportPreview( chatReport: OnyxInputOrEntry, iouReport: Report, comment = '', - transaction?: OnyxInputOrEntry, + transaction: OnyxInputOrEntry = null, childReportID?: string, -): ReportAction { +): ReportAction { const hasReceipt = TransactionUtils.hasReceipt(transaction); const isReceiptBeingScanned = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction); const message = getReportPreviewMessage(iouReport); @@ -4445,11 +4458,11 @@ function buildOptimisticMovedTrackedExpenseModifiedReportAction(transactionThrea */ function updateReportPreview( iouReport: OnyxEntry, - reportPreviewAction: ReportPreviewAction, + reportPreviewAction: ReportAction, isPayRequest = false, comment = '', transaction?: OnyxEntry, -): ReportPreviewAction { +): ReportAction { const hasReceipt = TransactionUtils.hasReceipt(transaction); const recentReceiptTransactions = reportPreviewAction?.childRecentReceiptTransactionIDs ?? {}; const transactionsToKeep = TransactionUtils.getRecentTransactions(recentReceiptTransactions); @@ -4465,6 +4478,7 @@ function updateReportPreview( } const message = getReportPreviewMessage(iouReport, reportPreviewAction); + const originalMessage = ReportActionsUtils.getOriginalMessage(reportPreviewAction); return { ...reportPreviewAction, message: [ @@ -4486,15 +4500,16 @@ function updateReportPreview( // As soon as we add a transaction without a receipt to the report, it will have ready expenses, // so we remove the whisper originalMessage: { - ...(reportPreviewAction.originalMessage ?? {}), - whisperedTo: hasReceipt ? reportPreviewAction?.originalMessage?.whisperedTo : [], + ...(originalMessage ?? {}), + whisperedTo: hasReceipt ? originalMessage?.whisperedTo : [], + linkedReportID: originalMessage?.linkedReportID ?? '0', }, }; } function buildOptimisticTaskReportAction( taskReportID: string, - actionName: OriginalMessageActionName, + actionName: typeof CONST.REPORT.ACTIONS.TYPE.TASK_COMPLETED | typeof CONST.REPORT.ACTIONS.TYPE.TASK_REOPENED | typeof CONST.REPORT.ACTIONS.TYPE.TASK_CANCELLED, message = '', actorAccountID = currentUserAccountID, createdOffset = 0, @@ -4503,6 +4518,8 @@ function buildOptimisticTaskReportAction( taskReportID, type: actionName, text: message, + html: message, + whisperedTo: [], }; return { actionName, @@ -4873,7 +4890,11 @@ function buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID: numbe * * @param reason - A reason why the chat has been archived */ -function buildOptimisticClosedReportAction(emailClosingReport: string, policyName: string, reason: string = CONST.REPORT.ARCHIVE_REASON.DEFAULT): OptimisticClosedReportAction { +function buildOptimisticClosedReportAction( + emailClosingReport: string, + policyName: string, + reason: ValueOf = CONST.REPORT.ARCHIVE_REASON.DEFAULT, +): OptimisticClosedReportAction { return { actionName: CONST.REPORT.ACTIONS.TYPE.CLOSED, actorAccountID: currentUserAccountID, @@ -4913,7 +4934,9 @@ function buildOptimisticClosedReportAction(emailClosingReport: string, policyNam * Returns an optimistic Dismissed Violation Report Action. Use the originalMessage customize this to the type of * violation being dismissed. */ -function buildOptimisticDismissedViolationReportAction(originalMessage: OriginalMessageDismissedViolation['originalMessage']): OptimisticDismissedViolationReportAction { +function buildOptimisticDismissedViolationReportAction( + originalMessage: ReportAction['originalMessage'], +): OptimisticDismissedViolationReportAction { return { actionName: CONST.REPORT.ACTIONS.TYPE.DISMISSED_VIOLATION, actorAccountID: currentUserAccountID, @@ -5274,10 +5297,10 @@ function doesTransactionThreadHaveViolations( transactionViolations: OnyxCollection, parentReportAction: OnyxInputOrEntry, ): boolean { - if (parentReportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { + if (!ReportActionsUtils.isMoneyRequestAction(parentReportAction)) { return false; } - const {IOUTransactionID, IOUReportID} = parentReportAction.originalMessage ?? {}; + const {IOUTransactionID, IOUReportID} = ReportActionsUtils.getOriginalMessage(parentReportAction) ?? {}; if (!IOUTransactionID || !IOUReportID) { return false; } @@ -5298,7 +5321,10 @@ function shouldDisplayTransactionThreadViolations( transactionViolations: OnyxCollection, parentReportAction: OnyxEntry, ): boolean { - const {IOUReportID} = (parentReportAction?.originalMessage as IOUMessage) ?? {}; + if (!ReportActionsUtils.isMoneyRequestAction(parentReportAction)) { + return false; + } + const {IOUReportID} = ReportActionsUtils.getOriginalMessage(parentReportAction) ?? {}; if (isSettled(IOUReportID)) { return false; } @@ -6193,27 +6219,18 @@ function getTaskAssigneeChatOnyxData( }, ); - // BE will send different report's participants and assigneeAccountID. We clear the optimistic ones to avoid duplicated entries - successData.push( - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${assigneeChatReportID}`, - value: { - pendingFields: { - createChat: null, - }, - isOptimisticReport: false, - participants: {[assigneeAccountID]: null}, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - value: { - [assigneeAccountID]: null, + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${assigneeChatReportID}`, + value: { + pendingFields: { + createChat: null, }, + isOptimisticReport: false, + // BE will send a different participant. We clear the optimistic one to avoid duplicated entries + participants: {[assigneeAccountID]: null}, }, - ); + }); failureData.push( { @@ -6242,7 +6259,7 @@ function getTaskAssigneeChatOnyxData( // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const displayname = allPersonalDetails?.[assigneeAccountID]?.displayName || allPersonalDetails?.[assigneeAccountID]?.login || ''; optimisticAssigneeAddComment = buildOptimisticTaskCommentReportAction(taskReportID, title, assigneeAccountID, `assigned to ${displayname}`, parentReportID); - const lastAssigneeCommentText = formatReportLastMessageText(optimisticAssigneeAddComment.reportAction.message?.[0]?.text ?? ''); + const lastAssigneeCommentText = formatReportLastMessageText(ReportActionsUtils.getReportActionText(optimisticAssigneeAddComment.reportAction as ReportAction)); const optimisticAssigneeReport = { lastVisibleActionCreated: currentTime, lastMessageText: lastAssigneeCommentText, @@ -6287,18 +6304,18 @@ function getTaskAssigneeChatOnyxData( * Return iou report action display message */ function getIOUReportActionDisplayMessage(reportAction: OnyxEntry, transaction?: OnyxEntry): string { - if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { + if (!ReportActionsUtils.isMoneyRequestAction(reportAction)) { return ''; } - const originalMessage = reportAction.originalMessage; - const {IOUReportID} = originalMessage; + const originalMessage = ReportActionsUtils.getOriginalMessage(reportAction); + const {IOUReportID} = originalMessage ?? {}; const iouReport = getReportOrDraftReport(IOUReportID); let translationKey: TranslationPaths; - if (originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY) { + if (originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY) { // The `REPORT_ACTION_TYPE.PAY` action type is used for both fulfilling existing requests and sending money. To // differentiate between these two scenarios, we check if the `originalMessage` contains the `IOUDetails` // property. If it does, it indicates that this is a 'Pay someone' action. - const {amount, currency} = originalMessage.IOUDetails ?? originalMessage; + const {amount, currency} = originalMessage?.IOUDetails ?? originalMessage ?? {}; const formattedAmount = CurrencyUtils.convertToDisplayString(Math.abs(amount), currency) ?? ''; switch (originalMessage.paymentType) { @@ -6318,7 +6335,7 @@ function getIOUReportActionDisplayMessage(reportAction: OnyxEntry, const transactionDetails = getTransactionDetails(!isEmptyObject(transaction) ? transaction : undefined); const formattedAmount = CurrencyUtils.convertToDisplayString(transactionDetails?.amount ?? 0, transactionDetails?.currency); - const isRequestSettled = isSettled(originalMessage.IOUReportID); + const isRequestSettled = isSettled(originalMessage?.IOUReportID); const isApproved = isReportApproved(iouReport); if (isRequestSettled) { return Localize.translateLocal('iou.payerSettled', { @@ -6448,7 +6465,7 @@ function hasSmartscanError(reportActions: ReportAction[]) { } const IOUReportID = ReportActionsUtils.getIOUReportIDFromReportActionPreview(action); const isReportPreviewError = ReportActionsUtils.isReportPreviewAction(action) && shouldShowRBRForMissingSmartscanFields(IOUReportID) && !isSettled(IOUReportID); - const transactionID = (action.originalMessage as IOUMessage).IOUTransactionID ?? '-1'; + const transactionID = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? '-1' : '-1'; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? {}; const isSplitBillError = ReportActionsUtils.isSplitBillAction(action) && TransactionUtils.hasMissingSmartscanFields(transaction as Transaction); @@ -6746,10 +6763,10 @@ function isAllowedToSubmitDraftExpenseReport(report: OnyxEntry): boolean */ function getIndicatedMissingPaymentMethod(userWallet: OnyxEntry, reportId: string, reportAction: ReportAction): MissingPaymentMethod | undefined { const isSubmitterOfUnsettledReport = isCurrentUserSubmitter(reportId) && !isSettled(reportId); - if (!isSubmitterOfUnsettledReport || reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_QUEUED) { + if (!isSubmitterOfUnsettledReport || !ReportActionsUtils.isReimbursementQueuedAction(reportAction)) { return undefined; } - const paymentType = reportAction.originalMessage?.paymentType; + const paymentType = ReportActionsUtils.getOriginalMessage(reportAction)?.paymentType; if (paymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY) { return isEmpty(userWallet) || userWallet.tierName === CONST.WALLET.TIER_NAME.SILVER ? 'wallet' : undefined; } @@ -6900,7 +6917,7 @@ function createDraftTransactionAndNavigateToParticipantSelector(transactionID: s const linkedTrackedExpenseReportAction = Object.values(reportActions) .filter(Boolean) - .find((action) => (action.originalMessage as IOUMessage)?.IOUTransactionID === transactionID); + .find((action) => ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID === transactionID); const {created, amount, currency, merchant, mccGroup} = getTransactionDetails(transaction) ?? {}; const comment = getTransactionCommentObject(transaction); @@ -7271,5 +7288,6 @@ export type { OptimisticTaskReportAction, OptionData, TransactionDetails, + PartialReportAction, ParsingDetails, }; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index eef98b213dec..7929c2d60475 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -6,7 +6,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetails, PersonalDetailsList, ReportActions, TransactionViolation} from '@src/types/onyx'; import type Beta from '@src/types/onyx/Beta'; -import type {ChangeLog} from '@src/types/onyx/OriginalMessage'; import type Policy from '@src/types/onyx/Policy'; import type PriorityMode from '@src/types/onyx/PriorityMode'; import type Report from '@src/types/onyx/Report'; @@ -345,18 +344,13 @@ function getOptionData({ if ((result.isChatRoom || result.isPolicyExpenseChat || result.isThread || result.isTaskReport || isThreadMessage) && !result.isArchivedRoom) { const lastActionName = lastAction?.actionName ?? report.lastActionType; - if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.RENAMED) { - const newName = lastAction?.originalMessage?.newName ?? ''; + if (ReportActionsUtils.isRenamedAction(lastAction)) { + const newName = ReportActionsUtils.getOriginalMessage(lastAction)?.newName ?? ''; result.alternateText = Localize.translate(preferredLocale, 'newRoomPage.roomRenamedTo', {newName}); } else if (ReportActionsUtils.isTaskAction(lastAction)) { result.alternateText = ReportUtils.formatReportLastMessageText(TaskUtils.getTaskReportActionMessage(lastAction).text); - } else if ( - lastActionName === CONST.REPORT.ACTIONS.TYPE.ROOM_CHANGE_LOG.INVITE_TO_ROOM || - lastActionName === CONST.REPORT.ACTIONS.TYPE.ROOM_CHANGE_LOG.REMOVE_FROM_ROOM || - lastActionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.INVITE_TO_ROOM || - lastActionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.REMOVE_FROM_ROOM - ) { - const lastActionOriginalMessage = lastAction?.actionName ? (lastAction?.originalMessage as ChangeLog) : null; + } else if (ReportActionsUtils.isRoomChangeLogAction(lastAction)) { + const lastActionOriginalMessage = lastAction?.actionName ? ReportActionsUtils.getOriginalMessage(lastAction) : null; const targetAccountIDs = lastActionOriginalMessage?.targetAccountIDs ?? []; const targetAccountIDsLength = targetAccountIDs.length !== 0 ? targetAccountIDs.length : report.lastMessageHtml?.match(/]*><\/mention-user>/g)?.length ?? 0; const verb = diff --git a/src/libs/TaskUtils.ts b/src/libs/TaskUtils.ts index bd22ae134ee5..332d82915463 100644 --- a/src/libs/TaskUtils.ts +++ b/src/libs/TaskUtils.ts @@ -6,6 +6,7 @@ import type {Report} from '@src/types/onyx'; import type {Message} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import * as Localize from './Localize'; +import {getReportActionHtml, getReportActionText} from './ReportActionsUtils'; let allReports: OnyxCollection = {}; Onyx.connect({ @@ -29,8 +30,8 @@ function getTaskReportActionMessage(action: OnyxEntry): Pick { +function getReportPreviewAction(chatReportID: string, iouReportID: string): OnyxInputValue> { const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`] ?? {}; // Find the report preview action from the chat report return ( Object.values(reportActions).find( - (reportAction) => reportAction && reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW && reportAction.originalMessage.linkedReportID === iouReportID, + (reportAction): reportAction is ReportAction => + reportAction && ReportActionsUtils.isReportPreviewAction(reportAction) && ReportActionsUtils.getOriginalMessage(reportAction)?.linkedReportID === iouReportID, ) ?? null ); } @@ -526,8 +526,8 @@ function buildOnyxDataForMoneyRequest( key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, value: { ...iouReport, - lastMessageText: iouAction.message?.[0]?.text, - lastMessageHtml: iouAction.message?.[0]?.html, + lastMessageText: ReportActionsUtils.getReportActionText(iouAction), + lastMessageHtml: ReportActionsUtils.getReportActionHtml(iouAction), lastVisibleActionCreated: iouAction.created, pendingFields: { ...(shouldCreateNewMoneyRequestReport ? {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD} : {preview: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), @@ -893,8 +893,8 @@ function buildOnyxDataForInvoice( key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, value: { ...iouReport, - lastMessageText: iouAction.message?.[0]?.text, - lastMessageHtml: iouAction.message?.[0]?.html, + lastMessageText: ReportActionsUtils.getReportActionText(iouAction), + lastMessageHtml: ReportActionsUtils.getReportActionHtml(iouAction), pendingFields: { createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, }, @@ -1228,8 +1228,8 @@ function buildOnyxDataForTrackExpense( key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, value: { ...chatReport, - lastMessageText: iouAction.message?.[0]?.text, - lastMessageHtml: iouAction.message?.[0]?.html, + lastMessageText: ReportActionsUtils.getReportActionText(iouAction), + lastMessageHtml: ReportActionsUtils.getReportActionHtml(iouAction), lastReadTime: DateUtils.getDBTime(), iouReportID: iouReport?.reportID, }, @@ -1283,8 +1283,8 @@ function buildOnyxDataForTrackExpense( key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, value: { ...iouReport, - lastMessageText: iouAction.message?.[0]?.text, - lastMessageHtml: iouAction.message?.[0]?.html, + lastMessageText: ReportActionsUtils.getReportActionText(iouAction), + lastMessageHtml: ReportActionsUtils.getReportActionHtml(iouAction), pendingFields: { ...(shouldCreateNewMoneyRequestReport ? {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD} : {preview: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), }, @@ -2027,7 +2027,7 @@ function getMoneyRequestInformation( let reportPreviewAction = shouldCreateNewMoneyRequestReport ? null : getReportPreviewAction(chatReport.reportID, iouReport.reportID); if (reportPreviewAction) { - reportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction as ReportPreviewAction, false, comment, optimisticTransaction); + reportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction, false, comment, optimisticTransaction); } else { reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, iouReport, comment, optimisticTransaction); chatReport.lastVisibleActionCreated = reportPreviewAction.created; @@ -2250,12 +2250,12 @@ function getTrackExpenseInformation( linkedTrackedExpenseReportAction, ); - let reportPreviewAction: OnyxInputValue = null; + let reportPreviewAction: OnyxInputValue> = null; if (shouldUseMoneyReport && iouReport) { reportPreviewAction = shouldCreateNewMoneyRequestReport ? null : getReportPreviewAction(chatReport.reportID, iouReport.reportID); if (reportPreviewAction) { - reportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction as ReportPreviewAction, false, comment, optimisticTransaction); + reportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction, false, comment, optimisticTransaction); } else { reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, iouReport, comment, optimisticTransaction); // Generated ReportPreview action is a parent report action of the iou report. @@ -3466,7 +3466,9 @@ function requestMoney( created, merchant, receipt, - isMovingTransactionFromTrackExpense ? (linkedTrackedExpenseReportAction?.originalMessage as IOUMessage)?.IOUTransactionID : undefined, + isMovingTransactionFromTrackExpense && linkedTrackedExpenseReportAction && ReportActionsUtils.isMoneyRequestAction(linkedTrackedExpenseReportAction) + ? ReportActionsUtils.getOriginalMessage(linkedTrackedExpenseReportAction)?.IOUTransactionID + : undefined, category, tag, taxCode, @@ -3656,7 +3658,9 @@ function trackExpense( payeeAccountID, moneyRequestReportID, linkedTrackedExpenseReportAction, - isMovingTransactionFromTrackExpense ? (linkedTrackedExpenseReportAction?.originalMessage as IOUMessage)?.IOUTransactionID : undefined, + isMovingTransactionFromTrackExpense && linkedTrackedExpenseReportAction && ReportActionsUtils.isMoneyRequestAction(linkedTrackedExpenseReportAction) + ? ReportActionsUtils.getOriginalMessage(linkedTrackedExpenseReportAction)?.IOUTransactionID + : undefined, ); const activeReportID = isMoneyRequestReport ? report.reportID : chatReport.reportID; @@ -3882,8 +3886,8 @@ function createSplitsAndOnyxData( ); splitChatReport.lastReadTime = DateUtils.getDBTime(); - splitChatReport.lastMessageText = splitIOUReportAction.message?.[0]?.text; - splitChatReport.lastMessageHtml = splitIOUReportAction.message?.[0]?.html; + splitChatReport.lastMessageText = ReportActionsUtils.getReportActionText(splitIOUReportAction); + splitChatReport.lastMessageHtml = ReportActionsUtils.getReportActionHtml(splitIOUReportAction); splitChatReport.lastActorAccountID = currentUserAccountID; splitChatReport.lastVisibleActionCreated = splitIOUReportAction.created; @@ -4139,7 +4143,7 @@ function createSplitsAndOnyxData( let oneOnOneReportPreviewAction = getReportPreviewAction(oneOnOneChatReport.reportID, oneOnOneIOUReport.reportID); if (oneOnOneReportPreviewAction) { - oneOnOneReportPreviewAction = ReportUtils.updateReportPreview(oneOnOneIOUReport, oneOnOneReportPreviewAction as ReportPreviewAction); + oneOnOneReportPreviewAction = ReportUtils.updateReportPreview(oneOnOneIOUReport, oneOnOneReportPreviewAction); } else { oneOnOneReportPreviewAction = ReportUtils.buildOptimisticReportPreview(oneOnOneChatReport, oneOnOneIOUReport); } @@ -4461,8 +4465,8 @@ function startSplitBill({ ); splitChatReport.lastReadTime = DateUtils.getDBTime(); - splitChatReport.lastMessageText = splitIOUReportAction.message?.[0]?.text; - splitChatReport.lastMessageHtml = splitIOUReportAction.message?.[0]?.html; + splitChatReport.lastMessageText = ReportActionsUtils.getReportActionText(splitIOUReportAction); + splitChatReport.lastMessageHtml = ReportActionsUtils.getReportActionHtml(splitIOUReportAction); // If we have an existing splitChatReport (group chat or workspace) use it's pending fields, otherwise indicate that we are adding a chat if (!existingSplitChatReport) { @@ -4855,7 +4859,7 @@ function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportA let oneOnOneReportPreviewAction = getReportPreviewAction(oneOnOneChatReport?.reportID ?? '-1', oneOnOneIOUReport?.reportID ?? '-1'); if (oneOnOneReportPreviewAction) { - oneOnOneReportPreviewAction = ReportUtils.updateReportPreview(oneOnOneIOUReport, oneOnOneReportPreviewAction as ReportPreviewAction); + oneOnOneReportPreviewAction = ReportUtils.updateReportPreview(oneOnOneIOUReport, oneOnOneReportPreviewAction); } else { oneOnOneReportPreviewAction = ReportUtils.buildOptimisticReportPreview(oneOnOneChatReport, oneOnOneIOUReport, '', oneOnOneTransaction); } @@ -4991,7 +4995,7 @@ function editRegularMoneyRequest( '', false, ); - updatedMoneyRequestReport.lastMessageText = lastMessage[0].text; + updatedMoneyRequestReport.lastMessageText = ReportActionsUtils.getTextFromHtml(lastMessage[0].html); updatedMoneyRequestReport.lastMessageHtml = lastMessage[0].html; // Update the last message of the chat report @@ -5267,10 +5271,11 @@ function updateMoneyRequestAmountAndCurrency({ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false) { // STEP 1: Get all collections we're updating - const iouReportID = reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? reportAction.originalMessage.IOUReportID : '-1'; + const iouReportID = ReportActionsUtils.isMoneyRequestAction(reportAction) ? ReportActionsUtils.getOriginalMessage(reportAction)?.IOUReportID : '-1'; const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`] ?? null; const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`]; - const reportPreviewAction = getReportPreviewAction(iouReport?.chatReportID ?? '-1', iouReport?.reportID ?? '-1'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const reportPreviewAction = getReportPreviewAction(iouReport?.chatReportID ?? '-1', iouReport?.reportID ?? '-1')!; const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; const transactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; const transactionThreadID = reportAction.childReportID; @@ -5300,7 +5305,7 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor }, ], originalMessage: { - IOUTransactionID: null, + IOUTransactionID: undefined, }, errors: undefined, }, @@ -5314,7 +5319,7 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor // STEP 4: Update the iouReport and reportPreview with new totals and messages if it wasn't deleted let updatedIOUReport: OnyxTypes.Report | null; const currency = TransactionUtils.getCurrency(transaction); - const updatedReportPreviewAction: OnyxTypes.ReportAction | EmptyObject = {...reportPreviewAction}; + const updatedReportPreviewAction: OnyxTypes.ReportAction = {...reportPreviewAction}; updatedReportPreviewAction.pendingAction = shouldDeleteIOUReport ? CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE : CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE; if (iouReport && ReportUtils.isExpenseReport(iouReport)) { updatedIOUReport = {...iouReport}; @@ -5343,9 +5348,14 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor amount: CurrencyUtils.convertToDisplayString(updatedIOUReport?.total, updatedIOUReport?.currency), }); - if (updatedReportPreviewAction?.message?.[0]) { - updatedReportPreviewAction.message[0].text = messageText; - updatedReportPreviewAction.message[0].deleted = shouldDeleteIOUReport ? DateUtils.getDBTime() : ''; + if (ReportActionsUtils.getReportActionMessage(updatedReportPreviewAction)) { + if (Array.isArray(updatedReportPreviewAction?.message) && updatedReportPreviewAction.message?.[0]) { + updatedReportPreviewAction.message[0].text = messageText; + updatedReportPreviewAction.message[0].deleted = shouldDeleteIOUReport ? DateUtils.getDBTime() : ''; + } else if (!Array.isArray(updatedReportPreviewAction.message) && updatedReportPreviewAction.message) { + updatedReportPreviewAction.message.text = messageText; + updatedReportPreviewAction.message.deleted = shouldDeleteIOUReport ? DateUtils.getDBTime() : ''; + } } if (updatedReportPreviewAction && reportPreviewAction?.childMoneyRequestCount && reportPreviewAction?.childMoneyRequestCount > 0) { @@ -5682,8 +5692,8 @@ function getSendMoneyParams( key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticIOUReport.reportID}`, value: { ...optimisticIOUReport, - lastMessageText: optimisticIOUReportAction.message?.[0]?.text, - lastMessageHtml: optimisticIOUReportAction.message?.[0]?.html, + lastMessageText: ReportActionsUtils.getReportActionText(optimisticIOUReportAction), + lastMessageHtml: ReportActionsUtils.getReportActionHtml(optimisticIOUReportAction), }, }; const optimisticTransactionThreadData: OnyxUpdate = { @@ -5943,7 +5953,7 @@ function getPayMoneyRequestParams( let optimisticReportPreviewAction = null; const reportPreviewAction = getReportPreviewAction(chatReport.reportID, iouReport.reportID); if (reportPreviewAction) { - optimisticReportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction as ReportPreviewAction, true); + optimisticReportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction, true); } let currentNextStep = null; let optimisticNextStep = null; @@ -5962,8 +5972,8 @@ function getPayMoneyRequestParams( lastVisibleActionCreated: optimisticIOUReportAction.created, hasOutstandingChildRequest: false, iouReportID: null, - lastMessageText: optimisticIOUReportAction.message?.[0]?.text, - lastMessageHtml: optimisticIOUReportAction.message?.[0]?.html, + lastMessageText: ReportActionsUtils.getReportActionText(optimisticIOUReportAction), + lastMessageHtml: ReportActionsUtils.getReportActionHtml(optimisticIOUReportAction), }, }, { @@ -5981,8 +5991,8 @@ function getPayMoneyRequestParams( key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, value: { ...iouReport, - lastMessageText: optimisticIOUReportAction.message?.[0]?.text, - lastMessageHtml: optimisticIOUReportAction.message?.[0]?.html, + lastMessageText: ReportActionsUtils.getReportActionText(optimisticIOUReportAction), + lastMessageHtml: ReportActionsUtils.getReportActionHtml(optimisticIOUReportAction), hasOutstandingChildRequest: false, statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, pendingFields: { @@ -6222,8 +6232,8 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject, full key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, value: { ...expenseReport, - lastMessageText: optimisticApprovedReportAction.message?.[0]?.text, - lastMessageHtml: optimisticApprovedReportAction.message?.[0]?.html, + lastMessageText: ReportActionsUtils.getReportActionText(optimisticApprovedReportAction), + lastMessageHtml: ReportActionsUtils.getReportActionHtml(optimisticApprovedReportAction), stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.APPROVED, pendingFields: { @@ -6356,8 +6366,8 @@ function submitReport(expenseReport: OnyxTypes.Report) { key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, value: { ...expenseReport, - lastMessageText: optimisticSubmittedReportAction.message?.[0]?.text ?? '', - lastMessageHtml: optimisticSubmittedReportAction.message?.[0]?.html ?? '', + lastMessageText: ReportActionsUtils.getReportActionText(optimisticSubmittedReportAction), + lastMessageHtml: ReportActionsUtils.getReportActionHtml(optimisticSubmittedReportAction), stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, }, @@ -6482,8 +6492,8 @@ function cancelPayment(expenseReport: OnyxTypes.Report, chatReport: OnyxTypes.Re key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, value: { ...expenseReport, - lastMessageText: optimisticReportAction.message?.[0]?.text, - lastMessageHtml: optimisticReportAction.message?.[0]?.html, + lastMessageText: ReportActionsUtils.getReportActionText(optimisticReportAction), + lastMessageHtml: ReportActionsUtils.getReportActionHtml(optimisticReportAction), stateNum, statusNum, }, diff --git a/src/libs/actions/Policy/Member.ts b/src/libs/actions/Policy/Member.ts index 0da122a16bae..7c87dd597d51 100644 --- a/src/libs/actions/Policy/Member.ts +++ b/src/libs/actions/Policy/Member.ts @@ -14,12 +14,13 @@ import * as ErrorUtils from '@libs/ErrorUtils'; import Log from '@libs/Log'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PhoneNumber from '@libs/PhoneNumber'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {InvitedEmailsToAccountIDs, PersonalDetailsList, Policy, PolicyEmployee, PolicyOwnershipChangeChecks, Report, ReportAction} from '@src/types/onyx'; import type {PendingAction} from '@src/types/onyx/OnyxCommon'; -import type {OriginalMessageJoinPolicyChangeLog} from '@src/types/onyx/OriginalMessage'; +import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage'; import type {Attributes, Rate} from '@src/types/onyx/Policy'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -710,7 +711,7 @@ function acceptJoinRequest(reportID: string, reportAction: OnyxEntry = { @@ -1026,7 +1026,7 @@ function navigateToAndOpenChildReport(childReportID = '-1', parentReportAction: const childReportChatType = parentReport && ReportUtils.isSelfDM(parentReport) ? undefined : parentReport?.chatType; const newChat = ReportUtils.buildOptimisticChatReport( participantAccountIDs, - parentReportAction?.message?.[0]?.text, + ReportActionsUtils.getReportActionText(parentReportAction), childReportChatType, parentReport?.policyID ?? CONST.POLICY.OWNER_EMAIL_FAKE, CONST.POLICY.OWNER_ACCOUNT_ID_FAKE, @@ -1479,7 +1479,7 @@ function editReportComment(reportID: string, originalReportAction: OnyxEntry = { [reportActionID]: { pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, @@ -1702,7 +1702,7 @@ function toggleSubscribeToChildReport(childReportID = '-1', parentReportAction: const parentReport = allReports?.[parentReportID]; const newChat = ReportUtils.buildOptimisticChatReport( participantAccountIDs, - parentReportAction?.message?.[0]?.text, + ReportActionsUtils.getReportActionText(parentReportAction), parentReport?.chatType, parentReport?.policyID ?? CONST.POLICY.OWNER_EMAIL_FAKE, CONST.POLICY.OWNER_ACCOUNT_ID_FAKE, @@ -2122,8 +2122,8 @@ function deleteReport(reportID: string) { const reportActionsForReport = allReportActions?.[reportID]; const transactionIDs = Object.values(reportActionsForReport ?? {}) - .filter((reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) - .map((reportAction) => reportAction.originalMessage.IOUTransactionID); + .filter((reportAction): reportAction is ReportAction => ReportActionsUtils.isMoneyRequestAction(reportAction)) + .map((reportAction) => ReportActionsUtils.getOriginalMessage(reportAction)?.IOUTransactionID); [...new Set(transactionIDs)].forEach((transactionID) => { onyxData[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] = null; @@ -2943,9 +2943,9 @@ function openLastOpenedPublicRoom(lastOpenedPublicRoomID: string) { /** Flag a comment as offensive */ function flagComment(reportID: string, reportAction: OnyxEntry, severity: string) { const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction); - const message = reportAction?.message?.[0]; + const message = ReportActionsUtils.getReportActionMessage(reportAction); - if (!message) { + if (!message || !reportAction) { return; } @@ -3583,8 +3583,8 @@ function clearNewRoomFormError() { } function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEntry, resolution: ValueOf) { - const message = reportAction?.message?.[0]; - if (!message) { + const message = ReportActionsUtils.getReportActionMessage(reportAction); + if (!message || !reportAction) { return; } @@ -3677,8 +3677,9 @@ function resolveActionableReportMentionWhisper( } function dismissTrackExpenseActionableWhisper(reportID: string, reportAction: OnyxEntry): void { - const message = reportAction?.message?.[0]; - if (!message) { + const isArrayMessage = Array.isArray(reportAction?.message); + const message = ReportActionsUtils.getReportActionMessage(reportAction); + if (!message || !reportAction) { return; } @@ -3693,7 +3694,7 @@ function dismissTrackExpenseActionableWhisper(reportID: string, reportAction: On key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, value: { [reportAction.reportActionID]: { - message: [updatedMessage], + message: isArrayMessage ? [updatedMessage] : updatedMessage, originalMessage: { resolution: CONST.REPORT.ACTIONABLE_TRACK_EXPENSE_WHISPER_RESOLUTION.NOTHING, }, diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 4435978075e0..395c99fc4b26 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -44,7 +44,7 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction, k } // Delete the failed task report too - const taskReportID = reportAction.message?.[0]?.taskReportID; + const taskReportID = ReportActionUtils.getReportActionMessage(reportAction)?.taskReportID; if (taskReportID && ReportActionUtils.isCreatedTaskReportAction(reportAction)) { Report.deleteReport(taskReportID); } diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index d3d2deb3b1f1..8dfbc152a3ea 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -132,7 +132,7 @@ function createTaskAndNavigate( optimisticTaskReport.parentReportActionID = optimisticAddCommentReport.reportAction.reportActionID; const currentTime = DateUtils.getDBTimeWithSkew(); - const lastCommentText = ReportUtils.formatReportLastMessageText(optimisticAddCommentReport?.reportAction?.message?.[0]?.text ?? ''); + const lastCommentText = ReportUtils.formatReportLastMessageText(ReportActionsUtils.getReportActionText(optimisticAddCommentReport.reportAction)); const optimisticParentReport = { lastVisibleActionCreated: optimisticAddCommentReport.reportAction.created, lastMessageText: lastCommentText, diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index eb3d56158b0c..83a73033d204 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -43,7 +43,6 @@ import type {OnyxServerUpdate} from '@src/types/onyx/OnyxUpdatesFromServer'; import type OnyxPersonalDetails from '@src/types/onyx/PersonalDetails'; import type {Status} from '@src/types/onyx/PersonalDetails'; import type ReportAction from '@src/types/onyx/ReportAction'; -import type {OriginalMessage} from '@src/types/onyx/ReportAction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import applyOnyxUpdatesReliably from './applyOnyxUpdatesReliably'; @@ -532,9 +531,13 @@ function playSoundForMessageType(pushJSON: OnyxServerUpdate[]) { } } - const types = flatten.map((data) => data?.originalMessage).filter(Boolean) as OriginalMessage[]; + const types = flatten.map((data) => ReportActionsUtils.getOriginalMessage(data)).filter(Boolean); for (const message of types) { + if (!message) { + return; + } + // Pay someone flow if ('IOUDetails' in message) { return playSound(SOUNDS.SUCCESS); diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx index f1e541d2fa91..136d8e5b59eb 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx @@ -239,6 +239,7 @@ function BaseReportActionContextMenu({ {filteredContextMenuActions.map((contextAction, index) => { const closePopup = !isMini; const payload: ContextMenuActionPayload = { + // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style reportAction: (reportAction ?? null) as ReportAction, reportID, draftMessage, diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index 370093e122a2..bf634b4ac8ae 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -35,7 +35,7 @@ import {hideContextMenu, showDeleteModal} from './ReportActionContextMenu'; /** Gets the HTML version of the message in an action */ function getActionHtml(reportAction: OnyxInputOrEntry): string { - const message = reportAction?.message?.at(-1) ?? null; + const message = Array.isArray(reportAction?.message) ? reportAction?.message?.at(-1) ?? null : reportAction?.message ?? null; return message?.html ?? ''; } @@ -377,7 +377,7 @@ const ContextMenuActions: ContextMenuAction[] = [ const modifyExpenseMessage = ModifiedExpenseMessage.getForReportAction(reportID, reportAction); Clipboard.setString(modifyExpenseMessage); } else if (ReportActionsUtils.isReimbursementDeQueuedAction(reportAction)) { - const {expenseReportID} = reportAction.originalMessage; + const {expenseReportID} = ReportActionsUtils.getOriginalMessage(reportAction) ?? {}; const displayMessage = ReportUtils.getReimbursementDeQueuedActionMessage(reportAction, expenseReportID); Clipboard.setString(displayMessage); } else if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index 6b265456c8ea..5cfce625201e 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -265,10 +265,11 @@ function PopoverReportActionContextMenu(_props: unknown, ref: ForwardedRef (onComfirmDeleteModal.current = runAndResetCallback(onComfirmDeleteModal.current)); const reportAction = reportActionRef.current; if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { + const originalMessage = ReportActionsUtils.getOriginalMessage(reportAction); if (ReportActionsUtils.isTrackExpenseAction(reportAction)) { - IOU.deleteTrackExpense(reportIDRef.current, reportAction?.originalMessage?.IOUTransactionID ?? '-1', reportAction); + IOU.deleteTrackExpense(reportIDRef.current, originalMessage?.IOUTransactionID ?? '-1', reportAction); } else { - IOU.deleteMoneyRequest(reportAction?.originalMessage?.IOUTransactionID ?? '-1', reportAction); + IOU.deleteMoneyRequest(originalMessage?.IOUTransactionID ?? '-1', reportAction); } } else if (reportAction) { Report.deleteReportComment(reportIDRef.current, reportAction); diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 46477964d17b..699df4c76f8b 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -542,7 +542,8 @@ function ComposerWithSuggestions( ) { event.preventDefault(); if (lastReportAction) { - Report.saveReportActionDraft(reportID, lastReportAction, parseHtmlToMarkdown(lastReportAction.message?.at(-1)?.html ?? '')); + const message = Array.isArray(lastReportAction?.message) ? lastReportAction?.message?.at(-1) ?? null : lastReportAction?.message ?? null; + Report.saveReportActionDraft(reportID, lastReportAction, parseHtmlToMarkdown(message?.html ?? '')); } } }, diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index b9dda0b5abd9..53470367e88f 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -154,9 +154,6 @@ type ReportActionItemProps = { shouldUseThreadDividerLine?: boolean; } & ReportActionItemOnyxProps; -const isIOUReport = (actionObj: OnyxEntry): actionObj is OnyxTypes.ReportActionBase & OnyxTypes.OriginalMessageIOU => - actionObj?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU; - function ReportActionItem({ modal, action, @@ -207,6 +204,7 @@ function ReportActionItem({ const reportScrollManager = useReportScrollManager(); const isActionableWhisper = ReportActionsUtils.isActionableMentionWhisper(action) || ReportActionsUtils.isActionableTrackExpense(action) || ReportActionsUtils.isActionableReportMentionWhisper(action); + const originalMessage = ReportActionsUtils.getOriginalMessage(action); const highlightedBackgroundColorIfNeeded = useMemo( () => (isReportActionLinked ? StyleUtils.getBackgroundColorStyle(theme.messageHighlightBG) : {}), @@ -214,15 +212,19 @@ function ReportActionItem({ ); const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(action); - const prevActionResolution = usePrevious(isActionableWhisper ? action.originalMessage.resolution : null); + const prevActionResolution = usePrevious(isActionableWhisper && originalMessage && 'resolution' in originalMessage ? originalMessage?.resolution : null); // IOUDetails only exists when we are sending money - const isSendingMoney = isIOUReport(action) && action.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && action.originalMessage.IOUDetails; + const isSendingMoney = + ReportActionsUtils.isMoneyRequestAction(action) && + ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && + ReportActionsUtils.getOriginalMessage(action)?.IOUDetails; const updateHiddenState = useCallback( (isHiddenValue: boolean) => { setIsHidden(isHiddenValue); - const isAttachment = ReportUtils.isReportMessageAttachment(action.message?.at(-1)); + const message = Array.isArray(action.message) ? action.message?.at(-1) : action.message; + const isAttachment = ReportUtils.isReportMessageAttachment(message); if (!isAttachment) { return; } @@ -289,7 +291,7 @@ function ReportActionItem({ // Hide the message if it is being moderated for a higher offense, or is hidden by a moderator // Removed messages should not be shown anyway and should not need this flow - const latestDecision = action.message?.[0]?.moderationDecision?.decision ?? ''; + const latestDecision = ReportActionsUtils.getReportActionMessage(action)?.moderationDecision?.decision ?? ''; useEffect(() => { if (action.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT) { return; @@ -361,10 +363,10 @@ function ReportActionItem({ return; } - if (prevActionResolution !== action.originalMessage.resolution) { + if (prevActionResolution !== (originalMessage && 'resolution' in originalMessage ? originalMessage.resolution : null)) { reportScrollManager.scrollToIndex(index); } - }, [index, action, prevActionResolution, reportScrollManager, isActionableWhisper]); + }, [index, originalMessage, prevActionResolution, reportScrollManager, isActionableWhisper]); const toggleReaction = useCallback( (emoji: Emoji) => { @@ -387,18 +389,18 @@ function ReportActionItem({ const attachmentContextValue = useMemo(() => ({reportID: report.reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [report.reportID]); const actionableItemButtons: ActionableItem[] = useMemo(() => { - if (!isActionableWhisper && (!ReportActionsUtils.isActionableJoinRequest(action) || action.originalMessage.choice !== ('' as JoinWorkspaceResolution))) { + if (!isActionableWhisper && (!ReportActionsUtils.isActionableJoinRequest(action) || ReportActionsUtils.getOriginalMessage(action)?.choice !== ('' as JoinWorkspaceResolution))) { return []; } if (ReportActionsUtils.isActionableTrackExpense(action)) { - const transactionID = action?.originalMessage?.transactionID; + const transactionID = ReportActionsUtils.getOriginalMessage(action)?.transactionID; return [ { text: 'actionableMentionTrackExpense.submit', key: `${action.reportActionID}-actionableMentionTrackExpense-submit`, onPress: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID, report.reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID); + ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', report.reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID); }, isMediumSized: true, }, @@ -406,7 +408,7 @@ function ReportActionItem({ text: 'actionableMentionTrackExpense.categorize', key: `${action.reportActionID}-actionableMentionTrackExpense-categorize`, onPress: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID, report.reportID, CONST.IOU.ACTION.CATEGORIZE, action.reportActionID); + ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', report.reportID, CONST.IOU.ACTION.CATEGORIZE, action.reportActionID); }, isMediumSized: true, }, @@ -414,7 +416,7 @@ function ReportActionItem({ text: 'actionableMentionTrackExpense.share', key: `${action.reportActionID}-actionableMentionTrackExpense-share`, onPress: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID, report.reportID, CONST.IOU.ACTION.SHARE, action.reportActionID); + ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', report.reportID, CONST.IOU.ACTION.SHARE, action.reportActionID); }, isMediumSized: true, }, @@ -488,20 +490,20 @@ function ReportActionItem({ // Show the MoneyRequestPreview for when expense is present if ( - isIOUReport(action) && - action.originalMessage && + ReportActionsUtils.isMoneyRequestAction(action) && + ReportActionsUtils.getOriginalMessage(action) && // For the pay flow, we only want to show MoneyRequestAction when sending money. When paying, we display a regular system message - (action.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || - action.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT || - action.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK || + (ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || + ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT || + ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK || isSendingMoney) ) { // There is no single iouReport for bill splits, so only 1:1 requests require an iouReportID - const iouReportID = action.originalMessage.IOUReportID ? action.originalMessage.IOUReportID.toString() : '-1'; + const iouReportID = ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ? ReportActionsUtils.getOriginalMessage(action)?.IOUReportID?.toString() ?? '-1' : '-1'; children = ( ); - } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.TRIPPREVIEW) { + } else if (ReportActionsUtils.isTripPreview(action)) { children = ( ); - } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_QUEUED) { + } else if (ReportActionsUtils.isReimbursementQueuedAction(action)) { const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails[linkedReport?.ownerAccountID ?? -1]); - const paymentType = action.originalMessage.paymentType ?? ''; + const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID ?? '-1', action); children = ( @@ -601,7 +603,7 @@ function ReportActionItem({ ); - } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_DEQUEUED) { + } else if (ReportActionsUtils.isReimbursementDeQueuedAction(action)) { children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE) { children = ; @@ -611,13 +613,13 @@ function ReportActionItem({ } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.HOLD) { children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.HOLD_COMMENT) { - children = ; + children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.UNHOLD) { children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.MERGED_WITH_CASH_TRANSACTION) { children = ; - } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.DISMISSED_VIOLATION) { - children = ; + } else if (ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.DISMISSED_VIOLATION)) { + children = ; } else { const hasBeenFlagged = ![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === moderationDecision) && @@ -782,7 +784,9 @@ function ReportActionItem({ } if (action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) { - const transactionID = (parentReportActionForTransactionThread as OnyxTypes.OriginalMessageIOU)?.originalMessage.IOUTransactionID; + const transactionID = ReportActionsUtils.isMoneyRequestAction(parentReportActionForTransactionThread) + ? ReportActionsUtils.getOriginalMessage(parentReportActionForTransactionThread)?.IOUTransactionID + : '-1'; return ( ; } - if (action.actionName === CONST.REPORT.ACTIONS.TYPE.CHRONOS_OOO_LIST) { + if (ReportActionsUtils.isChronosOOOListAction(action)) { return ( 1; - const iouReportID = isIOUReport(action) && action.originalMessage.IOUReportID ? action.originalMessage.IOUReportID.toString() : '-1'; + const iouReportID = + ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUReportID + ? (ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ?? '').toString() + : '-1'; const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID); const isWhisper = whisperedTo.length > 0 && transactionsWithReceipts.length === 0; const whisperedToPersonalDetails = isWhisper @@ -942,7 +954,8 @@ export default withOnyx({ key: ONYXKEYS.USER_WALLET, }, linkedTransactionRouteError: { - key: ({action}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${(action as OnyxTypes.OriginalMessageIOU)?.originalMessage?.IOUTransactionID ?? -1}`, + key: ({action}) => + `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`, selector: (transaction: OnyxEntry) => transaction?.errorFields?.route ?? null, }, modal: { diff --git a/src/pages/home/report/ReportActionItemFragment.tsx b/src/pages/home/report/ReportActionItemFragment.tsx index 8f19d452f150..27227251b0b6 100644 --- a/src/pages/home/report/ReportActionItemFragment.tsx +++ b/src/pages/home/report/ReportActionItemFragment.tsx @@ -10,8 +10,9 @@ import convertToLTR from '@libs/convertToLTR'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import type * as OnyxCommon from '@src/types/onyx/OnyxCommon'; -import type {ActionName, DecisionName, OriginalMessageSource} from '@src/types/onyx/OriginalMessage'; +import type {DecisionName, OriginalMessageSource} from '@src/types/onyx/OriginalMessage'; import type {Message} from '@src/types/onyx/ReportAction'; +import type ReportActionName from '@src/types/onyx/ReportActionName'; import AttachmentCommentFragment from './comment/AttachmentCommentFragment'; import TextCommentFragment from './comment/TextCommentFragment'; @@ -59,7 +60,7 @@ type ReportActionItemFragmentProps = { pendingAction?: OnyxCommon.PendingAction; /** The report action name */ - actionName?: ActionName; + actionName?: ReportActionName; moderationDecision?: DecisionName; }; @@ -70,7 +71,7 @@ const MUTED_ACTIONS = [ CONST.REPORT.ACTIONS.TYPE.APPROVED, CONST.REPORT.ACTIONS.TYPE.MOVED, CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_JOIN_REQUEST, -] as ActionName[]; +] as ReportActionName[]; function ReportActionItemFragment({ pendingAction, diff --git a/src/pages/home/report/ReportActionItemMessage.tsx b/src/pages/home/report/ReportActionItemMessage.tsx index 58aa1f9acc41..d97a7218bf7f 100644 --- a/src/pages/home/report/ReportActionItemMessage.tsx +++ b/src/pages/home/report/ReportActionItemMessage.tsx @@ -12,7 +12,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ReportAction, Transaction} from '@src/types/onyx'; -import type {OriginalMessageAddComment} from '@src/types/onyx/OriginalMessage'; +import type {Message} from '@src/types/onyx/ReportAction'; import TextCommentFragment from './comment/TextCommentFragment'; import ReportActionItemFragment from './ReportActionItemFragment'; @@ -42,7 +42,13 @@ function ReportActionItemMessage({action, transaction, displayAsGroup, reportID, const styles = useThemeStyles(); const {translate} = useLocalize(); - const fragments = (action.previousMessage ?? action.message ?? []).filter((item) => !!item); + const actionMessage = action.previousMessage ?? action.message; + let fragments: Message[] = []; + if (Array.isArray(actionMessage)) { + fragments = actionMessage.filter((item): item is Message => !!item); + } else { + fragments = actionMessage ? [actionMessage] : []; + } const isIOUReport = ReportActionsUtils.isMoneyRequestAction(action); if (ReportActionsUtils.isMemberChangeAction(action)) { @@ -63,7 +69,7 @@ function ReportActionItemMessage({action, transaction, displayAsGroup, reportID, let iouMessage: string | undefined; if (isIOUReport) { - const originalMessage = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? action.originalMessage : null; + const originalMessage = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? ReportActionsUtils.getOriginalMessage(action) : null; const iouReportID = originalMessage?.IOUReportID; if (iouReportID) { iouMessage = ReportUtils.getIOUReportActionDisplayMessage(action, transaction); @@ -89,7 +95,7 @@ function ReportActionItemMessage({action, transaction, displayAsGroup, reportID, isThreadParentMessage={ReportActionsUtils.isThreadParentMessage(action, reportID)} pendingAction={action.pendingAction} actionName={action.actionName} - source={(action.originalMessage as OriginalMessageAddComment['originalMessage'])?.source} + source={ReportActionsUtils.isAddCommentAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.source : ''} accountID={action.actorAccountID ?? -1} style={style} displayAsGroup={displayAsGroup} @@ -100,7 +106,7 @@ function ReportActionItemMessage({action, transaction, displayAsGroup, reportID, // to decide if the fragment should be from left to right for RTL display names e.g. Arabic for proper // formatting. isFragmentContainingDisplayName={index === 0} - moderationDecision={action.message?.[0]?.moderationDecision?.decision} + moderationDecision={ReportActionsUtils.getReportActionMessage(action)?.moderationDecision?.decision} /> )); diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 49f889c7aa92..ff6922b13a9d 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -110,7 +110,7 @@ function ReportActionItemMessageEdit( useEffect(() => { draftMessageVideoAttributeCache.clear(); - const originalMessage = parseHtmlToMarkdown(action.message?.[0]?.html ?? '', undefined, undefined, (videoSource, attrs) => { + const originalMessage = parseHtmlToMarkdown(ReportActionsUtils.getReportActionHtml(action), undefined, undefined, (videoSource, attrs) => { draftMessageVideoAttributeCache.set(videoSource, attrs); }); if (ReportActionsUtils.isDeletedAction(action) || !!(action.message && draftMessage === originalMessage) || !!(prevDraftMessage === draftMessage || isCommentPendingSaved.current)) { diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx index a370cdd3fca0..ac2228bf272a 100644 --- a/src/pages/home/report/ReportActionItemSingle.tsx +++ b/src/pages/home/report/ReportActionItemSingle.tsx @@ -19,6 +19,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import ControlSelection from '@libs/ControlSelection'; import DateUtils from '@libs/DateUtils'; import Navigation from '@libs/Navigation/Navigation'; +import {getReportActionMessage} from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; @@ -254,7 +255,7 @@ function ReportActionItemSingle({ delegateAccountID={action?.delegateAccountID} isSingleLine actorIcon={icon} - moderationDecision={action?.message?.[0]?.moderationDecision?.decision} + moderationDecision={getReportActionMessage(action)?.moderationDecision?.decision} /> ))} diff --git a/src/pages/home/report/ReportActionsListItemRenderer.tsx b/src/pages/home/report/ReportActionsListItemRenderer.tsx index 18118e48f7cb..c095f3fa0cb0 100644 --- a/src/pages/home/report/ReportActionsListItemRenderer.tsx +++ b/src/pages/home/report/ReportActionsListItemRenderer.tsx @@ -86,7 +86,7 @@ function ReportActionsListItemRenderer({ pendingAction: reportAction.pendingAction, actionName: reportAction.actionName, errors: reportAction.errors, - originalMessage: reportAction.originalMessage, + originalMessage: reportAction?.originalMessage, childCommenterCount: reportAction.childCommenterCount, linkMetadata: reportAction.linkMetadata, childReportID: reportAction.childReportID, @@ -115,7 +115,7 @@ function ReportActionsListItemRenderer({ reportAction.pendingAction, reportAction.actionName, reportAction.errors, - reportAction.originalMessage, + reportAction?.originalMessage, reportAction.childCommenterCount, reportAction.linkMetadata, reportAction.childReportID, diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index b98e878743be..976b7ed7c96f 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -480,14 +480,16 @@ function ReportActionsView({ } const reportPreviewAction = ReportActionsUtils.getReportPreviewAction(report.chatReportID ?? '-1', report.reportID); - const moneyRequestActions = reportActions.filter( - (action) => - action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && - action.originalMessage && - (action.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || - !!(action.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && action.originalMessage.IOUDetails) || - action.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK), - ); + const moneyRequestActions = reportActions.filter((action) => { + const originalMessage = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action) : undefined; + return ( + ReportActionsUtils.isMoneyRequestAction(action) && + originalMessage && + (originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || + !!(originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && originalMessage?.IOUDetails) || + originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK) + ); + }); if (report.total && moneyRequestActions.length < (reportPreviewAction?.childMoneyRequestCount ?? 0) && isEmptyObject(transactionThreadReport)) { const optimisticIOUAction = ReportUtils.buildOptimisticIOUReportAction( diff --git a/src/pages/iou/HoldReasonPage.tsx b/src/pages/iou/HoldReasonPage.tsx index 0070f5e5be0a..1cfc65d987d0 100644 --- a/src/pages/iou/HoldReasonPage.tsx +++ b/src/pages/iou/HoldReasonPage.tsx @@ -61,7 +61,7 @@ function HoldReasonPage({route}: HoldReasonPageProps) { // We have extra isWorkspaceRequest condition since, for 1:1 requests, canEditMoneyRequest will rightly return false // as we do not allow requestee to edit fields like description and amount. // But, we still want the requestee to be able to put the request on hold - if (!ReportUtils.canEditMoneyRequest(parentReportAction) && isWorkspaceRequest) { + if (ReportActionsUtils.isMoneyRequestAction(parentReportAction) && !ReportUtils.canEditMoneyRequest(parentReportAction) && isWorkspaceRequest) { return; } @@ -79,7 +79,7 @@ function HoldReasonPage({route}: HoldReasonPageProps) { // We have extra isWorkspaceRequest condition since, for 1:1 requests, canEditMoneyRequest will rightly return false // as we do not allow requestee to edit fields like description and amount. // But, we still want the requestee to be able to put the request on hold - if (!ReportUtils.canEditMoneyRequest(parentReportAction) && isWorkspaceRequest) { + if (ReportActionsUtils.isMoneyRequestAction(parentReportAction) && !ReportUtils.canEditMoneyRequest(parentReportAction) && isWorkspaceRequest) { const formErrors = {}; ErrorUtils.addErrorMessage(formErrors, 'reportModified', translate('common.error.requestModified')); FormActions.setErrors(ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM, formErrors); diff --git a/src/pages/iou/SplitBillDetailsPage.tsx b/src/pages/iou/SplitBillDetailsPage.tsx index 93f98a2993b1..5ae5e2dfb438 100644 --- a/src/pages/iou/SplitBillDetailsPage.tsx +++ b/src/pages/iou/SplitBillDetailsPage.tsx @@ -15,6 +15,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import type {SplitDetailsNavigatorParamList} from '@libs/Navigation/types'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import withReportAndReportActionOrNotFound from '@pages/home/report/withReportAndReportActionOrNotFound'; @@ -63,7 +64,7 @@ function SplitBillDetailsPage({personalDetails, report, route, reportActions, tr const {translate} = useLocalize(); const theme = useTheme(); const reportAction = useMemo(() => reportActions?.[route.params.reportActionID] ?? ({} as ReportAction), [reportActions, route.params.reportActionID]); - const participantAccountIDs = reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? reportAction?.originalMessage.participantAccountIDs ?? [] : []; + const participantAccountIDs = ReportActionsUtils.isMoneyRequestAction(reportAction) ? ReportActionsUtils.getOriginalMessage(reportAction)?.participantAccountIDs ?? [] : []; // In case this is workspace split expense, we manually add the workspace as the second participant of the split expense // because we don't save any accountID in the report action's originalMessage other than the payee's accountID @@ -158,16 +159,16 @@ const WrappedComponent = withOnyx { const reportAction = reportActions?.[route.params.reportActionID]; - const IOUTransactionID = - reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction?.originalMessage?.IOUTransactionID ? reportAction.originalMessage.IOUTransactionID : 0; + const originalMessage = reportAction && ReportActionsUtils.isMoneyRequestAction(reportAction) ? ReportActionsUtils.getOriginalMessage(reportAction) : undefined; + const IOUTransactionID = reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && originalMessage?.IOUTransactionID ? originalMessage.IOUTransactionID : 0; return `${ONYXKEYS.COLLECTION.TRANSACTION}${IOUTransactionID}`; }, }, draftTransaction: { key: ({route, reportActions}) => { const reportAction = reportActions?.[route.params.reportActionID]; - const IOUTransactionID = - reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction?.originalMessage?.IOUTransactionID ? reportAction.originalMessage.IOUTransactionID : 0; + const originalMessage = reportAction && ReportActionsUtils.isMoneyRequestAction(reportAction) ? ReportActionsUtils.getOriginalMessage(reportAction) : undefined; + const IOUTransactionID = reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && originalMessage?.IOUTransactionID ? originalMessage.IOUTransactionID : 0; return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${IOUTransactionID}`; }, }, diff --git a/src/pages/iou/request/step/IOURequestStepCategory.tsx b/src/pages/iou/request/step/IOURequestStepCategory.tsx index 18ddc19644ef..601fb4dc29da 100644 --- a/src/pages/iou/request/step/IOURequestStepCategory.tsx +++ b/src/pages/iou/request/step/IOURequestStepCategory.tsx @@ -16,6 +16,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import * as IOU from '@userActions/IOU'; @@ -99,7 +100,7 @@ function IOURequestStepCategory({ const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; const canEditSplitBill = isSplitBill && reportAction && session?.accountID === reportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction); // eslint-disable-next-line rulesdir/no-negated-variables - const shouldShowNotFoundPage = isEditing && (isSplitBill ? !canEditSplitBill : !ReportUtils.canEditMoneyRequest(reportAction)); + const shouldShowNotFoundPage = isEditing && (isSplitBill ? !canEditSplitBill : !ReportActionsUtils.isMoneyRequestAction(reportAction) || !ReportUtils.canEditMoneyRequest(reportAction)); const fetchData = () => { if (policy && policyCategories) { diff --git a/src/pages/iou/request/step/IOURequestStepDescription.tsx b/src/pages/iou/request/step/IOURequestStepDescription.tsx index 7ccce96bb06c..081f0b104355 100644 --- a/src/pages/iou/request/step/IOURequestStepDescription.tsx +++ b/src/pages/iou/request/step/IOURequestStepDescription.tsx @@ -14,6 +14,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import updateMultilineInputRange from '@libs/updateMultilineInputRange'; @@ -147,7 +148,7 @@ function IOURequestStepDescription({ const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; const canEditSplitBill = isSplitBill && reportAction && session?.accountID === reportAction.actorAccountID && TransactionUtils.areRequiredFieldsEmpty(transaction); // eslint-disable-next-line rulesdir/no-negated-variables - const shouldShowNotFoundPage = isEditing && (isSplitBill ? !canEditSplitBill : !ReportUtils.canEditMoneyRequest(reportAction)); + const shouldShowNotFoundPage = isEditing && (isSplitBill ? !canEditSplitBill : !ReportActionsUtils.isMoneyRequestAction(reportAction) || !ReportUtils.canEditMoneyRequest(reportAction)); return ( PolicyUtils.getTagLists(policyTags), [policyTags]); const shouldShowTag = ReportUtils.isReportInGroupPolicy(report) && (transactionTag || OptionsListUtils.hasEnabledTags(policyTagLists)); // eslint-disable-next-line rulesdir/no-negated-variables - const shouldShowNotFoundPage = !shouldShowTag || (isEditing && (isSplitBill ? !canEditSplitBill : reportAction && !canEditMoneyRequest(reportAction))); + const shouldShowNotFoundPage = + !shouldShowTag || (isEditing && (isSplitBill ? !canEditSplitBill : !ReportActionsUtils.isMoneyRequestAction(reportAction) || !ReportUtils.canEditMoneyRequest(reportAction))); const navigateBack = () => { Navigation.goBack(backTo); diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 9b07d6212c91..c700dca53f34 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -1,6 +1,8 @@ import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; +import type AssertTypesEqual from '@src/types/utils/AssertTypesEqual'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; +import type ReportActionName from './ReportActionName'; /** Types of join workspace resolutions */ type JoinWorkspaceResolution = ValueOf; @@ -8,83 +10,9 @@ type JoinWorkspaceResolution = ValueOf; -/** Names of report actions */ -type ActionName = DeepValueOf; - -/** Names of task report actions */ -type OriginalMessageActionName = - | 'ADDCOMMENT' - | 'APPROVED' - | 'CHRONOSOOOLIST' - | 'CLOSED' - | 'CREATED' - | 'HOLD' - | 'UNHOLD' - | 'IOU' - | 'MODIFIEDEXPENSE' - | 'REIMBURSEMENTQUEUED' - | 'RENAMED' - | 'REPORTPREVIEW' - | 'SUBMITTED' - | 'TASKCANCELLED' - | 'TASKCOMPLETED' - | 'TASKEDITED' - | 'TASKREOPENED' - | 'ACTIONABLEJOINREQUEST' - | 'ACTIONABLEMENTIONWHISPER' - | 'ACTIONABLEREPORTMENTIONWHISPER' - | 'ACTIONABLETRACKEXPENSEWHISPER' - | 'TRIPPREVIEW' - | ValueOf; - -/** Model of `approved` report action */ -type OriginalMessageApproved = { - /** Approved */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.APPROVED; - - /** Content of the original message */ - originalMessage: { - /** Approved expense report amount */ - amount: number; - - /** Currency of the approved expense report amount */ - currency: string; - - /** Report ID of the expense report */ - expenseReportID: string; - }; -}; - /** Types of sources of original message */ type OriginalMessageSource = 'Chronos' | 'email' | 'ios' | 'android' | 'web' | ''; -/** Model of `hold` report action */ -type OriginalMessageHold = { - /** Hold */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.HOLD; - - /** Content of the original message */ - originalMessage: unknown; -}; - -/** Model of `hold comment` report action */ -type OriginalMessageHoldComment = { - /** Hold comment */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.HOLD_COMMENT; - - /** Content of the original message */ - originalMessage: unknown; -}; - -/** Model of `unhold` report action */ -type OriginalMessageUnHold = { - /** Unhold */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.UNHOLD; - - /** Content of the original message */ - originalMessage: unknown; -}; - /** Details provided when sending money */ type IOUDetails = { /** How much was sent */ @@ -97,8 +25,8 @@ type IOUDetails = { currency: string; }; -/** Model of original message of `IOU` report action */ -type IOUMessage = { +/** Model of `IOU` report action */ +type OriginalMessageIOU = { /** The ID of the `IOU` transaction */ IOUTransactionID?: string; @@ -142,30 +70,6 @@ type IOUMessage = { whisperedTo?: number[]; }; -/** Model of original message of `reimbursed dequeued` report action */ -type ReimbursementDeQueuedMessage = { - /** Why the reimbursement was cancelled */ - cancellationReason: ValueOf; - - /** ID of the `expense` report */ - expenseReportID?: string; - - /** Amount that wasn't reimbursed */ - amount: number; - - /** Currency of the money that wasn't reimbursed */ - currency: string; -}; - -/** Model of `IOU` report action */ -type OriginalMessageIOU = { - /** IOU */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.IOU; - - /** Content of the original message */ - originalMessage: IOUMessage; -}; - /** Names of moderation decisions */ type DecisionName = ValueOf< Pick< @@ -201,145 +105,88 @@ type Reaction = { users: User[]; }; -/** Model of original message of `closed` report action */ -type Closed = { - /** Name of the policy */ - policyName: string; - - /** What was the reason to close the report */ - reason: ValueOf; - - /** When was the message last modified */ - lastModified?: string; - - /** If the report was closed because accounts got merged, then this is the new account ID */ - newAccountID?: number; - - /** If the report was closed because accounts got merged, then this is the old account ID */ - oldAccountID?: number; -}; - /** Model of `add comment` report action */ type OriginalMessageAddComment = { - /** Add comment */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT; + /** HTML content of the comment */ + html: string; - /** Content of the original message */ - originalMessage: { - /** HTML content of the comment */ - html: string; + /** Origin of the comment */ + source?: OriginalMessageSource; - /** Origin of the comment */ - source?: OriginalMessageSource; - - /** When was the comment last modified */ - lastModified?: string; + /** When was the comment last modified */ + lastModified?: string; - /** ID of the task report */ - taskReportID?: string; + /** ID of the task report */ + taskReportID?: string; - /** Collection of accountIDs of users mentioned in message */ - whisperedTo: number[]; - }; + /** Collection of accountIDs of users mentioned in message */ + whisperedTo: number[]; }; /** Model of `actionable mention whisper` report action */ type OriginalMessageActionableMentionWhisper = { - /** Actionable mention whisper */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER; + /** Account IDs of users that aren't members of the room */ + inviteeAccountIDs: number[]; - /** Content of the original message */ - originalMessage: { - /** Account IDs of users that aren't members of the room */ - inviteeAccountIDs: number[]; + /** Decision on whether to invite users that were mentioned but aren't members or do nothing */ + resolution?: ValueOf | null; - /** Decision on whether to invite users that were mentioned but aren't members or do nothing */ - resolution?: ValueOf | null; - - /** Collection of accountIDs of users mentioned in message */ - whisperedTo?: number[]; - }; + /** Collection of accountIDs of users mentioned in message */ + whisperedTo?: number[]; }; /** Model of `actionable report mention whisper` report action */ type OriginalMessageActionableReportMentionWhisper = { - /** Actionable report mention whisper */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_REPORT_MENTION_WHISPER; + /** Decision on whether to create a report that were mentioned but doesn't exist or do nothing */ + resolution?: ValueOf | null; - /** Content of the original message */ - originalMessage: { - /** Decision on whether to create a report that were mentioned but doesn't exist or do nothing */ - resolution?: ValueOf | null; - - /** Collection of accountIDs of users mentioned in message */ - whisperedTo?: number[]; - }; + /** Collection of accountIDs of users mentioned in message */ + whisperedTo?: number[]; }; /** Model of `submitted` report action */ type OriginalMessageSubmitted = { - /** Submitted */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.SUBMITTED; - - /** Content of the original message */ - originalMessage: { - /** Approved expense report amount */ - amount: number; + /** Approved expense amount */ + amount: number; - /** Currency of the approved expense report amount */ - currency: string; + /** Currency of the approved expense amount */ + currency: string; - /** Report ID of the expense report */ - expenseReportID: string; - }; + /** Report ID of the expense */ + expenseReportID: string; }; /** Model of `closed` report action */ type OriginalMessageClosed = { - /** Closed */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.CLOSED; - - /** Content of the original message */ - originalMessage: Closed; -}; + /** Name of the policy */ + policyName: string; -/** Model of `created` report action */ -type OriginalMessageCreated = { - /** Created */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.CREATED; + /** What was the reason to close the report */ + reason: ValueOf; - /** Content of the original message */ - originalMessage?: unknown; -}; + /** When was the message last modified */ + lastModified?: string; -/** Model of `marked reimbursed` report action */ -type OriginalMessageMarkedReimbursed = { - /** Marked reimbursed */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.MARKED_REIMBURSED; + /** If the report was closed because accounts got merged, then this is the new account ID */ + newAccountID?: number; - /** Content of the original message */ - originalMessage?: unknown; + /** If the report was closed because accounts got merged, then this is the old account ID */ + oldAccountID?: number; }; /** Model of `renamed` report action, created when chat rooms get renamed */ type OriginalMessageRenamed = { - /** Renamed */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.RENAMED; - - /** Content of the original message */ - originalMessage: { - /** Renamed room comment */ - html: string; + /** Renamed room comment */ + html: string; - /** When was report action last modified */ - lastModified: string; + /** When was report action last modified */ + lastModified: string; - /** Old room name */ - oldName: string; + /** Old room name */ + oldName: string; - /** New room name */ - newName: string; - }; + /** New room name */ + newName: string; }; /** Model of Chronos OOO Timestamp */ @@ -348,18 +195,6 @@ type ChronosOOOTimestamp = { date: string; }; -/** Model of change log */ -type ChangeLog = { - /** Account IDs of users that either got invited or removed from the room */ - targetAccountIDs?: number[]; - - /** Name of the chat room */ - roomName?: string; - - /** ID of the report */ - reportID?: number; -}; - /** Model of Chronos OOO Event */ type ChronosOOOEvent = { /** ID of the OOO event */ @@ -378,8 +213,44 @@ type ChronosOOOEvent = { end: ChronosOOOTimestamp; }; -/** Model of modified expense */ -type ModifiedExpense = { +/** Model of `Chronos OOO List` report action */ +type OriginalMessageChronosOOOList = { + /** Collection of OOO events to show in report action */ + events: ChronosOOOEvent[]; +}; + +/** Model of `report preview` report action */ +type OriginalMessageReportPreview = { + /** ID of the report to be previewed */ + linkedReportID: string; + + /** Collection of accountIDs of users mentioned in report */ + whisperedTo?: number[]; +}; + +/** Model of change log */ +type OriginalMessageChangeLog = { + /** Account IDs of users that either got invited or removed from the room */ + targetAccountIDs?: number[]; + + /** Name of the chat room */ + roomName?: string; + + /** ID of the report */ + reportID?: number; +}; + +/** Model of `join policy changelog` report action */ +type OriginalMessageJoinPolicyChangeLog = { + /** What was the invited user decision */ + choice: JoinWorkspaceResolution; + + /** ID of the affected policy */ + policyID: string; +}; + +/** Model of `modified expense` report action */ +type OriginalMessageModifiedExpense = { /** Old content of the comment */ oldComment?: string; @@ -442,253 +313,235 @@ type ModifiedExpense = { /** Collection of accountIDs of users mentioned in expense report */ whisperedTo?: number[]; -}; - -/** Model of `Chronos OOO List` report action */ -type OriginalMessageChronosOOOList = { - /** Chronos OOO list */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.CHRONOS_OOO_LIST; - - /** Content of the original message */ - originalMessage: { - /** Collection of OOO events to show in report action */ - events: ChronosOOOEvent[]; - }; -}; - -/** Model of `report preview` report action */ -type OriginalMessageReportPreview = { - /** Report preview */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW; - - /** Content of the original message */ - originalMessage: { - /** ID of the report to be previewed */ - linkedReportID: string; - - /** Collection of accountIDs of users mentioned in report */ - whisperedTo?: number[]; - }; -}; - -/** Model of `policy change log` report action */ -type OriginalMessagePolicyChangeLog = { - /** Policy change log */ - actionName: ValueOf; - - /** Content of the original message */ - originalMessage: ChangeLog; -}; - -/** Model of `join policy changelog` report action */ -type OriginalMessageJoinPolicyChangeLog = { - /** Actionable join request */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_JOIN_REQUEST; - - /** Content of the original message */ - originalMessage: { - /** What was the invited user decision */ - choice: JoinWorkspaceResolution; - - /** ID of the affected policy */ - policyID: string; - }; -}; - -/** Model of `room change log` report action */ -type OriginalMessageRoomChangeLog = { - /** Room change log */ - actionName: ValueOf; - /** Content of the original message */ - originalMessage: ChangeLog; -}; - -/** Model of `policy task` report action */ -type OriginalMessagePolicyTask = { - /** Policy task */ - actionName: - | typeof CONST.REPORT.ACTIONS.TYPE.TASK_EDITED - | typeof CONST.REPORT.ACTIONS.TYPE.TASK_CANCELLED - | typeof CONST.REPORT.ACTIONS.TYPE.TASK_COMPLETED - | typeof CONST.REPORT.ACTIONS.TYPE.TASK_REOPENED - | typeof CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE; - - /** Content of the original message */ - originalMessage: unknown; -}; - -/** Model of `modified expense` report action */ -type OriginalMessageModifiedExpense = { - /** Modified expense */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE; - - /** Content of the original message */ - originalMessage: ModifiedExpense; + /** The ID of moved report */ + movedToReportID?: string; }; /** Model of `reimbursement queued` report action */ type OriginalMessageReimbursementQueued = { - /** Reimbursement queued */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_QUEUED; - - /** Content of the original message */ - originalMessage: { - /** How is the payment getting reimbursed */ - paymentType: DeepValueOf; - }; + /** How is the payment getting reimbursed */ + paymentType: DeepValueOf; }; /** Model of `actionable tracked expense whisper` report action */ type OriginalMessageActionableTrackedExpenseWhisper = { - /** Actionable track expense whisper */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_TRACK_EXPENSE_WHISPER; - - /** Content of the original message */ - originalMessage: { - /** ID of the transaction */ - transactionID: string; + /** ID of the transaction */ + transactionID: string; - /** When was the tracked expense whisper last modified */ - lastModified: string; + /** When was the tracked expense whisper last modified */ + lastModified: string; - /** What was the decision of the user */ - resolution?: ValueOf; - }; + /** What was the decision of the user */ + resolution?: ValueOf; }; /** Model of `reimbursement dequeued` report action */ type OriginalMessageReimbursementDequeued = { - /** Reimbursement dequeued */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_DEQUEUED; + /** Why the reimbursement was cancelled */ + cancellationReason: ValueOf; - /** Content of the original message */ - originalMessage: ReimbursementDeQueuedMessage; + /** ID of the `expense` report */ + expenseReportID?: string; + + /** Amount that wasn't reimbursed */ + amount: number; + + /** Currency of the money that wasn't reimbursed */ + currency: string; }; /** Model of `moved` report action */ type OriginalMessageMoved = { - /** Moved */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.MOVED; + /** ID of the old policy */ + fromPolicyID: string; - /** Content of the original message */ - originalMessage: { - /** ID of the old policy */ - fromPolicyID: string; + /** ID of the new policy */ + toPolicyID: string; - /** ID of the new policy */ - toPolicyID: string; + /** ID of the new parent report */ + newParentReportID: string; - /** ID of the new parent report */ - newParentReportID: string; - - /** ID of the moved report */ - movedReportID: string; - }; -}; - -/** Model of `merged with cash transaction` report action */ -type OriginalMessageMergedWithCashTransaction = { - /** Merged with cash transaction */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.MERGED_WITH_CASH_TRANSACTION; - - /** Content of the original message */ - originalMessage: Record; // No data is sent with this action + /** ID of the moved report */ + movedReportID: string; }; /** Model of `dismissed violation` report action */ type OriginalMessageDismissedViolation = { - /** Dismissed violation */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.DISMISSED_VIOLATION; - - /** Content of the original message */ - originalMessage: { - /** Why the violation was dismissed */ - reason: string; + /** Why the violation was dismissed */ + reason: string; - /** Name of the violation */ - violationName: string; - }; + /** Name of the violation */ + violationName: string; }; /** Model of `trip room preview` report action */ type OriginalMessageTripRoomPreview = { - /** Trip Room Preview */ - actionName: typeof CONST.REPORT.ACTIONS.TYPE.TRIPPREVIEW; + /** ID of the report to be previewed */ + linkedReportID: string; - /** Content of the original message */ - originalMessage: { - /** ID of the report to be previewed */ - linkedReportID: string; - - /** When was report action last modified */ - lastModified?: string; + /** When was report action last modified */ + lastModified?: string; - /** Collection of accountIDs of users mentioned in report */ - whisperedTo?: number[]; - }; + /** Collection of accountIDs of users mentioned in report */ + whisperedTo?: number[]; }; -/** Model of report action */ -type OriginalMessage = - | OriginalMessageApproved - | OriginalMessageIOU - | OriginalMessageAddComment - | OriginalMessageActionableMentionWhisper - | OriginalMessageActionableReportMentionWhisper - | OriginalMessageSubmitted - | OriginalMessageClosed - | OriginalMessageCreated - | OriginalMessageHold - | OriginalMessageHoldComment - | OriginalMessageUnHold - | OriginalMessageRenamed - | OriginalMessageChronosOOOList - | OriginalMessageReportPreview - | OriginalMessageRoomChangeLog - | OriginalMessagePolicyChangeLog - | OriginalMessagePolicyTask - | OriginalMessageJoinPolicyChangeLog - | OriginalMessageModifiedExpense - | OriginalMessageReimbursementQueued - | OriginalMessageReimbursementDequeued - | OriginalMessageMoved - | OriginalMessageMarkedReimbursed - | OriginalMessageTripRoomPreview - | OriginalMessageActionableTrackedExpenseWhisper - | OriginalMessageMergedWithCashTransaction - | OriginalMessageDismissedViolation; +/** Model of `approved` report action */ +type OriginalMessageApproved = { + /** Approved expense amount */ + amount: number; + + /** Currency of the approved expense amount */ + currency: string; + + /** Report ID of the expense */ + expenseReportID: string; +}; + +/** The map type of original message */ +type OriginalMessageMap = { + /** */ + [CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_JOIN_REQUEST]: OriginalMessageJoinPolicyChangeLog; + /** */ + [CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER]: OriginalMessageActionableMentionWhisper; + /** */ + [CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_REPORT_MENTION_WHISPER]: OriginalMessageActionableReportMentionWhisper; + /** */ + [CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_TRACK_EXPENSE_WHISPER]: OriginalMessageActionableTrackedExpenseWhisper; + /** */ + [CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT]: OriginalMessageAddComment; + /** */ + [CONST.REPORT.ACTIONS.TYPE.APPROVED]: OriginalMessageApproved; + /** */ + [CONST.REPORT.ACTIONS.TYPE.CHANGE_FIELD]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.CHANGE_POLICY]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.CHANGE_TYPE]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.CHRONOS_OOO_LIST]: OriginalMessageChronosOOOList; + /** */ + [CONST.REPORT.ACTIONS.TYPE.CLOSED]: OriginalMessageClosed; + /** */ + [CONST.REPORT.ACTIONS.TYPE.CREATED]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.DELEGATE_SUBMIT]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.DELETED_ACCOUNT]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.DISMISSED_VIOLATION]: OriginalMessageDismissedViolation; + /** */ + [CONST.REPORT.ACTIONS.TYPE.DONATION]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_CSV]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_QUICK_BOOKS]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.FORWARDED]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.HOLD]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.HOLD_COMMENT]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.INTEGRATIONS_MESSAGE]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.IOU]: OriginalMessageIOU; + /** */ + [CONST.REPORT.ACTIONS.TYPE.MANAGER_ATTACH_RECEIPT]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.MANAGER_DETACH_RECEIPT]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.MARK_REIMBURSED_FROM_INTEGRATION]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.MARKED_REIMBURSED]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.MERGED_WITH_CASH_TRANSACTION]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE]: OriginalMessageModifiedExpense; + /** */ + [CONST.REPORT.ACTIONS.TYPE.MOVED]: OriginalMessageMoved; + /** */ + [CONST.REPORT.ACTIONS.TYPE.OUTDATED_BANK_ACCOUNT]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_ACH_BOUNCE]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_ACH_CANCELLED]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_ACCOUNT_CHANGED]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_DEQUEUED]: OriginalMessageReimbursementDequeued; + /** */ + [CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_DELAYED]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_QUEUED]: OriginalMessageReimbursementQueued; + /** */ + [CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_REQUESTED]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_SETUP]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.RENAMED]: OriginalMessageRenamed; + /** */ + [CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW]: OriginalMessageReportPreview; + /** */ + [CONST.REPORT.ACTIONS.TYPE.SELECTED_FOR_RANDOM_AUDIT]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.SHARE]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.STRIPE_PAID]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.SUBMITTED]: OriginalMessageSubmitted; + /** */ + [CONST.REPORT.ACTIONS.TYPE.TASK_CANCELLED]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.TASK_COMPLETED]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.TASK_EDITED]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.TASK_REOPENED]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.TAKE_CONTROL]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.UNAPPROVED]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.UNHOLD]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.UNSHARE]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.UPDATE_GROUP_CHAT_MEMBER_ROLE]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_SETUP_REQUESTED]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.TRIPPREVIEW]: OriginalMessageTripRoomPreview; +} & { + [T in ValueOf]: OriginalMessageChangeLog; +} & { + [T in ValueOf]: OriginalMessageChangeLog; +}; + +/** */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +type AssertOriginalMessageDefinedForAllActions = AssertTypesEqual< + ReportActionName, + keyof OriginalMessageMap, + `Error: Types don't match, OriginalMessageMap type is missing: ${Exclude}` +>; + +/** */ +type OriginalMessage = OriginalMessageMap[T]; export default OriginalMessage; export type { - JoinWorkspaceResolution, + DecisionName, + OriginalMessageIOU, ChronosOOOEvent, - Decision, + PaymentMethodType, + OriginalMessageSource, Reaction, - ActionName, - IOUMessage, - ReimbursementDeQueuedMessage, - Closed, - OriginalMessageActionName, - ChangeLog, - ModifiedExpense, - OriginalMessageApproved, - OriginalMessageSubmitted, - OriginalMessageIOU, - OriginalMessageCreated, - OriginalMessageRenamed, - OriginalMessageAddComment, - OriginalMessageJoinPolicyChangeLog, - OriginalMessageActionableMentionWhisper, - OriginalMessageActionableReportMentionWhisper, - OriginalMessageReportPreview, + Decision, + OriginalMessageChangeLog, + JoinWorkspaceResolution, OriginalMessageModifiedExpense, - OriginalMessageChronosOOOList, - OriginalMessageRoomChangeLog, - OriginalMessageSource, - OriginalMessageReimbursementDequeued, - DecisionName, - PaymentMethodType, - OriginalMessageActionableTrackedExpenseWhisper, - OriginalMessageDismissedViolation, }; diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index ee6fc76ba3cd..8c8386b239a5 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -1,4 +1,4 @@ -import type {ValueOf} from 'type-fest'; +import type {Spread, ValueOf} from 'type-fest'; import type {FileObject} from '@components/AttachmentModal'; import type {AvatarSource} from '@libs/UserUtils'; import type CONST from '@src/CONST'; @@ -6,17 +6,12 @@ import type ONYXKEYS from '@src/ONYXKEYS'; import type CollectionDataSet from '@src/types/utils/CollectionDataSet'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import type * as OnyxCommon from './OnyxCommon'; -import type {Decision, OriginalMessageModifiedExpense, OriginalMessageReportPreview} from './OriginalMessage'; import type OriginalMessage from './OriginalMessage'; +import type {Decision} from './OriginalMessage'; import type {NotificationPreference} from './Report'; +import type ReportActionName from './ReportActionName'; import type {Receipt} from './Transaction'; -/** Partial content of report action message */ -type ReportActionMessageJSON = { - /** Collection of accountIDs from users that were mentioned in report */ - whisperedTo?: number[]; -}; - /** Model of report action message */ type Message = { /** The type of the action item fragment. Used to render a corresponding component */ @@ -147,6 +142,9 @@ type ReportActionBase = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** The ID of the previous reportAction on the report. It is a string represenation of a 64-bit integer (or null for CREATED actions). */ previousReportActionID?: string; + /** The name (or type) of the action */ + actionName: ReportActionName; + /** Account ID of the actor that created the action */ actorAccountID?: number; @@ -159,12 +157,6 @@ type ReportActionBase = OnyxCommon.OnyxValueWithOfflineFeedback<{ /** ISO-formatted datetime */ created: string; - /** report action message */ - message?: Array; - - /** report action message */ - previousMessage?: Array; - /** Whether we have received a response back from the server */ isLoading?: boolean; @@ -271,14 +263,22 @@ type ReportActionBase = OnyxCommon.OnyxValueWithOfflineFeedback<{ whisperedToAccountIDs?: number[]; }>; -/** Model of report action */ -type ReportAction = ReportActionBase & OriginalMessage; +/** + * + */ +type ReportAction = ReportActionBase & { + /** @deprecated Used in old report actions before migration. Replaced by using getOriginalMessage function. */ + originalMessage?: OriginalMessage; + + /** report action message */ + message?: (OriginalMessage & Message) | Array; -/** Model of report preview action */ -type ReportPreviewAction = ReportActionBase & OriginalMessageReportPreview; + /** report action message */ + previousMessage?: (OriginalMessage & Message) | Array; +}; -/** Model of modifies expense action */ -type ModifiedExpenseAction = ReportActionBase & OriginalMessageModifiedExpense; +/** */ +type ReportActionChangeLog = ReportAction>>; /** Record of report actions, indexed by report action ID */ type ReportActions = Record; @@ -287,4 +287,4 @@ type ReportActions = Record; type ReportActionsCollectionDataSet = CollectionDataSet; export default ReportAction; -export type {ReportActions, ReportActionBase, Message, LinkMetadata, OriginalMessage, ReportActionsCollectionDataSet, ReportPreviewAction, ModifiedExpenseAction, ReportActionMessageJSON}; +export type {ReportActions, Message, LinkMetadata, OriginalMessage, ReportActionsCollectionDataSet, ReportActionChangeLog}; diff --git a/src/types/onyx/ReportActionName.ts b/src/types/onyx/ReportActionName.ts new file mode 100644 index 000000000000..b5a16c88242a --- /dev/null +++ b/src/types/onyx/ReportActionName.ts @@ -0,0 +1,7 @@ +import type CONST from '@src/CONST'; +import type DeepValueOf from '@src/types/utils/DeepValueOf'; + +/** The name (or type) of a reportAction */ +type ReportActionName = DeepValueOf; + +export default ReportActionName; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 47e0f34045ec..e50e57c8e156 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -55,7 +55,7 @@ import type RecentlyUsedTags from './RecentlyUsedTags'; import type RecentWaypoint from './RecentWaypoint'; import type ReimbursementAccount from './ReimbursementAccount'; import type Report from './Report'; -import type {ReportActionBase, ReportActions} from './ReportAction'; +import type {ReportActions} from './ReportAction'; import type ReportAction from './ReportAction'; import type ReportActionReactions from './ReportActionReactions'; import type ReportActionsDraft from './ReportActionsDraft'; @@ -178,7 +178,6 @@ export type { RecentlyUsedReportFields, DecisionName, OriginalMessageIOU, - ReportActionBase, LastPaymentMethod, LastSelectedDistanceRates, InvitedEmailsToAccountIDs, diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 4167d247a953..fbd31012b023 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -18,9 +18,7 @@ import * as ReportUtils from '@src/libs/ReportUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; -import type {IOUMessage, OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import type {Participant} from '@src/types/onyx/Report'; -import type {ReportActionBase} from '@src/types/onyx/ReportAction'; import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import PusherHelper from '../utils/PusherHelper'; @@ -75,7 +73,7 @@ describe('actions/IOU', () => { const merchant = 'KFC'; let iouReportID: string | undefined; let createdAction: OnyxEntry; - let iouAction: OnyxEntry; + let iouAction: OnyxEntry>; let transactionID: string | undefined; let transactionThread: OnyxEntry; let transactionThreadCreatedAction: OnyxEntry; @@ -128,27 +126,28 @@ describe('actions/IOU', () => { (reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED, ); const iouActions = Object.values(reportActionsForIOUReport ?? {}).filter( - (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU, + (reportAction): reportAction is OnyxTypes.ReportAction => ReportActionsUtils.isMoneyRequestAction(reportAction), ); expect(Object.values(createdActions).length).toBe(1); expect(Object.values(iouActions).length).toBe(1); createdAction = createdActions?.[0] ?? null; iouAction = iouActions?.[0] ?? null; + const originalMessage = ReportActionsUtils.isMoneyRequestAction(iouAction) ? ReportActionsUtils.getOriginalMessage(iouAction) : undefined; // The CREATED action should not be created after the IOU action expect(Date.parse(createdAction?.created ?? '')).toBeLessThan(Date.parse(iouAction?.created ?? '')); // The IOUReportID should be correct - expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID); + expect(originalMessage?.IOUReportID).toBe(iouReportID); // The comment should be included in the IOU action - expect(iouAction.originalMessage.comment).toBe(comment); + expect(originalMessage?.comment).toBe(comment); // The amount in the IOU action should be correct - expect(iouAction.originalMessage.amount).toBe(amount); + expect(originalMessage?.amount).toBe(amount); // The IOU type should be correct - expect(iouAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); + expect(originalMessage?.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); // Both actions should be pending expect(createdAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); @@ -209,7 +208,7 @@ describe('actions/IOU', () => { expect(transaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); // The transactionID on the iou action should match the one from the transactions collection - expect((iouAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(transactionID); + expect(iouAction && ReportActionsUtils.getOriginalMessage(iouAction)?.IOUTransactionID).toBe(transactionID); expect(transaction?.merchant).toBe(merchant); @@ -264,7 +263,7 @@ describe('actions/IOU', () => { created: DateUtils.getDBTime(), }; let iouReportID: string | undefined; - let iouAction: OnyxEntry; + let iouAction: OnyxEntry>; let iouCreatedAction: OnyxEntry; let transactionID: string | undefined; mockFetch?.pause?.(); @@ -314,24 +313,25 @@ describe('actions/IOU', () => { Onyx.disconnect(connectionID); iouCreatedAction = Object.values(allIOUReportActions ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED); - iouAction = Object.values(allIOUReportActions ?? {}).find( - (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU, + iouAction = Object.values(allIOUReportActions ?? {}).find((reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), ); + const originalMessage = iouAction ? ReportActionsUtils.getOriginalMessage(iouAction) : null; // The CREATED action should not be created after the IOU action expect(Date.parse(iouCreatedAction?.created ?? '')).toBeLessThan(Date.parse(iouAction?.created ?? '')); // The IOUReportID should be correct - expect(iouAction?.originalMessage?.IOUReportID).toBe(iouReportID); + expect(originalMessage?.IOUReportID).toBe(iouReportID); // The comment should be included in the IOU action - expect(iouAction?.originalMessage?.comment).toBe(comment); + expect(originalMessage?.comment).toBe(comment); // The amount in the IOU action should be correct - expect(iouAction?.originalMessage?.amount).toBe(amount); + expect(originalMessage?.amount).toBe(amount); // The IOU action type should be correct - expect(iouAction?.originalMessage?.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); + expect(originalMessage?.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); // The IOU action should be pending expect(iouAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); @@ -354,6 +354,7 @@ describe('actions/IOU', () => { expect(Object.values(allTransactions ?? {}).length).toBe(1); const transaction = Object.values(allTransactions ?? {}).find((t) => !isEmptyObject(t)); transactionID = transaction?.transactionID; + const originalMessage = iouAction ? ReportActionsUtils.getOriginalMessage(iouAction) : null; // The transaction should be attached to the IOU report expect(transaction?.reportID).toBe(iouReportID); @@ -370,7 +371,7 @@ describe('actions/IOU', () => { expect(transaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); // The transactionID on the iou action should match the one from the transactions collection - expect((iouAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(transactionID); + expect(originalMessage?.IOUTransactionID).toBe(transactionID); resolve(); }, @@ -445,7 +446,7 @@ describe('actions/IOU', () => { currency: CONST.CURRENCY.USD, total: existingTransaction.amount, }; - const iouAction: OnyxEntry = { + const iouAction: OnyxEntry> = { reportActionID: NumberUtils.rand64(), actionName: CONST.REPORT.ACTIONS.TYPE.IOU, actorAccountID: RORY_ACCOUNT_ID, @@ -459,7 +460,7 @@ describe('actions/IOU', () => { participantAccountIDs: [RORY_ACCOUNT_ID, CARLOS_ACCOUNT_ID], }, }; - let newIOUAction: OnyxEntry; + let newIOUAction: OnyxEntry>; let newTransaction: OnyxEntry; mockFetch?.pause?.(); return Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, chatReport) @@ -513,21 +514,23 @@ describe('actions/IOU', () => { expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(3); newIOUAction = Object.values(reportActionsForIOUReport ?? {}).find( - (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => + (reportAction): reportAction is OnyxTypes.ReportAction => reportAction?.reportActionID !== createdAction.reportActionID && reportAction?.reportActionID !== iouAction?.reportActionID, ); + const newOriginalMessage = newIOUAction ? ReportActionsUtils.getOriginalMessage(newIOUAction) : null; + // The IOUReportID should be correct - expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID); + expect(ReportActionsUtils.getOriginalMessage(iouAction)?.IOUReportID).toBe(iouReportID); // The comment should be included in the IOU action - expect(newIOUAction?.originalMessage.comment).toBe(comment); + expect(newOriginalMessage?.comment).toBe(comment); // The amount in the IOU action should be correct - expect(newIOUAction?.originalMessage.amount).toBe(amount); + expect(newOriginalMessage?.amount).toBe(amount); // The type of the IOU action should be correct - expect(newIOUAction?.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); + expect(newOriginalMessage?.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); // The IOU action should be pending expect(newIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); @@ -558,7 +561,9 @@ describe('actions/IOU', () => { expect(newTransaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); // The transactionID on the iou action should match the one from the transactions collection - expect((newIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(newTransaction?.transactionID); + expect(ReportActionsUtils.isMoneyRequestAction(newIOUAction) ? ReportActionsUtils.getOriginalMessage(newIOUAction)?.IOUTransactionID : '0').toBe( + newTransaction?.transactionID, + ); resolve(); }, @@ -604,7 +609,7 @@ describe('actions/IOU', () => { let chatReportID: string | undefined; let iouReportID: string | undefined; let createdAction: OnyxEntry; - let iouAction: OnyxEntry; + let iouAction: OnyxEntry>; let transactionID: string; let transactionThreadReport: OnyxEntry; let transactionThreadAction: OnyxEntry; @@ -659,27 +664,29 @@ describe('actions/IOU', () => { Object.values(reportActionsForIOUReport ?? {}).filter((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) ?? null; const iouActions = Object.values(reportActionsForIOUReport ?? {}).filter( - (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU, + (reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), ) ?? null; expect(Object.values(createdActions).length).toBe(1); expect(Object.values(iouActions).length).toBe(1); createdAction = createdActions[0]; iouAction = iouActions[0]; + const originalMessage = ReportActionsUtils.getOriginalMessage(iouAction); // The CREATED action should not be created after the IOU action expect(Date.parse(createdAction?.created ?? '')).toBeLessThan(Date.parse(iouAction?.created ?? {})); // The IOUReportID should be correct - expect(iouAction.originalMessage.IOUReportID).toBe(iouReportID); + expect(originalMessage?.IOUReportID).toBe(iouReportID); // The comment should be included in the IOU action - expect(iouAction.originalMessage.comment).toBe(comment); + expect(originalMessage?.comment).toBe(comment); // The amount in the IOU action should be correct - expect(iouAction.originalMessage.amount).toBe(amount); + expect(originalMessage?.amount).toBe(amount); // The type should be correct - expect(iouAction.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); + expect(originalMessage?.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); // Both actions should be pending expect(createdAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); @@ -711,7 +718,7 @@ describe('actions/IOU', () => { expect(transaction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); // The transactionID on the iou action should match the one from the transactions collection - expect((iouAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(transactionID); + expect(iouAction && ReportActionsUtils.getOriginalMessage(iouAction)?.IOUTransactionID).toBe(transactionID); resolve(); }, @@ -731,7 +738,10 @@ describe('actions/IOU', () => { callback: (reportActionsForIOUReport) => { Onyx.disconnect(connectionID); expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(2); - iouAction = Object.values(reportActionsForIOUReport ?? {}).find((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); + iouAction = Object.values(reportActionsForIOUReport ?? {}).find( + (reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), + ); expect(iouAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); resolve(); }, @@ -791,7 +801,10 @@ describe('actions/IOU', () => { waitForCollectionCallback: false, callback: (reportActionsForReport) => { Onyx.disconnect(connectionID); - iouAction = Object.values(reportActionsForReport ?? {}).find((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); + iouAction = Object.values(reportActionsForReport ?? {}).find( + (reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), + ); expect(iouAction).toBeFalsy(); resolve(); }, @@ -808,7 +821,10 @@ describe('actions/IOU', () => { waitForCollectionCallback: false, callback: (reportActionsForReport) => { Onyx.disconnect(connectionID); - iouAction = Object.values(reportActionsForReport ?? {}).find((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); + iouAction = Object.values(reportActionsForReport ?? {}).find( + (reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), + ); expect(iouAction).toBeFalsy(); resolve(); }, @@ -993,23 +1009,23 @@ describe('actions/IOU', () => { }; let carlosIOUReport: OnyxEntry; - let carlosIOUAction: OnyxEntry; + let carlosIOUAction: OnyxEntry>; let carlosIOUCreatedAction: OnyxEntry; let carlosTransaction: OnyxEntry; - let julesIOUAction: OnyxEntry; + let julesIOUAction: OnyxEntry>; let julesIOUCreatedAction: OnyxEntry; let julesTransaction: OnyxEntry; let vitChatReport: OnyxEntry; let vitIOUReport: OnyxEntry; let vitCreatedAction: OnyxEntry; - let vitIOUAction: OnyxEntry; + let vitIOUAction: OnyxEntry>; let vitTransaction: OnyxEntry; let groupChat: OnyxEntry; let groupCreatedAction: OnyxEntry; - let groupIOUAction: OnyxEntry; + let groupIOUAction: OnyxEntry>; let groupTransaction: OnyxEntry; const reportCollectionDataSet = toCollectionDataSet(ONYXKEYS.COLLECTION.REPORT, [carlosChatReport, julesChatReport, julesIOUReport], (item) => item.reportID); @@ -1177,61 +1193,72 @@ describe('actions/IOU', () => { // Carlos DM should have two reportActions – the existing CREATED action and a pending IOU action expect(Object.values(carlosReportActions ?? {}).length).toBe(2); carlosIOUCreatedAction = Object.values(carlosReportActions ?? {}).find( - (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED, + (reportAction): reportAction is OnyxTypes.ReportAction => + reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED, ); carlosIOUAction = Object.values(carlosReportActions ?? {}).find( - (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU, + (reportAction): reportAction is OnyxTypes.ReportAction => ReportActionsUtils.isMoneyRequestAction(reportAction), ); + const carlosOriginalMessage = carlosIOUAction ? ReportActionsUtils.getOriginalMessage(carlosIOUAction) : undefined; + expect(carlosIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); - expect(carlosIOUAction?.originalMessage.IOUReportID).toBe(carlosIOUReport?.reportID); - expect(carlosIOUAction?.originalMessage.amount).toBe(amount / 4); - expect(carlosIOUAction?.originalMessage.comment).toBe(comment); - expect(carlosIOUAction?.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); + expect(carlosOriginalMessage?.IOUReportID).toBe(carlosIOUReport?.reportID); + expect(carlosOriginalMessage?.amount).toBe(amount / 4); + expect(carlosOriginalMessage?.comment).toBe(comment); + expect(carlosOriginalMessage?.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); expect(Date.parse(carlosIOUCreatedAction?.created ?? '')).toBeLessThan(Date.parse(carlosIOUAction?.created ?? '')); // Jules DM should have three reportActions, the existing CREATED action, the existing IOU action, and a new pending IOU action expect(Object.values(julesReportActions ?? {}).length).toBe(3); expect(julesReportActions?.[julesCreatedAction.reportActionID]).toStrictEqual(julesCreatedAction); julesIOUCreatedAction = Object.values(julesReportActions ?? {}).find( - (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED, + (reportAction): reportAction is OnyxTypes.ReportAction => + reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED, ); julesIOUAction = Object.values(julesReportActions ?? {}).find( - (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => + (reportAction): reportAction is OnyxTypes.ReportAction => reportAction.reportActionID !== julesCreatedAction.reportActionID && reportAction.reportActionID !== julesExistingIOUAction.reportActionID, ); + const julesOriginalMessage = julesIOUAction ? ReportActionsUtils.getOriginalMessage(julesIOUAction) : undefined; + expect(julesIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); - expect(julesIOUAction?.originalMessage.IOUReportID).toBe(julesIOUReport?.reportID); - expect(julesIOUAction?.originalMessage.amount).toBe(amount / 4); - expect(julesIOUAction?.originalMessage.comment).toBe(comment); - expect(julesIOUAction?.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); + expect(julesOriginalMessage?.IOUReportID).toBe(julesIOUReport?.reportID); + expect(julesOriginalMessage?.amount).toBe(amount / 4); + expect(julesOriginalMessage?.comment).toBe(comment); + expect(julesOriginalMessage?.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); expect(Date.parse(julesIOUCreatedAction?.created ?? '')).toBeLessThan(Date.parse(julesIOUAction?.created ?? '')); // Vit DM should have two reportActions – a pending CREATED action and a pending IOU action expect(Object.values(vitReportActions ?? {}).length).toBe(2); vitCreatedAction = Object.values(vitReportActions ?? {}).find( - (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED, + (reportAction): reportAction is OnyxTypes.ReportAction => + reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED, ); - vitIOUAction = Object.values(vitReportActions ?? {}).find( - (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU, + vitIOUAction = Object.values(vitReportActions ?? {}).find((reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), ); + const vitOriginalMessage = vitIOUAction ? ReportActionsUtils.getOriginalMessage(vitIOUAction) : undefined; + expect(vitCreatedAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); expect(vitIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); - expect(vitIOUAction?.originalMessage.IOUReportID).toBe(vitIOUReport?.reportID); - expect(vitIOUAction?.originalMessage.amount).toBe(amount / 4); - expect(vitIOUAction?.originalMessage.comment).toBe(comment); - expect(vitIOUAction?.originalMessage.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); + expect(vitOriginalMessage?.IOUReportID).toBe(vitIOUReport?.reportID); + expect(vitOriginalMessage?.amount).toBe(amount / 4); + expect(vitOriginalMessage?.comment).toBe(comment); + expect(vitOriginalMessage?.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.CREATE); expect(Date.parse(vitCreatedAction?.created ?? '')).toBeLessThan(Date.parse(vitIOUAction?.created ?? '')); // Group chat should have two reportActions – a pending CREATED action and a pending IOU action w/ type SPLIT expect(Object.values(groupReportActions ?? {}).length).toBe(2); groupCreatedAction = Object.values(groupReportActions ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED); groupIOUAction = Object.values(groupReportActions ?? {}).find( - (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU, + (reportAction): reportAction is OnyxTypes.ReportAction => ReportActionsUtils.isMoneyRequestAction(reportAction), ); + const groupOriginalMessage = groupIOUAction ? ReportActionsUtils.getOriginalMessage(groupIOUAction) : undefined; + expect(groupCreatedAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); expect(groupIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); - expect(groupIOUAction?.originalMessage).not.toHaveProperty('IOUReportID'); - expect(groupIOUAction?.originalMessage?.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.SPLIT); + expect(groupOriginalMessage).not.toHaveProperty('IOUReportID'); + expect(groupOriginalMessage?.type).toBe(CONST.IOU.REPORT_ACTION_TYPE.SPLIT); expect(Date.parse(groupCreatedAction?.created ?? '')).toBeLessThanOrEqual(Date.parse(groupIOUAction?.created ?? '')); resolve(); @@ -1257,13 +1284,13 @@ describe('actions/IOU', () => { expect(allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${julesExistingTransaction?.transactionID}`]).toBeTruthy(); carlosTransaction = Object.values(allTransactions ?? {}).find( - (transaction) => transaction?.transactionID === (carlosIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID, + (transaction) => carlosIOUAction && transaction?.transactionID === ReportActionsUtils.getOriginalMessage(carlosIOUAction)?.IOUTransactionID, ); julesTransaction = Object.values(allTransactions ?? {}).find( - (transaction) => transaction?.transactionID === (julesIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID, + (transaction) => julesIOUAction && transaction?.transactionID === ReportActionsUtils.getOriginalMessage(julesIOUAction)?.IOUTransactionID, ); vitTransaction = Object.values(allTransactions ?? {}).find( - (transaction) => transaction?.transactionID === (vitIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID, + (transaction) => vitIOUAction && transaction?.transactionID === ReportActionsUtils.getOriginalMessage(vitIOUAction)?.IOUTransactionID, ); groupTransaction = Object.values(allTransactions ?? {}).find((transaction) => transaction?.reportID === CONST.REPORT.SPLIT_REPORTID); @@ -1383,7 +1410,7 @@ describe('actions/IOU', () => { const comment = 'Giv money plz'; let chatReport: OnyxEntry; let iouReport: OnyxEntry; - let createIOUAction: OnyxEntry; + let createIOUAction: OnyxEntry>; let payIOUAction: OnyxEntry; let transaction: OnyxEntry; IOU.requestMoney({reportID: ''}, amount, CONST.CURRENCY.USD, '', '', RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {}); @@ -1432,9 +1459,11 @@ describe('actions/IOU', () => { const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.iouReportID}`]; - createIOUAction = Object.values(reportActionsForIOUReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); + createIOUAction = Object.values(reportActionsForIOUReport ?? {}).find( + (reportAction): reportAction is OnyxTypes.ReportAction => ReportActionsUtils.isMoneyRequestAction(reportAction), + ); expect(createIOUAction).toBeTruthy(); - expect((createIOUAction?.originalMessage as IOUMessage)?.IOUReportID).toBe(iouReport?.reportID); + expect(createIOUAction && ReportActionsUtils.getOriginalMessage(createIOUAction)?.IOUReportID).toBe(iouReport?.reportID); resolve(); }, @@ -1454,7 +1483,7 @@ describe('actions/IOU', () => { expect(transaction).toBeTruthy(); expect(transaction?.amount).toBe(amount); expect(transaction?.reportID).toBe(iouReport?.reportID); - expect((createIOUAction?.originalMessage as IOUMessage)?.IOUTransactionID).toBe(transaction?.transactionID); + expect(createIOUAction && ReportActionsUtils.getOriginalMessage(createIOUAction)?.IOUTransactionID).toBe(transaction?.transactionID); resolve(); }, }); @@ -1505,7 +1534,8 @@ describe('actions/IOU', () => { payIOUAction = Object.values(reportActionsForIOUReport ?? {}).find( (reportAction) => - reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction?.originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY, + ReportActionsUtils.isMoneyRequestAction(reportAction) && + ReportActionsUtils.getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY, ); expect(payIOUAction).toBeTruthy(); expect(payIOUAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); @@ -1553,7 +1583,9 @@ describe('actions/IOU', () => { expect(Object.values(reportActionsForIOUReport ?? {}).length).toBe(3); payIOUAction = Object.values(reportActionsForIOUReport ?? {}).find( - (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY, + (reportAction) => + ReportActionsUtils.isMoneyRequestAction(reportAction) && + ReportActionsUtils.getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY, ); expect(payIOUAction).toBeTruthy(); @@ -1577,7 +1609,7 @@ describe('actions/IOU', () => { it('updates the IOU request and IOU report when offline', () => { let thread: OptimisticChatReport; let iouReport: OnyxEntry; - let iouAction: OnyxEntry; + let iouAction: OnyxEntry>; let transaction: OnyxEntry; mockFetch?.pause?.(); @@ -1611,7 +1643,9 @@ describe('actions/IOU', () => { callback: (reportActionsForIOUReport) => { Onyx.disconnect(connectionID); - [iouAction] = Object.values(reportActionsForIOUReport ?? {}).filter((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); + [iouAction] = Object.values(reportActionsForIOUReport ?? {}).filter( + (reportAction): reportAction is OnyxTypes.ReportAction => ReportActionsUtils.isMoneyRequestAction(reportAction), + ); resolve(); }, }); @@ -1685,7 +1719,7 @@ describe('actions/IOU', () => { Onyx.disconnect(connectionID); const updatedAction = Object.values(allActions ?? {}).find((reportAction) => !isEmptyObject(reportAction)); expect(updatedAction?.actionName).toEqual('MODIFIEDEXPENSE'); - expect(updatedAction?.originalMessage).toEqual( + expect(updatedAction && ReportActionsUtils.getOriginalMessage(updatedAction)).toEqual( expect.objectContaining({amount: 20000, newComment: 'Double the amount!', oldAmount: amount, oldComment: comment}), ); resolve(); @@ -1730,7 +1764,7 @@ describe('actions/IOU', () => { it('resets the IOU request and IOU report when api returns an error', () => { let thread: OptimisticChatReport; let iouReport: OnyxEntry; - let iouAction: OnyxEntry; + let iouAction: OnyxEntry>; let transaction: OnyxEntry; IOU.requestMoney({reportID: ''}, amount, CONST.CURRENCY.USD, '', merchant, RORY_EMAIL, RORY_ACCOUNT_ID, {login: CARLOS_EMAIL, accountID: CARLOS_ACCOUNT_ID}, comment, {}); @@ -1762,7 +1796,9 @@ describe('actions/IOU', () => { callback: (reportActionsForIOUReport) => { Onyx.disconnect(connectionID); - [iouAction] = Object.values(reportActionsForIOUReport ?? {}).filter((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); + [iouAction] = Object.values(reportActionsForIOUReport ?? {}).filter( + (reportAction): reportAction is OnyxTypes.ReportAction => ReportActionsUtils.isMoneyRequestAction(reportAction), + ); resolve(); }, }); @@ -2075,7 +2111,7 @@ describe('actions/IOU', () => { const comment = 'Send me money please'; let chatReport: OnyxEntry; let iouReport: OnyxEntry; - let createIOUAction: OnyxEntry; + let createIOUAction: OnyxEntry>; let transaction: OnyxEntry; let thread: OptimisticChatReport; const TEST_USER_ACCOUNT_ID = 1; @@ -2165,11 +2201,11 @@ describe('actions/IOU', () => { // Then we should find an IOU action with specific properties const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.iouReportID}`]; - createIOUAction = Object.values(reportActionsForIOUReport ?? {}).find( - (reportAction): reportAction is ReportActionBase & OriginalMessageIOU => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU, + createIOUAction = Object.values(reportActionsForIOUReport ?? {}).find((reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), ); expect(createIOUAction).toBeTruthy(); - expect(createIOUAction?.originalMessage.IOUReportID).toBe(iouReport?.reportID); + expect(createIOUAction && ReportActionsUtils.getOriginalMessage(createIOUAction)?.IOUReportID).toBe(iouReport?.reportID); // When fetching all transactions from Onyx const allTransactions = await new Promise>((resolve) => { @@ -2188,7 +2224,7 @@ describe('actions/IOU', () => { expect(transaction).toBeTruthy(); expect(transaction?.amount).toBe(amount); expect(transaction?.reportID).toBe(iouReport?.reportID); - expect(createIOUAction?.originalMessage.IOUTransactionID).toBe(transaction?.transactionID); + expect(createIOUAction && ReportActionsUtils.getOriginalMessage(createIOUAction)?.IOUTransactionID).toBe(transaction?.transactionID); }); afterEach(PusherHelper.teardown); @@ -2215,7 +2251,9 @@ describe('actions/IOU', () => { }); }); - createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); + createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), + ); // Then the IOU Action should be truthy for offline support. expect(createIOUAction).toBeTruthy(); @@ -2249,7 +2287,9 @@ describe('actions/IOU', () => { }); }); - createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction) => reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); + createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), + ); expect(createIOUAction).toBeFalsy(); // Then we recheck the transaction from the transactions collection @@ -2428,7 +2468,9 @@ describe('actions/IOU', () => { }); }); const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.iouReportID}`]; - createIOUAction = Object.values(reportActionsForIOUReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); + createIOUAction = Object.values(reportActionsForIOUReport ?? {}).find((reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), + ); expect(createIOUAction?.childReportID).toBe(thread.reportID); await waitForBatchedUpdates(); @@ -2510,7 +2552,9 @@ describe('actions/IOU', () => { }); }); const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.iouReportID}`]; - createIOUAction = Object.values(reportActionsForIOUReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); + createIOUAction = Object.values(reportActionsForIOUReport ?? {}).find((reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), + ); expect(createIOUAction?.childReportID).toBe(thread.reportID); await waitForBatchedUpdates(); @@ -2546,7 +2590,9 @@ describe('actions/IOU', () => { waitForCollectionCallback: false, callback: (reportActionsForReport) => { Onyx.disconnect(connectionID); - createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); + createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), + ); resolve(); }, }); @@ -2695,7 +2741,9 @@ describe('actions/IOU', () => { }); const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.iouReportID}`]; - createIOUAction = Object.values(reportActionsForIOUReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); + createIOUAction = Object.values(reportActionsForIOUReport ?? {}).find((reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), + ); expect(createIOUAction?.childReportID).toBe(thread.reportID); await waitForBatchedUpdates(); @@ -2715,7 +2763,9 @@ describe('actions/IOU', () => { waitForCollectionCallback: false, callback: (reportActionsForReport) => { Onyx.disconnect(connectionID); - createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); + createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), + ); resolve(); }, }); @@ -2785,8 +2835,10 @@ describe('actions/IOU', () => { waitForCollectionCallback: false, callback: (reportActionsForReport) => { Onyx.disconnect(connectionID); - createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); - expect(createIOUAction?.message?.[0]?.isDeletedParentAction).toBeTruthy(); + createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), + ); + expect(ReportActionsUtils.getReportActionMessage(createIOUAction)?.isDeletedParentAction).toBeTruthy(); resolve(); }, }); @@ -2803,8 +2855,10 @@ describe('actions/IOU', () => { waitForCollectionCallback: false, callback: (reportActionsForReport) => { Onyx.disconnect(connectionID); - createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); - expect(createIOUAction?.message?.[0]?.isDeletedParentAction).toBeTruthy(); + createIOUAction = Object.values(reportActionsForReport ?? {}).find((reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), + ); + expect(ReportActionsUtils.getReportActionMessage(createIOUAction)?.isDeletedParentAction).toBeTruthy(); resolve(); }, }); @@ -2839,7 +2893,7 @@ describe('actions/IOU', () => { const ioupreview = ReportActionsUtils.getReportPreviewAction(chatReport?.reportID ?? '-1', iouReport?.reportID ?? '-1'); expect(ioupreview).toBeTruthy(); - expect(ioupreview?.message?.[0]?.text).toBe('rory@expensifail.com owes $300.00'); + expect(ReportActionsUtils.getReportActionText(ioupreview)).toBe('rory@expensifail.com owes $300.00'); // When we delete the first expense mockFetch?.pause?.(); @@ -2926,7 +2980,9 @@ describe('actions/IOU', () => { }); const reportActionsForIOUReport = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.iouReportID}`]; - createIOUAction = Object.values(reportActionsForIOUReport ?? {}).find((reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU); + createIOUAction = Object.values(reportActionsForIOUReport ?? {}).find((reportAction): reportAction is OnyxTypes.ReportAction => + ReportActionsUtils.isMoneyRequestAction(reportAction), + ); expect(createIOUAction?.childReportID).toBe(thread.reportID); await waitForBatchedUpdates(); diff --git a/tests/actions/PolicyMemberTest.ts b/tests/actions/PolicyMemberTest.ts index f4d2470707f8..98dcdb2c9626 100644 --- a/tests/actions/PolicyMemberTest.ts +++ b/tests/actions/PolicyMemberTest.ts @@ -3,9 +3,9 @@ import CONST from '@src/CONST'; import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager'; import * as Member from '@src/libs/actions/Policy/Member'; import * as Policy from '@src/libs/actions/Policy/Policy'; +import * as ReportActionsUtils from '@src/libs/ReportActionsUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy as PolicyType, Report, ReportAction} from '@src/types/onyx'; -import type {OriginalMessageJoinPolicyChangeLog} from '@src/types/onyx/OriginalMessage'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomPolicy from '../utils/collections/policies'; @@ -37,10 +37,10 @@ describe('actions/PolicyMember', () => { ...createRandomReport(0), policyID: fakePolicy.id, }; - const fakeReportAction: ReportAction = { + const fakeReportAction = { ...createRandomReportAction(0), actionName: CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_JOIN_REQUEST, - } as ReportAction; + } as ReportAction; mockFetch?.pause?.(); Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); @@ -57,12 +57,10 @@ describe('actions/PolicyMember', () => { callback: (reportActions) => { Onyx.disconnect(connectionID); - const reportAction = reportActions?.[fakeReportAction.reportActionID]; + const reportAction = reportActions?.[fakeReportAction.reportActionID] as ReportAction; if (!isEmptyObject(reportAction)) { - expect((reportAction.originalMessage as OriginalMessageJoinPolicyChangeLog['originalMessage'])?.choice)?.toBe( - CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.ACCEPT, - ); + expect(ReportActionsUtils.getOriginalMessage(reportAction)?.choice)?.toBe(CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.ACCEPT); expect(reportAction?.pendingAction).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); } resolve(); diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index cceb3ae437b9..4bbcafa76bbb 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -3,6 +3,7 @@ import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import {measureFunction} from 'reassure'; import type {ChatReportSelector} from '@hooks/useReportIDs'; +import {getReportActionMessage} from '@libs/ReportActionsUtils'; import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -61,7 +62,7 @@ const allReportActions = Object.fromEntries( message: [ { moderationDecision: { - decision: reportActions[key].message?.[0]?.moderationDecision?.decision, + decision: getReportActionMessage(reportActions[key])?.moderationDecision?.decision, }, }, ], diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 24433d36f0c3..19f552d22a7a 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -16,6 +16,7 @@ import LocalNotification from '@libs/Notification/LocalNotification'; import * as NumberUtils from '@libs/NumberUtils'; import * as Pusher from '@libs/Pusher/pusher'; import PusherConnectionManager from '@libs/PusherConnectionManager'; +import {getReportActionText} from '@libs/ReportActionsUtils'; import FontUtils from '@styles/utils/FontUtils'; import * as AppActions from '@userActions/App'; import * as Report from '@userActions/Report'; @@ -652,7 +653,7 @@ describe('Unread Indicators', () => { // Simulate the response from the server so that the comment can be deleted in this test lastReportAction = reportActions ? CollectionUtils.lastItem(reportActions) : undefined; Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { - lastMessageText: lastReportAction?.message?.[0]?.text, + lastMessageText: getReportActionText(lastReportAction), lastActorAccountID: lastReportAction?.actorAccountID, reportID: REPORT_ID, }); diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 98b92c77663b..4ec530036ba5 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -6,7 +6,6 @@ import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList, Policy, Report, ReportAction} from '@src/types/onyx'; -import type {ModifiedExpenseAction} from '@src/types/onyx/ReportAction'; import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet'; import * as NumberUtils from '../../src/libs/NumberUtils'; import * as LHNTestUtils from '../utils/LHNTestUtils'; @@ -850,7 +849,7 @@ describe('ReportUtils', () => { originalMessage: { whisperedTo: [123456], }, - } as ModifiedExpenseAction; + } as ReportAction; expect(ReportUtils.shouldDisableThread(reportAction, reportID)).toBeTruthy(); }); diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index 89c31d92843e..bf1a65a78870 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -15,7 +15,7 @@ import ReportActionItemSingle from '@pages/home/report/ReportActionItemSingle'; import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData'; import CONST from '@src/CONST'; import type {PersonalDetailsList, Policy, Report, ReportAction} from '@src/types/onyx'; -import type {ActionName} from '@src/types/onyx/OriginalMessage'; +import type ReportActionName from '@src/types/onyx/ReportActionName'; type MockedReportActionItemSingleProps = { /** Determines if the avatar is displayed as a subscript (positioned lower than normal) */ @@ -170,19 +170,6 @@ function getFakeReportAction(actor = 'email1@test.com', millisecondsInThePast = ], originalMessage: { whisperedTo: [], - childReportID: `${reportActionID}`, - emojiReactions: { - heart: { - createdAt: '2023-08-28 15:27:52', - users: { - 1: { - skinTones: { - '-1': '2023-08-28 15:27:52', - }, - }, - }, - }, - }, html: 'hey', lastModified: '2023-08-28 15:28:12.432', }, @@ -246,7 +233,7 @@ function getFakePolicy(id = '1', name = 'Workspace-Test-001'): Policy { /** * @param millisecondsInThePast the number of milliseconds in the past for the last message timestamp (to order reports by most recent messages) */ -function getFakeAdvancedReportAction(actionName: ActionName = 'IOU', actor = 'email1@test.com', millisecondsInThePast = 0): ReportAction { +function getFakeAdvancedReportAction(actionName: ReportActionName = 'IOU', actor = 'email1@test.com', millisecondsInThePast = 0): ReportAction { return { ...getFakeReportAction(actor, millisecondsInThePast), actionName, diff --git a/tests/utils/ReportTestUtils.ts b/tests/utils/ReportTestUtils.ts index 88fc6fc1bde1..61c778ab5576 100644 --- a/tests/utils/ReportTestUtils.ts +++ b/tests/utils/ReportTestUtils.ts @@ -1,10 +1,10 @@ import type {ReportAction, ReportActions} from '@src/types/onyx'; -import type {ActionName} from '@src/types/onyx/OriginalMessage'; +import type ReportActionName from '@src/types/onyx/ReportActionName'; import createRandomReportAction from './collections/reportActions'; -const actionNames: ActionName[] = ['ADDCOMMENT', 'IOU', 'REPORTPREVIEW', 'CLOSED']; +const actionNames: ReportActionName[] = ['ADDCOMMENT', 'IOU', 'REPORTPREVIEW', 'CLOSED']; -const getFakeReportAction = (index: number, actionName?: ActionName): ReportAction => +const getFakeReportAction = (index: number, actionName?: ReportActionName): ReportAction => ({ actionName, actorAccountID: index, @@ -49,14 +49,14 @@ const getFakeReportAction = (index: number, actionName?: ActionName): ReportActi const getMockedSortedReportActions = (length = 100): ReportAction[] => Array.from({length}, (element, index): ReportAction => { - const actionName: ActionName = index === 0 ? 'CREATED' : 'ADDCOMMENT'; + const actionName: ReportActionName = index === 0 ? 'CREATED' : 'ADDCOMMENT'; return getFakeReportAction(index + 1, actionName); }).reverse(); const getMockedReportActionsMap = (length = 100): ReportActions => { const mockReports: ReportActions[] = Array.from({length}, (element, index): ReportActions => { const reportID = index + 1; - const actionName: ActionName = index === 0 ? 'CREATED' : actionNames[index % actionNames.length]; + const actionName: ReportActionName = index === 0 ? 'CREATED' : actionNames[index % actionNames.length]; const reportAction = { ...createRandomReportAction(reportID), actionName, diff --git a/tests/utils/collections/reportActions.ts b/tests/utils/collections/reportActions.ts index dc3e9d8427b5..67dfdc10f00e 100644 --- a/tests/utils/collections/reportActions.ts +++ b/tests/utils/collections/reportActions.ts @@ -2,11 +2,11 @@ import {rand, randAggregation, randBoolean, randWord} from '@ngneat/falso'; import {format} from 'date-fns'; import CONST from '@src/CONST'; import type {ReportAction} from '@src/types/onyx'; -import type {ActionName} from '@src/types/onyx/OriginalMessage'; +import type ReportActionName from '@src/types/onyx/ReportActionName'; import type DeepRecord from '@src/types/utils/DeepRecord'; -const flattenActionNamesValues = (actionNames: DeepRecord) => { - let result: ActionName[] = []; +const flattenActionNamesValues = (actionNames: DeepRecord) => { + let result: ReportActionName[] = []; Object.values(actionNames).forEach((value) => { if (typeof value === 'object') { result = result.concat(flattenActionNamesValues(value)); @@ -26,7 +26,7 @@ const getRandomDate = (): string => { return formattedDate; }; -const deprecatedReportActions: ActionName[] = [ +const deprecatedReportActions: ReportActionName[] = [ CONST.REPORT.ACTIONS.TYPE.DELETED_ACCOUNT, CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_REQUESTED, CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_SETUP_REQUESTED, @@ -37,7 +37,7 @@ export default function createRandomReportAction(index: number): ReportAction { return { // we need to add any here because of the way we are generating random values // eslint-disable-next-line @typescript-eslint/no-explicit-any - actionName: rand(flattenActionNamesValues(CONST.REPORT.ACTIONS.TYPE).filter((actionType: ActionName) => !deprecatedReportActions.includes(actionType))) as any, + actionName: rand(flattenActionNamesValues(CONST.REPORT.ACTIONS.TYPE).filter((actionType: ReportActionName) => !deprecatedReportActions.includes(actionType))) as any, reportActionID: index.toString(), previousReportActionID: (index === 0 ? 0 : index - 1).toString(), actorAccountID: index,