From ed6e2da18c7da54b4ad62f81f7f5592f3df9ecc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Tue, 26 Sep 2023 22:30:42 +0100 Subject: [PATCH 1/9] Rename file to TS --- src/libs/{ReportActionsUtils.js => ReportActionsUtils.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/libs/{ReportActionsUtils.js => ReportActionsUtils.ts} (100%) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.ts similarity index 100% rename from src/libs/ReportActionsUtils.js rename to src/libs/ReportActionsUtils.ts From 6731db898d7d73b01d32b1a3306188d9f064ce40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Sun, 1 Oct 2023 13:02:23 +0100 Subject: [PATCH 2/9] Convert file to TS --- src/ONYXKEYS.ts | 2 +- src/languages/types.ts | 7 +- src/libs/ReportActionsUtils.ts | 503 +++++++++++--------------- src/libs/actions/IOU.js | 4 +- src/libs/actions/Report.js | 10 +- src/libs/actions/Task.js | 2 +- src/libs/isReportMessageAttachment.ts | 9 +- src/types/onyx/OriginalMessage.ts | 54 ++- src/types/onyx/Report.ts | 1 + src/types/onyx/ReportAction.ts | 28 +- src/types/onyx/index.ts | 3 +- 11 files changed, 295 insertions(+), 328 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index d2b3031220f1..20b5b9352868 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -382,7 +382,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record; [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; [ONYXKEYS.COLLECTION.REPORT_METADATA]: OnyxTypes.ReportMetadata; - [ONYXKEYS.COLLECTION.REPORT_ACTIONS]: OnyxTypes.ReportAction; + [ONYXKEYS.COLLECTION.REPORT_ACTIONS]: OnyxTypes.ReportActions; [ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS]: string; [ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS]: OnyxTypes.ReportActionReactions; [ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT]: string; diff --git a/src/languages/types.ts b/src/languages/types.ts index 70bf2e4cae3d..20fffa7fb612 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -1,3 +1,4 @@ +import {ReportAction} from '../types/onyx'; import en from './en'; type AddressLineParams = { @@ -42,15 +43,15 @@ type LocalTimeParams = { }; type EditActionParams = { - action: NonNullable; + action: ReportAction | null; }; type DeleteActionParams = { - action: NonNullable; + action: ReportAction | null; }; type DeleteConfirmationParams = { - action: NonNullable; + action: ReportAction | null; }; type BeginningOfChatHistoryDomainRoomPartOneParams = { diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 67c44784eeb2..a4ac94a5acdb 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1,16 +1,16 @@ -/* eslint-disable rulesdir/prefer-underscore-method */ -import lodashGet from 'lodash/get'; -import _ from 'underscore'; -import {max, parseISO, isEqual} from 'date-fns'; +import {isEqual, max, parseISO} from 'date-fns'; import lodashFindLast from 'lodash/findLast'; -import Onyx from 'react-native-onyx'; -import * as CollectionUtils from './CollectionUtils'; +import Onyx, {OnyxCollection, OnyxUpdate} from 'react-native-onyx'; +import {ValueOf} from 'type-fest'; import CONST from '../CONST'; import ONYXKEYS from '../ONYXKEYS'; +import * as OnyxTypes from '../types/onyx'; +import {ActionName} from '../types/onyx/OriginalMessage'; +import * as CollectionUtils from './CollectionUtils'; import Log from './Log'; import isReportMessageAttachment from './isReportMessageAttachment'; -const allReports = {}; +const allReports: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, callback: (report, key) => { @@ -23,7 +23,7 @@ Onyx.connect({ }, }); -const allReportActions = {}; +const allReportActions: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, callback: (actions, key) => { @@ -39,137 +39,86 @@ Onyx.connect({ let isNetworkOffline = false; Onyx.connect({ key: ONYXKEYS.NETWORK, - callback: (val) => (isNetworkOffline = lodashGet(val, 'isOffline', false)), + callback: (val) => (isNetworkOffline = val?.isOffline ?? false), }); -/** - * @param {Object} reportAction - * @returns {Boolean} - */ -function isCreatedAction(reportAction) { - return lodashGet(reportAction, 'actionName') === CONST.REPORT.ACTIONS.TYPE.CREATED; +function isCreatedAction(reportAction: OnyxTypes.ReportAction | null): boolean { + return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED; } -/** - * @param {Object} reportAction - * @returns {Boolean} - */ -function isDeletedAction(reportAction) { +function isDeletedAction(reportAction: OnyxTypes.ReportAction | null): boolean { // A deleted comment has either an empty array or an object with html field with empty string as value - const message = lodashGet(reportAction, 'message', []); - return message.length === 0 || lodashGet(message, [0, 'html']) === ''; + const message = reportAction?.message ?? []; + return message.length === 0 || message[0]?.html === ''; } -/** - * @param {Object} reportAction - * @returns {Boolean} - */ -function isDeletedParentAction(reportAction) { - return lodashGet(reportAction, ['message', 0, 'isDeletedParentAction'], false) && lodashGet(reportAction, 'childVisibleActionCount', 0) > 0; +function isDeletedParentAction(reportAction: OnyxTypes.ReportAction | null): boolean { + return (reportAction?.message?.[0]?.isDeletedParentAction ?? false) && (reportAction?.childVisibleActionCount ?? 0) > 0; } -/** - * @param {Object} reportAction - * @returns {Boolean} - */ -function isPendingRemove(reportAction) { - return lodashGet(reportAction, 'message[0].moderationDecision.decision') === CONST.MODERATION.MODERATOR_DECISION_PENDING_REMOVE; +function isPendingRemove(reportAction: OnyxTypes.ReportAction | null): boolean { + return reportAction?.message?.[0]?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_REMOVE; } -/** - * @param {Object} reportAction - * @returns {Boolean} - */ -function isMoneyRequestAction(reportAction) { - return lodashGet(reportAction, 'actionName', '') === CONST.REPORT.ACTIONS.TYPE.IOU; +function isMoneyRequestAction(reportAction: OnyxTypes.ReportAction | null): boolean { + return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU; } -/** - * @param {Object} reportAction - * @returns {Boolean} - */ -function isReportPreviewAction(reportAction) { - return lodashGet(reportAction, 'actionName', '') === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW; +function isReportPreviewAction(reportAction: OnyxTypes.ReportAction | null): boolean { + return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW; } -/** - * @param {Object} reportAction - * @returns {Boolean} - */ -function isModifiedExpenseAction(reportAction) { - return lodashGet(reportAction, 'actionName', '') === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE; +function isModifiedExpenseAction(reportAction: OnyxTypes.ReportAction | null): boolean { + return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE; } -function isWhisperAction(action) { - return (action.whisperedToAccountIDs || []).length > 0; +function isWhisperAction(reportAction: OnyxTypes.ReportAction | null): boolean { + return (reportAction?.whisperedToAccountIDs ?? []).length > 0; } /** * Returns whether the comment is a thread parent message/the first message in a thread - * - * @param {Object} reportAction - * @param {String} reportID - * @returns {Boolean} */ -function isThreadParentMessage(reportAction = {}, reportID) { - const {childType, childVisibleActionCount = 0, childReportID} = reportAction; +function isThreadParentMessage(reportAction: OnyxTypes.ReportAction | null, reportID: string): boolean { + const {childType, childVisibleActionCount = 0, childReportID} = reportAction ?? {}; return childType === CONST.REPORT.TYPE.CHAT && (childVisibleActionCount > 0 || String(childReportID) === reportID); } /** * Returns the parentReportAction if the given report is a thread/task. * - * @param {Object} report - * @param {Object} [allReportActionsParam] - * @returns {Object} * @deprecated Use Onyx.connect() or withOnyx() instead */ -function getParentReportAction(report, allReportActionsParam = undefined) { - if (!report || !report.parentReportID || !report.parentReportActionID) { +function getParentReportAction(report: OnyxTypes.Report | null, allReportActionsParam?: OnyxCollection): OnyxTypes.ReportAction | Record { + if (!report?.parentReportID || !report.parentReportActionID) { return {}; } - return lodashGet(allReportActionsParam || allReportActions, [report.parentReportID, report.parentReportActionID], {}); + return (allReportActionsParam ?? allReportActions)?.[report.parentReportID]?.[report.parentReportActionID] ?? {}; } /** * Find the reportAction having the given childReportID in parent report actions - * - * @param {String} childReportID - * @param {String} parentReportID - * @returns {Object} */ -function getParentReportActionInReport(childReportID, parentReportID) { - return _.find(allReportActions[parentReportID], (reportAction) => reportAction && `${reportAction.childReportID}` === `${childReportID}`); +function getParentReportActionInReport(childReportID: string, parentReportID: string): OnyxTypes.ReportAction | null { + return Object.values(allReportActions?.[parentReportID] ?? {}).find((reportAction) => reportAction && `${reportAction.childReportID}` === `${childReportID}`) ?? null; } /** * Determines if the given report action is sent money report action by checking for 'pay' type and presence of IOUDetails object. - * - * @param {Object} reportAction - * @returns {Boolean} */ -function isSentMoneyReportAction(reportAction) { - return ( - reportAction && - reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && - lodashGet(reportAction, 'originalMessage.type') === CONST.IOU.REPORT_ACTION_TYPE.PAY && - _.has(reportAction.originalMessage, 'IOUDetails') - ); +function isSentMoneyReportAction(reportAction: OnyxTypes.ReportAction | null): boolean { + return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && !!reportAction.originalMessage.IOUDetails; } /** * Returns whether the thread is a transaction thread, which is any thread with IOU parent * report action from requesting money (type - create) or from sending money (type - pay with IOUDetails field) - * - * @param {Object} parentReportAction - * @returns {Boolean} */ -function isTransactionThread(parentReportAction) { - const originalMessage = lodashGet(parentReportAction, 'originalMessage', {}); +function isTransactionThread(parentReportAction: OnyxTypes.ReportAction | null): boolean { return ( - parentReportAction && - parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && - (originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || (originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && _.has(originalMessage, 'IOUDetails'))) + parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && + (parentReportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || + (parentReportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && !!parentReportAction.originalMessage.IOUDetails)) ); } @@ -177,67 +126,56 @@ function isTransactionThread(parentReportAction) { * Sort an array of reportActions by their created timestamp first, and reportActionID second * This gives us a stable order even in the case of multiple reportActions created on the same millisecond * - * @param {Array} reportActions - * @param {Boolean} shouldSortInDescendingOrder - * @returns {Array} */ -function getSortedReportActions(reportActions, shouldSortInDescendingOrder = false) { - if (!_.isArray(reportActions)) { +function getSortedReportActions(reportActions: OnyxTypes.ReportAction[] | null, shouldSortInDescendingOrder = false): OnyxTypes.ReportAction[] { + if (!Array.isArray(reportActions)) { throw new Error(`ReportActionsUtils.getSortedReportActions requires an array, received ${typeof reportActions}`); } const invertedMultiplier = shouldSortInDescendingOrder ? -1 : 1; - return _.chain(reportActions) - .compact() - .sort((first, second) => { - // First sort by timestamp - if (first.created !== second.created) { - return (first.created < second.created ? -1 : 1) * invertedMultiplier; - } - - // Then by action type, ensuring that `CREATED` actions always come first if they have the same timestamp as another action type - if ((first.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED || second.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) && first.actionName !== second.actionName) { - return (first.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED ? -1 : 1) * invertedMultiplier; - } - // Ensure that `REPORTPREVIEW` actions always come after if they have the same timestamp as another action type - if ((first.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW || second.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW) && first.actionName !== second.actionName) { - return (first.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW ? 1 : -1) * invertedMultiplier; - } - - // Then fallback on reportActionID as the final sorting criteria. It is a random number, - // but using this will ensure that the order of reportActions with the same created time and action type - // will be consistent across all users and devices - return (first.reportActionID < second.reportActionID ? -1 : 1) * invertedMultiplier; - }) - .value(); + + return reportActions.filter(Boolean).sort((first, second) => { + // First sort by timestamp + if (first.created !== second.created) { + return (first.created < second.created ? -1 : 1) * invertedMultiplier; + } + + // Then by action type, ensuring that `CREATED` actions always come first if they have the same timestamp as another action type + if ((first.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED || second.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) && first.actionName !== second.actionName) { + return (first.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED ? -1 : 1) * invertedMultiplier; + } + // Ensure that `REPORTPREVIEW` actions always come after if they have the same timestamp as another action type + if ((first.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW || second.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW) && first.actionName !== second.actionName) { + return (first.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW ? 1 : -1) * invertedMultiplier; + } + + // Then fallback on reportActionID as the final sorting criteria. It is a random number, + // but using this will ensure that the order of reportActions with the same created time and action type + // will be consistent across all users and devices + return (first.reportActionID < second.reportActionID ? -1 : 1) * invertedMultiplier; + }); } /** * Finds most recent IOU request action ID. - * - * @param {Array} reportActions - * @returns {String} */ -function getMostRecentIOURequestActionID(reportActions) { - const iouRequestTypes = [CONST.IOU.REPORT_ACTION_TYPE.CREATE, CONST.IOU.REPORT_ACTION_TYPE.SPLIT]; - const iouRequestActions = _.filter(reportActions, (action) => iouRequestTypes.includes(lodashGet(action, 'originalMessage.type'))); +function getMostRecentIOURequestActionID(reportActions: OnyxTypes.ReportAction[] | null): string | null { + const iouRequestTypes: Array> = [CONST.IOU.REPORT_ACTION_TYPE.CREATE, CONST.IOU.REPORT_ACTION_TYPE.SPLIT]; + const iouRequestActions = reportActions?.filter((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && iouRequestTypes.includes(action.originalMessage.type)) ?? []; - if (_.isEmpty(iouRequestActions)) { + if (iouRequestActions.length === 0) { return null; } const sortedReportActions = getSortedReportActions(iouRequestActions); - return _.last(sortedReportActions).reportActionID; + return sortedReportActions.at(-1)?.reportActionID ?? null; } /** * Returns array of links inside a given report action - * - * @param {Object} reportAction - * @returns {Array} */ -function extractLinksFromMessageHtml(reportAction) { - const htmlContent = lodashGet(reportAction, ['message', 0, 'html']); +function extractLinksFromMessageHtml(reportAction: OnyxTypes.ReportAction | null): string[] { + const htmlContent = reportAction?.message?.[0]?.html; // Regex to get link in href prop inside of component const regex = /]*?\s+)?href="([^"]*)"/gi; @@ -246,16 +184,19 @@ function extractLinksFromMessageHtml(reportAction) { return []; } - return _.map([...htmlContent.matchAll(regex)], (match) => match[1]); + return [...htmlContent.matchAll(regex)].map((match) => match[1]); } /** * Returns the report action immediately before the specified index. - * @param {Array} reportActions - all actions - * @param {Number} actionIndex - index of the action - * @returns {Object|null} + * @param reportActions - all actions + * @param actionIndex - index of the action */ -function findPreviousAction(reportActions, actionIndex) { +function findPreviousAction(reportActions: OnyxTypes.ReportAction[] | null, actionIndex: number): OnyxTypes.ReportAction | null { + if (!reportActions) { + return null; + } + for (let i = actionIndex + 1; i < reportActions.length; i++) { // Find the next non-pending deletion report action, as the pending delete action means that it is not displayed in the UI, but still is in the report actions list. // If we are offline, all actions are pending but shown in the UI, so we take the previous action, even if it is a delete. @@ -263,6 +204,7 @@ function findPreviousAction(reportActions, actionIndex) { return reportActions[i]; } } + return null; } @@ -270,13 +212,11 @@ function findPreviousAction(reportActions, actionIndex) { * Returns true when the report action immediately before the specified index is a comment made by the same actor who who is leaving a comment in the action at the specified index. * Also checks to ensure that the comment is not too old to be shown as a grouped comment. * - * @param {Array} reportActions - * @param {Number} actionIndex - index of the comment item in state to check - * @returns {Boolean} + * @param actionIndex - index of the comment item in state to check */ -function isConsecutiveActionMadeByPreviousActor(reportActions, actionIndex) { +function isConsecutiveActionMadeByPreviousActor(reportActions: OnyxTypes.ReportAction[] | null, actionIndex: number): boolean { const previousAction = findPreviousAction(reportActions, actionIndex); - const currentAction = reportActions[actionIndex]; + const currentAction = reportActions?.[actionIndex]; // It's OK for there to be no previous action, and in that case, false will be returned // so that the comment isn't grouped @@ -309,12 +249,8 @@ function isConsecutiveActionMadeByPreviousActor(reportActions, actionIndex) { /** * Checks if a reportAction is deprecated. - * - * @param {Object} reportAction - * @param {String} key - * @returns {Boolean} */ -function isReportActionDeprecated(reportAction, key) { +function isReportActionDeprecated(reportAction: OnyxTypes.ReportAction | null, key: string): boolean { if (!reportAction) { return true; } @@ -332,12 +268,12 @@ function isReportActionDeprecated(reportAction, key) { /** * Checks if a reportAction is fit for display, meaning that it's not deprecated, is of a valid * and supported type, it's not deleted and also not closed. - * - * @param {Object} reportAction - * @param {String} key - * @returns {Boolean} */ -function shouldReportActionBeVisible(reportAction, key) { +function shouldReportActionBeVisible(reportAction: OnyxTypes.ReportAction | null, key: string): boolean { + if (!reportAction) { + return false; + } + if (isReportActionDeprecated(reportAction, key)) { return false; } @@ -346,8 +282,11 @@ function shouldReportActionBeVisible(reportAction, key) { return false; } + const {POLICYCHANGELOG: policyChangelogTypes, ...otherActionTypes} = CONST.REPORT.ACTIONS.TYPE; + const supportedActionTypes: ActionName[] = [...Object.values(otherActionTypes), ...Object.values(policyChangelogTypes)]; + // Filter out any unsupported reportAction types - if (!Object.values(CONST.REPORT.ACTIONS.TYPE).includes(reportAction.actionName) && !Object.values(CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG).includes(reportAction.actionName)) { + if (!supportedActionTypes.includes(reportAction.actionName)) { return false; } @@ -369,16 +308,13 @@ function shouldReportActionBeVisible(reportAction, key) { /** * Checks if a reportAction is fit for display as report last action, meaning that * it satisfies shouldReportActionBeVisible, it's not whisper action and not deleted. - * - * @param {Object} reportAction - * @returns {Boolean} */ -function shouldReportActionBeVisibleAsLastAction(reportAction) { +function shouldReportActionBeVisibleAsLastAction(reportAction: OnyxTypes.ReportAction | null): boolean { if (!reportAction) { return false; } - if (!_.isEmpty(reportAction.errors)) { + if (Object.keys(reportAction.errors ?? {}).length > 0) { return false; } @@ -388,42 +324,32 @@ function shouldReportActionBeVisibleAsLastAction(reportAction) { ); } -/** - * @param {String} reportID - * @param {Object} [actionsToMerge] - * @return {Object} - */ -function getLastVisibleAction(reportID, actionsToMerge = {}) { - const updatedActionsToMerge = {}; +function getLastVisibleAction(reportID: string, actionsToMerge: OnyxTypes.ReportActions = {}): OnyxTypes.ReportAction | null { + const updatedActionsToMerge: OnyxTypes.ReportActions = {}; if (actionsToMerge && Object.keys(actionsToMerge).length !== 0) { Object.keys(actionsToMerge).forEach( - (actionToMergeID) => (updatedActionsToMerge[actionToMergeID] = {...allReportActions[reportID][actionToMergeID], ...actionsToMerge[actionToMergeID]}), + (actionToMergeID) => (updatedActionsToMerge[actionToMergeID] = {...allReportActions?.[reportID]?.[actionToMergeID], ...actionsToMerge[actionToMergeID]}), ); } const actions = Object.values({ - ...allReportActions[reportID], + ...allReportActions?.[reportID], ...updatedActionsToMerge, }); const visibleActions = actions.filter((action) => shouldReportActionBeVisibleAsLastAction(action)); if (visibleActions.length === 0) { - return {}; + return null; } const maxDate = max(visibleActions.map((action) => parseISO(action.created))); const maxAction = visibleActions.find((action) => isEqual(parseISO(action.created), maxDate)); - return maxAction; + return maxAction ?? null; } -/** - * @param {String} reportID - * @param {Object} [actionsToMerge] - * @return {Object} - */ -function getLastVisibleMessage(reportID, actionsToMerge = {}) { +function getLastVisibleMessage(reportID: string, actionsToMerge: OnyxTypes.ReportActions = {}): OnyxTypes.Report | null { const lastVisibleAction = getLastVisibleAction(reportID, actionsToMerge); - const message = lodashGet(lastVisibleAction, ['message', 0], {}); + const message = lastVisibleAction?.message?.[0]; - if (isReportMessageAttachment(message)) { + if (message && isReportMessageAttachment(message)) { return { lastMessageTranslationKey: CONST.TRANSLATION_KEYS.ATTACHMENT, lastMessageText: CONST.ATTACHMENT_MESSAGE_TEXT, @@ -437,7 +363,7 @@ function getLastVisibleMessage(reportID, actionsToMerge = {}) { }; } - const messageText = lodashGet(message, 'text', ''); + const messageText = message?.text ?? ''; return { lastMessageText: String(messageText).replace(CONST.REGEX.AFTER_FIRST_LINE_BREAK, '').substring(0, CONST.REPORT.LAST_MESSAGE_TEXT_MAX_LENGTH).trim(), }; @@ -445,12 +371,11 @@ function getLastVisibleMessage(reportID, actionsToMerge = {}) { /** * A helper method to filter out report actions keyed by sequenceNumbers. - * - * @param {Object} reportActions - * @returns {Array} */ -function filterOutDeprecatedReportActions(reportActions) { - return _.filter(reportActions, (reportAction, key) => !isReportActionDeprecated(reportAction, key)); +function filterOutDeprecatedReportActions(reportActions: OnyxTypes.ReportActions | null): OnyxTypes.ReportAction[] { + return Object.entries(reportActions ?? {}) + .filter(([key, reportAction]) => !isReportActionDeprecated(reportAction, key)) + .map((entry) => entry[1]); } /** @@ -458,12 +383,11 @@ function filterOutDeprecatedReportActions(reportActions) { * The report actions need to be sorted by created timestamp first, and reportActionID second * to ensure they will always be displayed in the same order (in case multiple actions have the same timestamp). * This is all handled with getSortedReportActions() which is used by several other methods to keep the code DRY. - * - * @param {Object} reportActions - * @returns {Array} */ -function getSortedReportActionsForDisplay(reportActions) { - const filteredReportActions = _.filter(reportActions, (reportAction, key) => shouldReportActionBeVisible(reportAction, key)); +function getSortedReportActionsForDisplay(reportActions: OnyxTypes.ReportActions | null): OnyxTypes.ReportAction[] { + const filteredReportActions = Object.entries(reportActions ?? {}) + .filter(([key, reportAction]) => shouldReportActionBeVisible(reportAction, key)) + .map((entry) => entry[1]); return getSortedReportActions(filteredReportActions, true); } @@ -472,78 +396,66 @@ function getSortedReportActionsForDisplay(reportActions) { * This method returns the last closed report action so we can always show the correct archived report reason. * Additionally, archived #admins and #announce do not have the closed report action so we will return null if none is found. * - * @param {Object} reportActions - * @returns {Object|null} */ -function getLastClosedReportAction(reportActions) { +function getLastClosedReportAction(reportActions: OnyxTypes.ReportActions | null): OnyxTypes.ReportAction | null { // If closed report action is not present, return early - if (!_.some(reportActions, (action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED)) { + if (!Object.values(reportActions ?? {}).some((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED)) { return null; } + const filteredReportActions = filterOutDeprecatedReportActions(reportActions); const sortedReportActions = getSortedReportActions(filteredReportActions); - return lodashFindLast(sortedReportActions, (action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED); + return lodashFindLast(sortedReportActions, (action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED) ?? null; } /** - * @param {Array} onyxData - * @returns {Object} The latest report action in the `onyxData` or `null` if one couldn't be found + * @returns The latest report action in the `onyxData` or `null` if one couldn't be found */ -function getLatestReportActionFromOnyxData(onyxData) { - const reportActionUpdate = _.find(onyxData, (onyxUpdate) => onyxUpdate.key.startsWith(ONYXKEYS.COLLECTION.REPORT_ACTIONS)); +function getLatestReportActionFromOnyxData(onyxData: OnyxUpdate[] | null): OnyxTypes.ReportAction | null { + const reportActionUpdate = onyxData?.find((onyxUpdate) => onyxUpdate.key.startsWith(ONYXKEYS.COLLECTION.REPORT_ACTIONS)); if (!reportActionUpdate) { return null; } - const reportActions = _.values(reportActionUpdate.value); + const reportActions = Object.values((reportActionUpdate.value as OnyxTypes.ReportActions) ?? {}); const sortedReportActions = getSortedReportActions(reportActions); - return _.last(sortedReportActions); + return sortedReportActions.at(-1) ?? null; } /** * Find the transaction associated with this reportAction, if one exists. - * - * @param {String} reportID - * @param {String} reportActionID - * @returns {String|null} */ -function getLinkedTransactionID(reportID, reportActionID) { - const reportAction = lodashGet(allReportActions, [reportID, reportActionID]); +function getLinkedTransactionID(reportID: string, reportActionID: string): string | null { + const reportAction = allReportActions?.[reportID]?.[reportActionID]; if (!reportAction || reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { return null; } - return reportAction.originalMessage.IOUTransactionID; + return reportAction.originalMessage.IOUTransactionID ?? null; } -/** - * - * @param {String} reportID - * @param {String} reportActionID - * @returns {Object} - */ -function getReportAction(reportID, reportActionID) { - return lodashGet(allReportActions, [reportID, reportActionID], {}); +function getReportAction(reportID: string, reportActionID: string): OnyxTypes.ReportAction | null { + return allReportActions?.[reportID]?.[reportActionID] ?? null; } -/** - * @returns {string} - */ -function getMostRecentReportActionLastModified() { +function getMostRecentReportActionLastModified(): string { // Start with the oldest date possible let mostRecentReportActionLastModified = new Date(0).toISOString(); // Flatten all the actions // Loop over them all to find the one that is the most recent - const flatReportActions = _.flatten(_.map(allReportActions, (actions) => _.values(actions))); - _.each(flatReportActions, (action) => { + const flatReportActions = Object.values(allReportActions ?? {}) + .flatMap((actions) => (actions ? Object.values(actions) : [])) + .filter(Boolean); + flatReportActions.forEach((action) => { // Pending actions should not be counted here as a user could create a comment or some other action while offline and the server might know about // messages they have not seen yet. - if (!_.isEmpty(action.pendingAction)) { + if (action.pendingAction) { return; } - const lastModified = action.lastModified || action.created; + const lastModified = action.lastModified ?? action.created; + if (lastModified < mostRecentReportActionLastModified) { return; } @@ -553,8 +465,8 @@ function getMostRecentReportActionLastModified() { // We might not have actions so we also look at the report objects to see if any have a lastVisibleActionLastModified that is more recent. We don't need to get // any reports that have been updated before either a recently updated report or reportAction as we should be up to date on these - _.each(allReports, (report) => { - const reportLastVisibleActionLastModified = report.lastVisibleActionLastModified || report.lastVisibleActionCreated; + Object.values(allReports ?? {}).forEach((report) => { + const reportLastVisibleActionLastModified = report?.lastVisibleActionLastModified ?? report?.lastVisibleActionCreated; if (!reportLastVisibleActionLastModified || reportLastVisibleActionLastModified < mostRecentReportActionLastModified) { return; } @@ -566,66 +478,47 @@ function getMostRecentReportActionLastModified() { } /** - * @param {*} chatReportID - * @param {*} iouReportID - * @returns {Object} The report preview action or `null` if one couldn't be found + * @returns The report preview action or `null` if one couldn't be found */ -function getReportPreviewAction(chatReportID, iouReportID) { - return _.find( - allReportActions[chatReportID], - (reportAction) => reportAction && reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && lodashGet(reportAction, 'originalMessage.linkedReportID') === iouReportID, +function getReportPreviewAction(chatReportID: string, iouReportID: string): OnyxTypes.ReportAction | null { + return ( + Object.values(allReportActions?.[chatReportID] ?? {}).find( + (reportAction) => reportAction && reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && reportAction.originalMessage.linkedReportID === iouReportID, + ) ?? null ); } /** * Get the iouReportID for a given report action. - * - * @param {Object} reportAction - * @returns {String} */ -function getIOUReportIDFromReportActionPreview(reportAction) { - return lodashGet(reportAction, 'originalMessage.linkedReportID', ''); +function getIOUReportIDFromReportActionPreview(reportAction: OnyxTypes.ReportAction | null): string { + return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW ? reportAction.originalMessage.linkedReportID : ''; } -function isCreatedTaskReportAction(reportAction) { - return reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && _.has(reportAction.originalMessage, 'taskReportID'); +function isCreatedTaskReportAction(reportAction: OnyxTypes.ReportAction | null): boolean { + return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && Boolean(reportAction.originalMessage?.taskReportID); } /** * A helper method to identify if the message is deleted or not. - * - * @param {Object} reportAction - * @returns {Boolean} */ -function isMessageDeleted(reportAction) { - return lodashGet(reportAction, ['message', 0, 'isDeletedParentAction'], false); +function isMessageDeleted(reportAction: OnyxTypes.ReportAction | null): boolean { + return reportAction?.message?.[0]?.isDeletedParentAction ?? false; } /** * Returns the number of money requests associated with a report preview - * - * @param {Object|null} reportPreviewAction - * @returns {Number} */ -function getNumberOfMoneyRequests(reportPreviewAction) { - return lodashGet(reportPreviewAction, 'childMoneyRequestCount', 0); +function getNumberOfMoneyRequests(reportPreviewAction: OnyxTypes.ReportAction | null): number { + return reportPreviewAction?.childMoneyRequestCount ?? 0; } -/** - * @param {*} reportAction - * @returns {Boolean} - */ -function isSplitBillAction(reportAction) { - return lodashGet(reportAction, 'originalMessage.type', '') === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; +function isSplitBillAction(reportAction: OnyxTypes.ReportAction | null): boolean { + return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; } -/** - * - * @param {*} reportAction - * @returns {Boolean} - */ -function isTaskAction(reportAction) { - const reportActionName = lodashGet(reportAction, 'actionName', ''); +function isTaskAction(reportAction: OnyxTypes.ReportAction | null): boolean { + const reportActionName = reportAction?.actionName; return ( reportActionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED || reportActionName === CONST.REPORT.ACTIONS.TYPE.TASKCANCELLED || @@ -633,67 +526,79 @@ function isTaskAction(reportAction) { ); } -/** - * @param {*} reportID - * @returns {[Object]} - */ -function getAllReportActions(reportID) { - return lodashGet(allReportActions, reportID, []); +function getAllReportActions(reportID: string): OnyxTypes.ReportActions { + return allReportActions?.[reportID] ?? {}; } /** * Check whether a report action is an attachment (a file, such as an image or a zip). * - * @param {Object} reportAction report action - * @returns {Boolean} + * @param reportAction report action */ -function isReportActionAttachment(reportAction) { - const message = _.first(lodashGet(reportAction, 'message', [{}])); - return _.has(reportAction, 'isAttachment') ? reportAction.isAttachment : isReportMessageAttachment(message); +function isReportActionAttachment(reportAction: OnyxTypes.ReportAction | null): boolean { + const message = reportAction?.message?.[0]; + + if (reportAction && Object.hasOwn(reportAction, 'isAttachment')) { + // The condition asserts "isAttachment" exists. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return reportAction.isAttachment!; + } + + if (message) { + return isReportMessageAttachment(message) ?? false; + } + + return false; } // eslint-disable-next-line rulesdir/no-negated-variables -function isNotifiableReportAction(reportAction) { - return reportAction && _.contains([CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, CONST.REPORT.ACTIONS.TYPE.IOU, CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE], reportAction.actionName); +function isNotifiableReportAction(reportAction: OnyxTypes.ReportAction | null): boolean { + if (!reportAction) { + return false; + } + + const actions: ActionName[] = [CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, CONST.REPORT.ACTIONS.TYPE.IOU, CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE]; + + return actions.includes(reportAction.actionName); } export { - getSortedReportActions, - getLastVisibleAction, - getLastVisibleMessage, - getMostRecentIOURequestActionID, extractLinksFromMessageHtml, - isCreatedAction, - isDeletedAction, - shouldReportActionBeVisible, - shouldReportActionBeVisibleAsLastAction, - isReportActionDeprecated, - isConsecutiveActionMadeByPreviousActor, - getSortedReportActionsForDisplay, + getAllReportActions, + getIOUReportIDFromReportActionPreview, getLastClosedReportAction, + getLastVisibleAction, + getLastVisibleMessage, getLatestReportActionFromOnyxData, - isMoneyRequestAction, - isThreadParentMessage, getLinkedTransactionID, + getMostRecentIOURequestActionID, getMostRecentReportActionLastModified, - getReportPreviewAction, - isCreatedTaskReportAction, + getNumberOfMoneyRequests, getParentReportAction, getParentReportActionInReport, - isTransactionThread, - isSentMoneyReportAction, + getReportAction, + getReportPreviewAction, + getSortedReportActions, + getSortedReportActionsForDisplay, + isConsecutiveActionMadeByPreviousActor, + isCreatedAction, + isCreatedTaskReportAction, + isDeletedAction, isDeletedParentAction, - isReportPreviewAction, - isModifiedExpenseAction, - getIOUReportIDFromReportActionPreview, isMessageDeleted, - isWhisperAction, + isModifiedExpenseAction, + isMoneyRequestAction, + isNotifiableReportAction, isPendingRemove, - getReportAction, - getNumberOfMoneyRequests, + isReportActionAttachment, + isReportActionDeprecated, + isReportPreviewAction, + isSentMoneyReportAction, isSplitBillAction, isTaskAction, - getAllReportActions, - isReportActionAttachment, - isNotifiableReportAction, + isThreadParentMessage, + isTransactionThread, + isWhisperAction, + shouldReportActionBeVisible, + shouldReportActionBeVisibleAsLastAction, }; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 198ceb2b8172..ef88f314fc10 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1512,7 +1512,7 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView } updatedIOUReport.lastMessageText = iouReportLastMessageText; - updatedIOUReport.lastVisibleActionCreated = lastVisibleAction.created; + updatedIOUReport.lastVisibleActionCreated = lodashGet(lastVisibleAction, 'created'); updatedReportPreviewAction = {...reportPreviewAction}; const messageText = Localize.translateLocal('iou.payerOwesAmount', { @@ -1573,7 +1573,7 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView hasOutstandingIOU: false, iouReportID: null, lastMessageText: ReportActionsUtils.getLastVisibleMessage(iouReport.chatReportID, {[reportPreviewAction.reportActionID]: null}).lastMessageText, - lastVisibleActionCreated: ReportActionsUtils.getLastVisibleAction(iouReport.chatReportID, {[reportPreviewAction.reportActionID]: null}).created, + lastVisibleActionCreated: lodashGet(ReportActionsUtils.getLastVisibleAction(iouReport.chatReportID, {[reportPreviewAction.reportActionID]: null}), 'created'), }, }, ] diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 66008ae5ae2a..6d3317341497 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -361,8 +361,8 @@ function addActions(reportID, text = '', file) { const {lastMessageText = '', lastMessageTranslationKey = ''} = ReportActionsUtils.getLastVisibleMessage(reportID); if (lastMessageText || lastMessageTranslationKey) { const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(reportID); - const lastVisibleActionCreated = lastVisibleAction.created; - const lastActorAccountID = lastVisibleAction.actorAccountID; + const lastVisibleActionCreated = lodashGet(lastVisibleAction, 'created'); + const lastActorAccountID = lodashGet(lastVisibleAction, 'actorAccountID'); failureReport = { lastMessageTranslationKey, lastMessageText, @@ -1043,8 +1043,8 @@ function deleteReportComment(reportID, reportAction) { const {lastMessageText = '', lastMessageTranslationKey = ''} = ReportActionsUtils.getLastVisibleMessage(originalReportID, optimisticReportActions); if (lastMessageText || lastMessageTranslationKey) { const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(originalReportID, optimisticReportActions); - const lastVisibleActionCreated = lastVisibleAction.created; - const lastActorAccountID = lastVisibleAction.actorAccountID; + const lastVisibleActionCreated = lodashGet(lastVisibleAction, 'created'); + const lastActorAccountID = lodashGet(lastVisibleAction, 'actorAccountID'); optimisticReport = { lastMessageTranslationKey, lastMessageText, @@ -1225,7 +1225,7 @@ function editReportComment(reportID, originalReportAction, textForNewComment) { ]; const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(originalReportID, optimisticReportActions); - if (reportActionID === lastVisibleAction.reportActionID) { + if (reportActionID === lodashGet(lastVisibleAction, 'reportActionID')) { const lastMessageText = ReportUtils.formatReportLastMessageText(reportComment); const optimisticReport = { lastMessageTranslationKey: '', diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 91267b9b1053..8ab72e652b70 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -798,7 +798,7 @@ function cancelTask(taskReportID, taskTitle, originalStateNum, originalStatusNum key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`, value: { lastMessageText: ReportActionsUtils.getLastVisibleMessage(parentReport.reportID, {[parentReportAction.reportActionID]: null}).lastMessageText, - lastVisibleActionCreated: ReportActionsUtils.getLastVisibleAction(parentReport.reportID, {[parentReportAction.reportActionID]: null}).created, + lastVisibleActionCreated: lodashGet(ReportActionsUtils.getLastVisibleAction(parentReport.reportID, {[parentReportAction.reportActionID]: null}), 'created'), }, }, { diff --git a/src/libs/isReportMessageAttachment.ts b/src/libs/isReportMessageAttachment.ts index c257e1db4191..8d1112261d1f 100644 --- a/src/libs/isReportMessageAttachment.ts +++ b/src/libs/isReportMessageAttachment.ts @@ -1,10 +1,5 @@ import CONST from '../CONST'; - -type IsReportMessageAttachmentParams = { - text: string; - html: string; - translationKey: string; -}; +import {Message} from '../types/onyx/ReportAction'; /** * Check whether a report action is Attachment or not. @@ -12,7 +7,7 @@ type IsReportMessageAttachmentParams = { * * @param reportActionMessage report action's message as text, html and translationKey */ -export default function isReportMessageAttachment({text, html, translationKey}: IsReportMessageAttachmentParams): boolean { +export default function isReportMessageAttachment({text, html, translationKey}: Message): boolean { if (!text || !html) { return false; } diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 369ff44773ab..eab2d6939714 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -1,5 +1,19 @@ import {ValueOf} from 'type-fest'; import CONST from '../../CONST'; +import DeepValueOf from '../utils/DeepValueOf'; + +type ActionName = DeepValueOf; + +type OriginalMessageApproved = { + actionName: typeof CONST.REPORT.ACTIONS.TYPE.APPROVED; + originalMessage: unknown; +}; + +type IOUDetails = { + amount: number; + comment?: string; + currency: string; +}; type OriginalMessageIOU = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.IOU; @@ -8,24 +22,39 @@ type OriginalMessageIOU = { IOUTransactionID?: string; IOUReportID?: number; + + /** Only exists when we are sending money */ + IOUDetails?: IOUDetails; + amount: number; comment?: string; currency: string; lastModified?: string; participantAccountIDs?: number[]; participants?: string[]; - type: string; + type: ValueOf; }; }; -type FlagSeverityName = 'spam' | 'inconsiderate' | 'bullying' | 'intimidation' | 'harassment' | 'assault'; +type FlagSeverityName = ValueOf< + Pick< + typeof CONST.MODERATION, + 'FLAG_SEVERITY_SPAM' | 'FLAG_SEVERITY_INCONSIDERATE' | 'FLAG_SEVERITY_INTIMIDATION' | 'FLAG_SEVERITY_BULLYING' | 'FLAG_SEVERITY_HARASSMENT' | 'FLAG_SEVERITY_ASSAULT' + > +>; type FlagSeverity = { accountID: number; timestamp: string; }; +type DecisionName = ValueOf< + Pick< + typeof CONST.MODERATION, + 'MODERATOR_DECISION_PENDING' | 'MODERATOR_DECISION_PENDING_HIDE' | 'MODERATOR_DECISION_PENDING_REMOVE' | 'MODERATOR_DECISION_APPROVED' | 'MODERATOR_DECISION_HIDDEN' + > +>; type Decision = { - decision: string; + decision: DecisionName; timestamp: string; }; @@ -59,7 +88,7 @@ type OriginalMessageClosed = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.CLOSED; originalMessage: { policyName: string; - reason: 'default' | 'accountClosed' | 'accountMerged' | 'removedPolicy' | 'policyDeleted'; + reason: ValueOf lastModified?: string; }; }; @@ -125,7 +154,18 @@ type OriginalMessagePolicyTask = { originalMessage: unknown; }; +type OriginalMessageModifiedExpense = { + actionName: typeof CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE; + originalMessage: unknown; +}; + +type OriginalMessageReimbursementQueued = { + actionName: typeof CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTQUEUED; + originalMessage: unknown; +}; + type OriginalMessage = + | OriginalMessageApproved | OriginalMessageIOU | OriginalMessageAddComment | OriginalMessageClosed @@ -134,7 +174,9 @@ type OriginalMessage = | OriginalMessageChronosOOOList | OriginalMessageReportPreview | OriginalMessagePolicyChangeLog - | OriginalMessagePolicyTask; + | OriginalMessagePolicyTask + | OriginalMessageModifiedExpense + | OriginalMessageReimbursementQueued; export default OriginalMessage; -export type {Reaction, ChronosOOOEvent}; +export type {ChronosOOOEvent, Decision, Reaction, ActionName}; diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 46e51fe41238..b8a1a0634f7e 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -63,6 +63,7 @@ type Report = { /** The report type */ type?: string; + lastMessageTranslationKey?: string; parentReportID?: string; parentReportActionID?: string; isOptimisticReport?: boolean; diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index ec505a7e8d07..b266d5e78cf8 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -1,10 +1,13 @@ -import OriginalMessage, {Reaction} from './OriginalMessage'; import * as OnyxCommon from './OnyxCommon'; +import OriginalMessage, {Decision, Reaction} from './OriginalMessage'; type Message = { /** The type of the action item fragment. Used to render a corresponding component */ type: string; + /** The html content of the fragment. */ + html: string; + /** The text content of the fragment. */ text: string; @@ -34,6 +37,9 @@ type Message = { isDeletedParentAction: boolean; whisperedTo: number[]; reactions: Reaction[]; + + moderationDecision?: Decision; + translationKey?: string; }; type Person = { @@ -44,7 +50,10 @@ type Person = { type ReportActionBase = { /** The ID of the reportAction. It is the string representation of the a 64-bit integer. */ - reportActionID?: string; + reportActionID: string; + + /** @deprecated Replaced by reportActionID. */ + sequenceNumber?: number; /** 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; @@ -55,7 +64,7 @@ type ReportActionBase = { person?: Person[]; /** ISO-formatted datetime */ - created?: string; + created: string; /** report action message */ message?: Message[]; @@ -79,10 +88,23 @@ type ReportActionBase = { childCommenterCount?: number; childLastVisibleActionCreated?: string; childVisibleActionCount?: number; + childMoneyRequestCount?: number; + + /** ISO-formatted datetime */ + lastModified?: string; pendingAction?: OnyxCommon.PendingAction; + delegateAccountID?: string; + + /** Server side errors keyed by microtime */ + errors?: OnyxCommon.Errors; + + isAttachment?: boolean; }; type ReportAction = ReportActionBase & OriginalMessage; +type ReportActions = Record; + export default ReportAction; +export type {ReportActions, Message}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index e50925e7adf2..cb064869427e 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -37,7 +37,7 @@ import Policy from './Policy'; import PolicyCategory from './PolicyCategory'; import Report from './Report'; import ReportMetadata from './ReportMetadata'; -import ReportAction from './ReportAction'; +import ReportAction, {ReportActions} from './ReportAction'; import ReportActionReactions from './ReportActionReactions'; import SecurityGroup from './SecurityGroup'; import Transaction from './Transaction'; @@ -87,6 +87,7 @@ export type { Report, ReportMetadata, ReportAction, + ReportActions, ReportActionReactions, SecurityGroup, Transaction, From 12ed8197bc3e392e9d55bdd193e7519ca28aec14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Tue, 3 Oct 2023 16:55:32 +0100 Subject: [PATCH 3/9] Use OnyxEntry when applicable --- src/libs/ReportActionsUtils.ts | 62 +++++++++++++++++----------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 6f478eb0d9fe..4cee9f4bced2 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1,6 +1,6 @@ import {isEqual, max, parseISO} from 'date-fns'; import lodashFindLast from 'lodash/findLast'; -import Onyx, {OnyxCollection, OnyxUpdate} from 'react-native-onyx'; +import Onyx, {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; import CONST from '../CONST'; import ONYXKEYS from '../ONYXKEYS'; @@ -48,44 +48,44 @@ Onyx.connect({ callback: (val) => (isNetworkOffline = val?.isOffline ?? false), }); -function isCreatedAction(reportAction: OnyxTypes.ReportAction | null): boolean { +function isCreatedAction(reportAction: OnyxEntry): boolean { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED; } -function isDeletedAction(reportAction: OnyxTypes.ReportAction | null): boolean { +function isDeletedAction(reportAction: OnyxEntry): boolean { // A deleted comment has either an empty array or an object with html field with empty string as value const message = reportAction?.message ?? []; return message.length === 0 || message[0]?.html === ''; } -function isDeletedParentAction(reportAction: OnyxTypes.ReportAction | null): boolean { +function isDeletedParentAction(reportAction: OnyxEntry): boolean { return (reportAction?.message?.[0]?.isDeletedParentAction ?? false) && (reportAction?.childVisibleActionCount ?? 0) > 0; } -function isPendingRemove(reportAction: OnyxTypes.ReportAction | null): boolean { +function isPendingRemove(reportAction: OnyxEntry): boolean { return reportAction?.message?.[0]?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_REMOVE; } -function isMoneyRequestAction(reportAction: OnyxTypes.ReportAction | null): boolean { +function isMoneyRequestAction(reportAction: OnyxEntry): boolean { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU; } -function isReportPreviewAction(reportAction: OnyxTypes.ReportAction | null): boolean { +function isReportPreviewAction(reportAction: OnyxEntry): boolean { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW; } -function isModifiedExpenseAction(reportAction: OnyxTypes.ReportAction | null): boolean { +function isModifiedExpenseAction(reportAction: OnyxEntry): boolean { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE; } -function isWhisperAction(reportAction: OnyxTypes.ReportAction | null): boolean { +function isWhisperAction(reportAction: OnyxEntry): boolean { return (reportAction?.whisperedToAccountIDs ?? []).length > 0; } /** * Returns whether the comment is a thread parent message/the first message in a thread */ -function isThreadParentMessage(reportAction: OnyxTypes.ReportAction | null, reportID: string): boolean { +function isThreadParentMessage(reportAction: OnyxEntry, reportID: string): boolean { const {childType, childVisibleActionCount = 0, childReportID} = reportAction ?? {}; return childType === CONST.REPORT.TYPE.CHAT && (childVisibleActionCount > 0 || String(childReportID) === reportID); } @@ -95,7 +95,7 @@ function isThreadParentMessage(reportAction: OnyxTypes.ReportAction | null, repo * * @deprecated Use Onyx.connect() or withOnyx() instead */ -function getParentReportAction(report: OnyxTypes.Report | null, allReportActionsParam?: OnyxCollection): OnyxTypes.ReportAction | Record { +function getParentReportAction(report: OnyxEntry, allReportActionsParam?: OnyxCollection): OnyxTypes.ReportAction | Record { if (!report?.parentReportID || !report.parentReportActionID) { return {}; } @@ -105,7 +105,7 @@ function getParentReportAction(report: OnyxTypes.Report | null, allReportActions /** * Determines if the given report action is sent money report action by checking for 'pay' type and presence of IOUDetails object. */ -function isSentMoneyReportAction(reportAction: OnyxTypes.ReportAction | null): boolean { +function isSentMoneyReportAction(reportAction: OnyxEntry): boolean { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && !!reportAction.originalMessage.IOUDetails; } @@ -113,7 +113,7 @@ function isSentMoneyReportAction(reportAction: OnyxTypes.ReportAction | null): b * Returns whether the thread is a transaction thread, which is any thread with IOU parent * report action from requesting money (type - create) or from sending money (type - pay with IOUDetails field) */ -function isTransactionThread(parentReportAction: OnyxTypes.ReportAction | null): boolean { +function isTransactionThread(parentReportAction: OnyxEntry): boolean { return ( parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && (parentReportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || @@ -173,7 +173,7 @@ function getMostRecentIOURequestActionID(reportActions: OnyxTypes.ReportAction[] /** * Returns array of links inside a given report action */ -function extractLinksFromMessageHtml(reportAction: OnyxTypes.ReportAction | null): string[] { +function extractLinksFromMessageHtml(reportAction: OnyxEntry): string[] { const htmlContent = reportAction?.message?.[0]?.html; // Regex to get link in href prop inside of component @@ -191,7 +191,7 @@ function extractLinksFromMessageHtml(reportAction: OnyxTypes.ReportAction | null * @param reportActions - all actions * @param actionIndex - index of the action */ -function findPreviousAction(reportActions: OnyxTypes.ReportAction[] | null, actionIndex: number): OnyxTypes.ReportAction | null { +function findPreviousAction(reportActions: OnyxTypes.ReportAction[] | null, actionIndex: number): OnyxEntry { if (!reportActions) { return null; } @@ -249,7 +249,7 @@ function isConsecutiveActionMadeByPreviousActor(reportActions: OnyxTypes.ReportA /** * Checks if a reportAction is deprecated. */ -function isReportActionDeprecated(reportAction: OnyxTypes.ReportAction | null, key: string): boolean { +function isReportActionDeprecated(reportAction: OnyxEntry, key: string): boolean { if (!reportAction) { return true; } @@ -268,7 +268,7 @@ function isReportActionDeprecated(reportAction: OnyxTypes.ReportAction | null, k * Checks if a reportAction is fit for display, meaning that it's not deprecated, is of a valid * and supported type, it's not deleted and also not closed. */ -function shouldReportActionBeVisible(reportAction: OnyxTypes.ReportAction | null, key: string): boolean { +function shouldReportActionBeVisible(reportAction: OnyxEntry, key: string): boolean { if (!reportAction) { return false; } @@ -308,7 +308,7 @@ function shouldReportActionBeVisible(reportAction: OnyxTypes.ReportAction | null * Checks if a reportAction is fit for display as report last action, meaning that * it satisfies shouldReportActionBeVisible, it's not whisper action and not deleted. */ -function shouldReportActionBeVisibleAsLastAction(reportAction: OnyxTypes.ReportAction | null): boolean { +function shouldReportActionBeVisibleAsLastAction(reportAction: OnyxEntry): boolean { if (!reportAction) { return false; } @@ -325,7 +325,7 @@ function shouldReportActionBeVisibleAsLastAction(reportAction: OnyxTypes.ReportA ); } -function getLastVisibleAction(reportID: string, actionsToMerge: OnyxTypes.ReportActions = {}): OnyxTypes.ReportAction | null { +function getLastVisibleAction(reportID: string, actionsToMerge: OnyxTypes.ReportActions = {}): OnyxEntry { const updatedActionsToMerge: OnyxTypes.ReportActions = {}; if (actionsToMerge && Object.keys(actionsToMerge).length !== 0) { Object.keys(actionsToMerge).forEach( @@ -398,7 +398,7 @@ function getSortedReportActionsForDisplay(reportActions: OnyxTypes.ReportActions * Additionally, archived #admins and #announce do not have the closed report action so we will return null if none is found. * */ -function getLastClosedReportAction(reportActions: OnyxTypes.ReportActions | null): OnyxTypes.ReportAction | null { +function getLastClosedReportAction(reportActions: OnyxTypes.ReportActions | null): OnyxEntry { // If closed report action is not present, return early if (!Object.values(reportActions ?? {}).some((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED)) { return null; @@ -412,7 +412,7 @@ function getLastClosedReportAction(reportActions: OnyxTypes.ReportActions | null /** * @returns The latest report action in the `onyxData` or `null` if one couldn't be found */ -function getLatestReportActionFromOnyxData(onyxData: OnyxUpdate[] | null): OnyxTypes.ReportAction | null { +function getLatestReportActionFromOnyxData(onyxData: OnyxUpdate[] | null): OnyxEntry { const reportActionUpdate = onyxData?.find((onyxUpdate) => onyxUpdate.key.startsWith(ONYXKEYS.COLLECTION.REPORT_ACTIONS)); if (!reportActionUpdate) { @@ -435,7 +435,7 @@ function getLinkedTransactionID(reportID: string, reportActionID: string): strin return reportAction.originalMessage.IOUTransactionID ?? null; } -function getReportAction(reportID: string, reportActionID: string): OnyxTypes.ReportAction | null { +function getReportAction(reportID: string, reportActionID: string): OnyxEntry { return allReportActions?.[reportID]?.[reportActionID] ?? null; } @@ -481,7 +481,7 @@ function getMostRecentReportActionLastModified(): string { /** * @returns The report preview action or `null` if one couldn't be found */ -function getReportPreviewAction(chatReportID: string, iouReportID: string): OnyxTypes.ReportAction | null { +function getReportPreviewAction(chatReportID: string, iouReportID: string): OnyxEntry { return ( Object.values(allReportActions?.[chatReportID] ?? {}).find( (reportAction) => reportAction && reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && reportAction.originalMessage.linkedReportID === iouReportID, @@ -492,33 +492,33 @@ function getReportPreviewAction(chatReportID: string, iouReportID: string): Onyx /** * Get the iouReportID for a given report action. */ -function getIOUReportIDFromReportActionPreview(reportAction: OnyxTypes.ReportAction | null): string { +function getIOUReportIDFromReportActionPreview(reportAction: OnyxEntry): string { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW ? reportAction.originalMessage.linkedReportID : ''; } -function isCreatedTaskReportAction(reportAction: OnyxTypes.ReportAction | null): boolean { +function isCreatedTaskReportAction(reportAction: OnyxEntry): boolean { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && Boolean(reportAction.originalMessage?.taskReportID); } /** * A helper method to identify if the message is deleted or not. */ -function isMessageDeleted(reportAction: OnyxTypes.ReportAction | null): boolean { +function isMessageDeleted(reportAction: OnyxEntry): boolean { return reportAction?.message?.[0]?.isDeletedParentAction ?? false; } /** * Returns the number of money requests associated with a report preview */ -function getNumberOfMoneyRequests(reportPreviewAction: OnyxTypes.ReportAction | null): number { +function getNumberOfMoneyRequests(reportPreviewAction: OnyxEntry): number { return reportPreviewAction?.childMoneyRequestCount ?? 0; } -function isSplitBillAction(reportAction: OnyxTypes.ReportAction | null): boolean { +function isSplitBillAction(reportAction: OnyxEntry): boolean { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; } -function isTaskAction(reportAction: OnyxTypes.ReportAction | null): boolean { +function isTaskAction(reportAction: OnyxEntry): boolean { const reportActionName = reportAction?.actionName; return ( reportActionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED || @@ -536,7 +536,7 @@ function getAllReportActions(reportID: string): OnyxTypes.ReportActions { * * @param reportAction report action */ -function isReportActionAttachment(reportAction: OnyxTypes.ReportAction | null): boolean { +function isReportActionAttachment(reportAction: OnyxEntry): boolean { const message = reportAction?.message?.[0]; if (reportAction && Object.hasOwn(reportAction, 'isAttachment')) { @@ -553,7 +553,7 @@ function isReportActionAttachment(reportAction: OnyxTypes.ReportAction | null): } // eslint-disable-next-line rulesdir/no-negated-variables -function isNotifiableReportAction(reportAction: OnyxTypes.ReportAction | null): boolean { +function isNotifiableReportAction(reportAction: OnyxEntry): boolean { if (!reportAction) { return false; } From 2d37dfdd8de141511dcf1df91c7253834a1b2514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Tue, 3 Oct 2023 17:05:35 +0100 Subject: [PATCH 4/9] Fix prettier --- src/types/onyx/OriginalMessage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index eab2d6939714..3d996c6b2abf 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -88,7 +88,7 @@ type OriginalMessageClosed = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.CLOSED; originalMessage: { policyName: string; - reason: ValueOf + reason: ValueOf; lastModified?: string; }; }; From 22cfff61e044b8204b240c8dabb97eeac5d1ee3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 4 Oct 2023 13:05:28 +0100 Subject: [PATCH 5/9] Improve comment --- src/types/onyx/ReportAction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index b266d5e78cf8..9cf8584fde7c 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -52,7 +52,7 @@ type ReportActionBase = { /** The ID of the reportAction. It is the string representation of the a 64-bit integer. */ reportActionID: string; - /** @deprecated Replaced by reportActionID. */ + /** @deprecated Used in old report actions before migration. Replaced by reportActionID. */ sequenceNumber?: number; /** The ID of the previous reportAction on the report. It is a string represenation of a 64-bit integer (or null for CREATED actions). */ From 258d0a81a4fd57bc5b23e8751db6a078b3dbb2e0 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 9 Oct 2023 09:28:22 +0200 Subject: [PATCH 6/9] fix: resolve comments --- src/libs/ReportActionsUtils.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 4cee9f4bced2..e3b053c94252 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -106,7 +106,9 @@ function getParentReportAction(report: OnyxEntry, allReportAct * Determines if the given report action is sent money report action by checking for 'pay' type and presence of IOUDetails object. */ function isSentMoneyReportAction(reportAction: OnyxEntry): boolean { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && !!reportAction.originalMessage.IOUDetails; + return ( + reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction?.originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && !!reportAction?.originalMessage?.IOUDetails + ); } /** @@ -497,7 +499,7 @@ function getIOUReportIDFromReportActionPreview(reportAction: OnyxEntry): boolean { - return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && Boolean(reportAction.originalMessage?.taskReportID); + return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && !!reportAction.originalMessage?.taskReportID; } /** From e74d7248d068e4edb551525fb52bdaeb3cc38fd2 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 9 Oct 2023 09:42:13 +0200 Subject: [PATCH 7/9] fix: fixed import --- src/libs/ReportActionsUtils.ts | 85 +++++++++++++++++----------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index e3b053c94252..57646abc9341 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -4,7 +4,8 @@ import Onyx, {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; import CONST from '../CONST'; import ONYXKEYS from '../ONYXKEYS'; -import * as OnyxTypes from '../types/onyx'; +import ReportAction, {ReportActions} from '../types/onyx/ReportAction'; +import Report from '../types/onyx/Report'; import {ActionName} from '../types/onyx/OriginalMessage'; import * as CollectionUtils from './CollectionUtils'; import Log from './Log'; @@ -16,7 +17,7 @@ type LastVisibleMessage = { lastMessageHtml?: string; }; -const allReports: OnyxCollection = {}; +const allReports: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, callback: (report, key) => { @@ -29,7 +30,7 @@ Onyx.connect({ }, }); -const allReportActions: OnyxCollection = {}; +const allReportActions: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, callback: (actions, key) => { @@ -48,44 +49,44 @@ Onyx.connect({ callback: (val) => (isNetworkOffline = val?.isOffline ?? false), }); -function isCreatedAction(reportAction: OnyxEntry): boolean { +function isCreatedAction(reportAction: OnyxEntry): boolean { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED; } -function isDeletedAction(reportAction: OnyxEntry): boolean { +function isDeletedAction(reportAction: OnyxEntry): boolean { // A deleted comment has either an empty array or an object with html field with empty string as value const message = reportAction?.message ?? []; return message.length === 0 || message[0]?.html === ''; } -function isDeletedParentAction(reportAction: OnyxEntry): boolean { +function isDeletedParentAction(reportAction: OnyxEntry): boolean { return (reportAction?.message?.[0]?.isDeletedParentAction ?? false) && (reportAction?.childVisibleActionCount ?? 0) > 0; } -function isPendingRemove(reportAction: OnyxEntry): boolean { +function isPendingRemove(reportAction: OnyxEntry): boolean { return reportAction?.message?.[0]?.moderationDecision?.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_REMOVE; } -function isMoneyRequestAction(reportAction: OnyxEntry): boolean { +function isMoneyRequestAction(reportAction: OnyxEntry): boolean { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU; } -function isReportPreviewAction(reportAction: OnyxEntry): boolean { +function isReportPreviewAction(reportAction: OnyxEntry): boolean { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW; } -function isModifiedExpenseAction(reportAction: OnyxEntry): boolean { +function isModifiedExpenseAction(reportAction: OnyxEntry): boolean { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE; } -function isWhisperAction(reportAction: OnyxEntry): boolean { +function isWhisperAction(reportAction: OnyxEntry): boolean { return (reportAction?.whisperedToAccountIDs ?? []).length > 0; } /** * Returns whether the comment is a thread parent message/the first message in a thread */ -function isThreadParentMessage(reportAction: OnyxEntry, reportID: string): boolean { +function isThreadParentMessage(reportAction: OnyxEntry, reportID: string): boolean { const {childType, childVisibleActionCount = 0, childReportID} = reportAction ?? {}; return childType === CONST.REPORT.TYPE.CHAT && (childVisibleActionCount > 0 || String(childReportID) === reportID); } @@ -95,7 +96,7 @@ function isThreadParentMessage(reportAction: OnyxEntry, * * @deprecated Use Onyx.connect() or withOnyx() instead */ -function getParentReportAction(report: OnyxEntry, allReportActionsParam?: OnyxCollection): OnyxTypes.ReportAction | Record { +function getParentReportAction(report: OnyxEntry, allReportActionsParam?: OnyxCollection): ReportAction | Record { if (!report?.parentReportID || !report.parentReportActionID) { return {}; } @@ -105,7 +106,7 @@ function getParentReportAction(report: OnyxEntry, allReportAct /** * Determines if the given report action is sent money report action by checking for 'pay' type and presence of IOUDetails object. */ -function isSentMoneyReportAction(reportAction: OnyxEntry): boolean { +function isSentMoneyReportAction(reportAction: OnyxEntry): boolean { return ( reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction?.originalMessage?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && !!reportAction?.originalMessage?.IOUDetails ); @@ -115,7 +116,7 @@ function isSentMoneyReportAction(reportAction: OnyxEntry * Returns whether the thread is a transaction thread, which is any thread with IOU parent * report action from requesting money (type - create) or from sending money (type - pay with IOUDetails field) */ -function isTransactionThread(parentReportAction: OnyxEntry): boolean { +function isTransactionThread(parentReportAction: OnyxEntry): boolean { return ( parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && (parentReportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || @@ -128,7 +129,7 @@ function isTransactionThread(parentReportAction: OnyxEntry> = [CONST.IOU.REPORT_ACTION_TYPE.CREATE, CONST.IOU.REPORT_ACTION_TYPE.SPLIT]; const iouRequestActions = reportActions?.filter((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && iouRequestTypes.includes(action.originalMessage.type)) ?? []; @@ -175,7 +176,7 @@ function getMostRecentIOURequestActionID(reportActions: OnyxTypes.ReportAction[] /** * Returns array of links inside a given report action */ -function extractLinksFromMessageHtml(reportAction: OnyxEntry): string[] { +function extractLinksFromMessageHtml(reportAction: OnyxEntry): string[] { const htmlContent = reportAction?.message?.[0]?.html; // Regex to get link in href prop inside of component @@ -193,7 +194,7 @@ function extractLinksFromMessageHtml(reportAction: OnyxEntry { +function findPreviousAction(reportActions: ReportAction[] | null, actionIndex: number): OnyxEntry { if (!reportActions) { return null; } @@ -215,7 +216,7 @@ function findPreviousAction(reportActions: OnyxTypes.ReportAction[] | null, acti * * @param actionIndex - index of the comment item in state to check */ -function isConsecutiveActionMadeByPreviousActor(reportActions: OnyxTypes.ReportAction[] | null, actionIndex: number): boolean { +function isConsecutiveActionMadeByPreviousActor(reportActions: ReportAction[] | null, actionIndex: number): boolean { const previousAction = findPreviousAction(reportActions, actionIndex); const currentAction = reportActions?.[actionIndex]; @@ -251,7 +252,7 @@ function isConsecutiveActionMadeByPreviousActor(reportActions: OnyxTypes.ReportA /** * Checks if a reportAction is deprecated. */ -function isReportActionDeprecated(reportAction: OnyxEntry, key: string): boolean { +function isReportActionDeprecated(reportAction: OnyxEntry, key: string): boolean { if (!reportAction) { return true; } @@ -270,7 +271,7 @@ function isReportActionDeprecated(reportAction: OnyxEntry, key: string): boolean { +function shouldReportActionBeVisible(reportAction: OnyxEntry, key: string): boolean { if (!reportAction) { return false; } @@ -310,7 +311,7 @@ function shouldReportActionBeVisible(reportAction: OnyxEntry): boolean { +function shouldReportActionBeVisibleAsLastAction(reportAction: OnyxEntry): boolean { if (!reportAction) { return false; } @@ -327,8 +328,8 @@ function shouldReportActionBeVisibleAsLastAction(reportAction: OnyxEntry { - const updatedActionsToMerge: OnyxTypes.ReportActions = {}; +function getLastVisibleAction(reportID: string, actionsToMerge: ReportActions = {}): OnyxEntry { + const updatedActionsToMerge: ReportActions = {}; if (actionsToMerge && Object.keys(actionsToMerge).length !== 0) { Object.keys(actionsToMerge).forEach( (actionToMergeID) => (updatedActionsToMerge[actionToMergeID] = {...allReportActions?.[reportID]?.[actionToMergeID], ...actionsToMerge[actionToMergeID]}), @@ -348,7 +349,7 @@ function getLastVisibleAction(reportID: string, actionsToMerge: OnyxTypes.Report return maxAction ?? null; } -function getLastVisibleMessage(reportID: string, actionsToMerge: OnyxTypes.ReportActions = {}): LastVisibleMessage { +function getLastVisibleMessage(reportID: string, actionsToMerge: ReportActions = {}): LastVisibleMessage { const lastVisibleAction = getLastVisibleAction(reportID, actionsToMerge); const message = lastVisibleAction?.message?.[0]; @@ -375,7 +376,7 @@ function getLastVisibleMessage(reportID: string, actionsToMerge: OnyxTypes.Repor /** * A helper method to filter out report actions keyed by sequenceNumbers. */ -function filterOutDeprecatedReportActions(reportActions: OnyxTypes.ReportActions | null): OnyxTypes.ReportAction[] { +function filterOutDeprecatedReportActions(reportActions: ReportActions | null): ReportAction[] { return Object.entries(reportActions ?? {}) .filter(([key, reportAction]) => !isReportActionDeprecated(reportAction, key)) .map((entry) => entry[1]); @@ -387,7 +388,7 @@ function filterOutDeprecatedReportActions(reportActions: OnyxTypes.ReportActions * to ensure they will always be displayed in the same order (in case multiple actions have the same timestamp). * This is all handled with getSortedReportActions() which is used by several other methods to keep the code DRY. */ -function getSortedReportActionsForDisplay(reportActions: OnyxTypes.ReportActions | null): OnyxTypes.ReportAction[] { +function getSortedReportActionsForDisplay(reportActions: ReportActions | null): ReportAction[] { const filteredReportActions = Object.entries(reportActions ?? {}) .filter(([key, reportAction]) => shouldReportActionBeVisible(reportAction, key)) .map((entry) => entry[1]); @@ -400,7 +401,7 @@ function getSortedReportActionsForDisplay(reportActions: OnyxTypes.ReportActions * Additionally, archived #admins and #announce do not have the closed report action so we will return null if none is found. * */ -function getLastClosedReportAction(reportActions: OnyxTypes.ReportActions | null): OnyxEntry { +function getLastClosedReportAction(reportActions: ReportActions | null): OnyxEntry { // If closed report action is not present, return early if (!Object.values(reportActions ?? {}).some((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED)) { return null; @@ -414,14 +415,14 @@ function getLastClosedReportAction(reportActions: OnyxTypes.ReportActions | null /** * @returns The latest report action in the `onyxData` or `null` if one couldn't be found */ -function getLatestReportActionFromOnyxData(onyxData: OnyxUpdate[] | null): OnyxEntry { +function getLatestReportActionFromOnyxData(onyxData: OnyxUpdate[] | null): OnyxEntry { const reportActionUpdate = onyxData?.find((onyxUpdate) => onyxUpdate.key.startsWith(ONYXKEYS.COLLECTION.REPORT_ACTIONS)); if (!reportActionUpdate) { return null; } - const reportActions = Object.values((reportActionUpdate.value as OnyxTypes.ReportActions) ?? {}); + const reportActions = Object.values((reportActionUpdate.value as ReportActions) ?? {}); const sortedReportActions = getSortedReportActions(reportActions); return sortedReportActions.at(-1) ?? null; } @@ -437,7 +438,7 @@ function getLinkedTransactionID(reportID: string, reportActionID: string): strin return reportAction.originalMessage.IOUTransactionID ?? null; } -function getReportAction(reportID: string, reportActionID: string): OnyxEntry { +function getReportAction(reportID: string, reportActionID: string): OnyxEntry { return allReportActions?.[reportID]?.[reportActionID] ?? null; } @@ -483,7 +484,7 @@ 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?.[chatReportID] ?? {}).find( (reportAction) => reportAction && reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && reportAction.originalMessage.linkedReportID === iouReportID, @@ -494,33 +495,33 @@ function getReportPreviewAction(chatReportID: string, iouReportID: string): Onyx /** * Get the iouReportID for a given report action. */ -function getIOUReportIDFromReportActionPreview(reportAction: OnyxEntry): string { +function getIOUReportIDFromReportActionPreview(reportAction: OnyxEntry): string { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW ? reportAction.originalMessage.linkedReportID : ''; } -function isCreatedTaskReportAction(reportAction: OnyxEntry): boolean { +function isCreatedTaskReportAction(reportAction: OnyxEntry): boolean { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && !!reportAction.originalMessage?.taskReportID; } /** * A helper method to identify if the message is deleted or not. */ -function isMessageDeleted(reportAction: OnyxEntry): boolean { +function isMessageDeleted(reportAction: OnyxEntry): boolean { return reportAction?.message?.[0]?.isDeletedParentAction ?? false; } /** * Returns the number of money requests associated with a report preview */ -function getNumberOfMoneyRequests(reportPreviewAction: OnyxEntry): number { +function getNumberOfMoneyRequests(reportPreviewAction: OnyxEntry): number { return reportPreviewAction?.childMoneyRequestCount ?? 0; } -function isSplitBillAction(reportAction: OnyxEntry): boolean { +function isSplitBillAction(reportAction: OnyxEntry): boolean { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction.originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; } -function isTaskAction(reportAction: OnyxEntry): boolean { +function isTaskAction(reportAction: OnyxEntry): boolean { const reportActionName = reportAction?.actionName; return ( reportActionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED || @@ -529,7 +530,7 @@ function isTaskAction(reportAction: OnyxEntry): boolean ); } -function getAllReportActions(reportID: string): OnyxTypes.ReportActions { +function getAllReportActions(reportID: string): ReportActions { return allReportActions?.[reportID] ?? {}; } @@ -538,7 +539,7 @@ function getAllReportActions(reportID: string): OnyxTypes.ReportActions { * * @param reportAction report action */ -function isReportActionAttachment(reportAction: OnyxEntry): boolean { +function isReportActionAttachment(reportAction: OnyxEntry): boolean { const message = reportAction?.message?.[0]; if (reportAction && Object.hasOwn(reportAction, 'isAttachment')) { @@ -555,7 +556,7 @@ function isReportActionAttachment(reportAction: OnyxEntry): boolean { +function isNotifiableReportAction(reportAction: OnyxEntry): boolean { if (!reportAction) { return false; } From 590697543b42655e977f9ea6872237ae4c16b9c1 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 9 Oct 2023 09:53:35 +0200 Subject: [PATCH 8/9] fix: resolve comment --- src/libs/ReportActionsUtils.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 57646abc9341..54bb28d620b8 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -542,10 +542,8 @@ function getAllReportActions(reportID: string): ReportActions { function isReportActionAttachment(reportAction: OnyxEntry): boolean { const message = reportAction?.message?.[0]; - if (reportAction && Object.hasOwn(reportAction, 'isAttachment')) { - // The condition asserts "isAttachment" exists. - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return reportAction.isAttachment!; + if (reportAction && 'isAttachment' in reportAction) { + return reportAction.isAttachment ?? false; } if (message) { From 60d97bb6794e49ee7c2f955005546d696d31fcbf Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 17 Oct 2023 09:18:09 +0200 Subject: [PATCH 9/9] fix: resolved comments --- src/libs/ReportActionsUtils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 280c01d27491..9d7c87d1ec9d 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -541,7 +541,6 @@ function getAllReportActions(reportID: string): ReportActions { /** * Check whether a report action is an attachment (a file, such as an image or a zip). * - * @param reportAction report action */ function isReportActionAttachment(reportAction: OnyxEntry): boolean { const message = reportAction?.message?.[0]; @@ -551,7 +550,7 @@ function isReportActionAttachment(reportAction: OnyxEntry): boolea } if (message) { - return isReportMessageAttachment(message) ?? false; + return isReportMessageAttachment(message); } return false;