From 512de40902e2d00f4e58891c0b7b3045dbd6fe56 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Fri, 8 Dec 2023 13:08:23 +0100 Subject: [PATCH 1/6] [TS migration] Migrate 'ReportActionItemMessage.js' component --- src/libs/ReportUtils.ts | 6 +- .../home/report/ReportActionItemMessage.js | 101 ------------------ .../home/report/ReportActionItemMessage.tsx | 93 ++++++++++++++++ src/types/onyx/OriginalMessage.ts | 4 +- src/types/onyx/ReportAction.ts | 3 + 5 files changed, 102 insertions(+), 105 deletions(-) delete mode 100644 src/pages/home/report/ReportActionItemMessage.js create mode 100644 src/pages/home/report/ReportActionItemMessage.tsx diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 608c4333ab0f..a6393780d2a7 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1000,16 +1000,16 @@ function isOneOnOneChat(report: OnyxEntry): boolean { /** * Get the report given a reportID */ -function getReport(reportID: string | undefined): OnyxEntry | EmptyObject { +function getReport(reportID: string | undefined): OnyxEntry { /** * Using typical string concatenation here due to performance issues * with template literals. */ if (!allReports) { - return {}; + return null; } - return allReports?.[ONYXKEYS.COLLECTION.REPORT + reportID] ?? {}; + return allReports?.[ONYXKEYS.COLLECTION.REPORT + reportID] ?? null; } /** diff --git a/src/pages/home/report/ReportActionItemMessage.js b/src/pages/home/report/ReportActionItemMessage.js deleted file mode 100644 index 2265530f29a1..000000000000 --- a/src/pages/home/report/ReportActionItemMessage.js +++ /dev/null @@ -1,101 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React from 'react'; -import {Text, View} from 'react-native'; -import _ from 'underscore'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import useThemeStyles from '@styles/useThemeStyles'; -import CONST from '@src/CONST'; -import ReportActionItemFragment from './ReportActionItemFragment'; -import reportActionPropTypes from './reportActionPropTypes'; - -const propTypes = { - /** The report action */ - action: PropTypes.shape(reportActionPropTypes).isRequired, - - /** Should the comment have the appearance of being grouped with the previous comment? */ - displayAsGroup: PropTypes.bool.isRequired, - - /** Additional styles to add after local styles. */ - style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), - - /** Whether or not the message is hidden by moderation */ - isHidden: PropTypes.bool, - - /** The ID of the report */ - reportID: PropTypes.string.isRequired, - - /** localization props */ - ...withLocalizePropTypes, -}; - -const defaultProps = { - style: [], - isHidden: false, -}; - -function ReportActionItemMessage(props) { - const styles = useThemeStyles(); - const fragments = _.compact(props.action.previousMessage || props.action.message); - const isIOUReport = ReportActionsUtils.isMoneyRequestAction(props.action); - let iouMessage; - if (isIOUReport) { - const iouReportID = lodashGet(props.action, 'originalMessage.IOUReportID'); - if (iouReportID) { - iouMessage = ReportUtils.getReportPreviewMessage(ReportUtils.getReport(iouReportID), props.action); - } - } - - const isApprovedOrSubmittedReportAction = _.contains([CONST.REPORT.ACTIONS.TYPE.APPROVED, CONST.REPORT.ACTIONS.TYPE.SUBMITTED], props.action.actionName); - - /** - * Get the ReportActionItemFragments - * @param {Boolean} shouldWrapInText determines whether the fragments are wrapped in a Text component - * @returns {Object} report action item fragments - */ - const renderReportActionItemFragments = (shouldWrapInText) => { - const reportActionItemFragments = _.map(fragments, (fragment, index) => ( - - )); - - // Approving or submitting reports in oldDot results in system messages made up of multiple fragments of `TEXT` type - // which we need to wrap in `` to prevent them rendering on separate lines. - - return shouldWrapInText ? {reportActionItemFragments} : reportActionItemFragments; - }; - - return ( - - {!props.isHidden ? ( - renderReportActionItemFragments(isApprovedOrSubmittedReportAction) - ) : ( - {props.translate('moderation.flaggedContent')} - )} - - ); -} - -ReportActionItemMessage.propTypes = propTypes; -ReportActionItemMessage.defaultProps = defaultProps; -ReportActionItemMessage.displayName = 'ReportActionItemMessage'; - -export default withLocalize(ReportActionItemMessage); diff --git a/src/pages/home/report/ReportActionItemMessage.tsx b/src/pages/home/report/ReportActionItemMessage.tsx new file mode 100644 index 000000000000..4f70e98d44b9 --- /dev/null +++ b/src/pages/home/report/ReportActionItemMessage.tsx @@ -0,0 +1,93 @@ +import React, {ReactElement} from 'react'; +import {StyleProp, Text, View, ViewStyle} from 'react-native'; +import useLocalize from '@hooks/useLocalize'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import * as ReportUtils from '@libs/ReportUtils'; +import useThemeStyles from '@styles/useThemeStyles'; +import CONST from '@src/CONST'; +import {ReportAction} from '@src/types/onyx'; +import {OriginalMessageAddComment} from '@src/types/onyx/OriginalMessage'; +import ReportActionItemFragment from './ReportActionItemFragment'; + +type ReportActionItemMessageProps = { + /** The report action */ + action: ReportAction; + + /** Should the comment have the appearance of being grouped with the previous comment? */ + displayAsGroup: boolean; + + /** Additional styles to add after local styles. */ + style?: StyleProp; + + /** Whether or not the message is hidden by moderation */ + isHidden?: boolean; + + /** The ID of the report */ + reportID: string; +}; + +function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHidden = false}: ReportActionItemMessageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const fragments = (action.previousMessage ?? action.message ?? []).filter((item) => !!item); + const isIOUReport = ReportActionsUtils.isMoneyRequestAction(action); + let iouMessage: string | undefined; + if (isIOUReport) { + const originalMessage = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? action.originalMessage : null; + const iouReportID = originalMessage?.IOUReportID; + if (iouReportID) { + iouMessage = ReportUtils.getReportPreviewMessage(ReportUtils.getReport(iouReportID), action); + } + } + + const isApprovedOrSubmittedReportAction = [CONST.REPORT.ACTIONS.TYPE.APPROVED, CONST.REPORT.ACTIONS.TYPE.SUBMITTED].some((type) => type === action.actionName); + + /** + * Get the ReportActionItemFragments + * @param shouldWrapInText determines whether the fragments are wrapped in a Text component + * @returns report action item fragments + */ + const renderReportActionItemFragments = (shouldWrapInText: boolean): ReactElement | ReactElement[] => { + const reportActionItemFragments = fragments.map((fragment, index) => ( + + )); + + // Approving or submitting reports in oldDot results in system messages made up of multiple fragments of `TEXT` type + // which we need to wrap in `` to prevent them rendering on separate lines. + + return shouldWrapInText ? {reportActionItemFragments} : reportActionItemFragments; + }; + + return ( + + {!isHidden ? ( + renderReportActionItemFragments(isApprovedOrSubmittedReportAction) + ) : ( + {translate('moderation.flaggedContent')} + )} + + ); +} + +ReportActionItemMessage.displayName = 'ReportActionItemMessage'; + +export default ReportActionItemMessage; diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index f76fbd5ffd7d..c44589a4a105 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -24,6 +24,7 @@ type OriginalMessageApproved = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.APPROVED; originalMessage: unknown; }; +type OriginalMessageSource = 'Chronos' | 'email' | 'ios' | 'android' | 'web' | ''; type IOUDetails = { amount: number; @@ -94,6 +95,7 @@ type OriginalMessageAddComment = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT; originalMessage: { html: string; + source?: OriginalMessageSource; lastModified?: string; taskReportID?: string; edits?: string[]; @@ -231,4 +233,4 @@ type OriginalMessage = | OriginalMessageMoved; export default OriginalMessage; -export type {ChronosOOOEvent, Decision, Reaction, ActionName, IOUMessage, Closed, OriginalMessageActionName, ChangeLog}; +export type {ChronosOOOEvent, Decision, Reaction, ActionName, IOUMessage, Closed, OriginalMessageActionName, ChangeLog, OriginalMessageAddComment}; diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index a0e90f4e9c34..ee4345f50071 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -79,6 +79,9 @@ type ReportActionBase = { /** report action message */ message?: Message[]; + /** previous report action message */ + previousMessage?: Message[]; + /** Whether we have received a response back from the server */ isLoading?: boolean; From 8164342608fa099725d8759f65f098b9b9449050 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Mon, 11 Dec 2023 10:30:48 +0100 Subject: [PATCH 2/6] Update type imports --- src/pages/home/report/ReportActionItemMessage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItemMessage.tsx b/src/pages/home/report/ReportActionItemMessage.tsx index 4f70e98d44b9..81742a47dcde 100644 --- a/src/pages/home/report/ReportActionItemMessage.tsx +++ b/src/pages/home/report/ReportActionItemMessage.tsx @@ -5,8 +5,8 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; -import {ReportAction} from '@src/types/onyx'; -import {OriginalMessageAddComment} from '@src/types/onyx/OriginalMessage'; +import type {ReportAction} from '@src/types/onyx'; +import type {OriginalMessageAddComment} from '@src/types/onyx/OriginalMessage'; import ReportActionItemFragment from './ReportActionItemFragment'; type ReportActionItemMessageProps = { From bfbbe952acd05949f0cb295923d63743f5810f3a Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Mon, 11 Dec 2023 11:23:54 +0100 Subject: [PATCH 3/6] Add type predicate to isEmptyObject function --- src/libs/ReportUtils.ts | 8 ++++---- src/types/utils/EmptyObject.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a6393780d2a7..53eaeb3d00b8 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1000,16 +1000,16 @@ function isOneOnOneChat(report: OnyxEntry): boolean { /** * Get the report given a reportID */ -function getReport(reportID: string | undefined): OnyxEntry { +function getReport(reportID: string | undefined): OnyxEntry | EmptyObject { /** * Using typical string concatenation here due to performance issues * with template literals. */ if (!allReports) { - return null; + return {}; } - return allReports?.[ONYXKEYS.COLLECTION.REPORT + reportID] ?? null; + return allReports?.[ONYXKEYS.COLLECTION.REPORT + reportID] ?? {}; } /** @@ -1922,7 +1922,7 @@ function getTransactionReportName(reportAction: OnyxEntry): string * @param [reportAction] This can be either a report preview action or the IOU action */ function getReportPreviewMessage( - report: OnyxEntry, + report: OnyxEntry | EmptyObject, reportAction: OnyxEntry | EmptyObject = {}, shouldConsiderReceiptBeingScanned = false, isPreviewMessageForParentChatReport = false, diff --git a/src/types/utils/EmptyObject.ts b/src/types/utils/EmptyObject.ts index 9b9f3244a5f8..8cb1ff6db2a2 100644 --- a/src/types/utils/EmptyObject.ts +++ b/src/types/utils/EmptyObject.ts @@ -7,7 +7,7 @@ function isNotEmptyObject | Falsy>(arg: T | Em return Object.keys(arg ?? {}).length > 0; } -function isEmptyObject(obj: T): boolean { +function isEmptyObject(obj: T | EmptyObject | null | undefined): obj is EmptyObject { return Object.keys(obj ?? {}).length === 0; } From e08d75e301efe84af1023d55ee32d353b57a89b1 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 13 Dec 2023 09:46:19 +0100 Subject: [PATCH 4/6] Updates to follow main branch --- .../home/report/ReportActionItemMessage.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/pages/home/report/ReportActionItemMessage.tsx b/src/pages/home/report/ReportActionItemMessage.tsx index 81742a47dcde..a095115eff88 100644 --- a/src/pages/home/report/ReportActionItemMessage.tsx +++ b/src/pages/home/report/ReportActionItemMessage.tsx @@ -7,6 +7,7 @@ import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import type {ReportAction} from '@src/types/onyx'; import type {OriginalMessageAddComment} from '@src/types/onyx/OriginalMessage'; +import TextCommentFragment from './comment/TextCommentFragment'; import ReportActionItemFragment from './ReportActionItemFragment'; type ReportActionItemMessageProps = { @@ -32,6 +33,21 @@ function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHid const fragments = (action.previousMessage ?? action.message ?? []).filter((item) => !!item); const isIOUReport = ReportActionsUtils.isMoneyRequestAction(action); + + if (ReportActionsUtils.isMemberChangeAction(action)) { + const fragment = ReportActionsUtils.getMemberChangeMessageFragment(action); + + return ( + + ); + } + let iouMessage: string | undefined; if (isIOUReport) { const originalMessage = action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? action.originalMessage : null; From 6ad344fe14d96a9ae5aaf3d0eb13071432477245 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 13 Dec 2023 09:59:09 +0100 Subject: [PATCH 5/6] Fix linter error --- src/types/onyx/OriginalMessage.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index aa647649d418..aec355bf81b6 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -237,4 +237,16 @@ type OriginalMessage = | OriginalMessageMoved; export default OriginalMessage; -export type {ChronosOOOEvent, Decision, Reaction, ActionName, IOUMessage, Closed, OriginalMessageActionName, ChangeLog, OriginalMessageIOU, OriginalMessageCreated, OriginalMessageAddComment}; +export type { + ChronosOOOEvent, + Decision, + Reaction, + ActionName, + IOUMessage, + Closed, + OriginalMessageActionName, + ChangeLog, + OriginalMessageIOU, + OriginalMessageCreated, + OriginalMessageAddComment, +}; From c5da0d20a95e69a81f7f5df1b7c1fa343c5f5122 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 14 Dec 2023 17:11:29 +0100 Subject: [PATCH 6/6] Update isEmptyObject typing --- src/types/utils/EmptyObject.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/types/utils/EmptyObject.ts b/src/types/utils/EmptyObject.ts index 8cb1ff6db2a2..aa8b538499cd 100644 --- a/src/types/utils/EmptyObject.ts +++ b/src/types/utils/EmptyObject.ts @@ -2,12 +2,14 @@ import Falsy from './Falsy'; type EmptyObject = Record; +type EmptyValue = EmptyObject | null | undefined; + // eslint-disable-next-line rulesdir/no-negated-variables function isNotEmptyObject | Falsy>(arg: T | EmptyObject): arg is NonNullable { return Object.keys(arg ?? {}).length > 0; } -function isEmptyObject(obj: T | EmptyObject | null | undefined): obj is EmptyObject { +function isEmptyObject(obj: T | EmptyValue): obj is EmptyValue { return Object.keys(obj ?? {}).length === 0; }