From b0f38dfc08311097120a3ac62aa47ed1f7993ce3 Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Thu, 23 May 2024 10:34:12 +0700 Subject: [PATCH 1/6] Fix endless loading spinner of thread header Signed-off-by: Tsaqif --- src/libs/ReportUtils.ts | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 5749e241b6fb..0f10b90ed04f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -487,6 +487,7 @@ let currentUserEmail: string | undefined; let currentUserPrivateDomain: string | undefined; let currentUserAccountID: number | undefined; let isAnonymousUser = false; +let reportActionParsedHtmlCache = {}; const defaultAvatarBuildingIconTestID = 'SvgDefaultAvatarBuilding Icon'; @@ -3078,7 +3079,37 @@ function getReportActionMessage(reportAction: ReportAction | EmptyObject, parent if (ReportActionsUtils.isReimbursementQueuedAction(reportAction)) { return getReimbursementQueuedActionMessage(reportAction, getReport(parentReportID), false); } - return Str.removeSMSDomain(reportAction?.message?.[0]?.text ?? ''); + + const text = parseReportActionHtmlToText(reportAction); + return Str.removeSMSDomain(text); +} + +/** + * Parse html of reportAction into text + */ +function parseReportActionHtmlToText(reportAction: OnyxEntry): string{ + const {html, text} = reportAction?.message?.[0]; + + if (!html) { + return text ?? ""; + } + + const mentionReportRegex = //gi; + const matches = html.matchAll(mentionReportRegex); + + const reportIdToName = {}; + for (const match of matches) { + reportIdToName[match[1]] = getReportName(getReport(match[1])); + } + + const mentionUserRegex = //gi; + const accountIdToName = {}; + const accountIds = Array.from(html.matchAll(mentionUserRegex), (m) => m[1]); + const logins = PersonalDetailsUtils.getLoginsByAccountIDs(accountIds); + accountIds.forEach((id, index) => accountIdToName[id] = logins[index]); + + const parser = new ExpensiMark(); + return parser.htmlToText(html, {reportIdToName, accountIdToName}); } /** @@ -6746,6 +6777,7 @@ export { navigateToDetailsPage, navigateToPrivateNotes, parseReportRouteParams, + parseReportActionHtmlToText, reportFieldsEnabled, requiresAttentionFromCurrentUser, shouldAutoFocusOnKeyPress, From 09ca09fcdad1cfd86979a0081fe9c8c177689d00 Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Thu, 23 May 2024 13:27:58 +0700 Subject: [PATCH 2/6] Fix typecheck, lint and minor changes Signed-off-by: Tsaqif --- src/libs/ReportUtils.ts | 60 +++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 0f10b90ed04f..065edaca86e2 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -487,7 +487,6 @@ let currentUserEmail: string | undefined; let currentUserPrivateDomain: string | undefined; let currentUserAccountID: number | undefined; let isAnonymousUser = false; -let reportActionParsedHtmlCache = {}; const defaultAvatarBuildingIconTestID = 'SvgDefaultAvatarBuilding Icon'; @@ -3066,50 +3065,53 @@ function getInvoicePayerName(report: OnyxEntry): string { return getPolicyName(report, false, allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiver?.policyID}`]); } -/** - * Get the report action message for a report action. - */ -function getReportActionMessage(reportAction: ReportAction | EmptyObject, parentReportID?: string) { - if (isEmptyObject(reportAction)) { - return ''; - } - if (ReportActionsUtils.isApprovedOrSubmittedReportAction(reportAction)) { - return ReportActionsUtils.getReportActionMessageText(reportAction); - } - if (ReportActionsUtils.isReimbursementQueuedAction(reportAction)) { - return getReimbursementQueuedActionMessage(reportAction, getReport(parentReportID), false); - } - - const text = parseReportActionHtmlToText(reportAction); - return Str.removeSMSDomain(text); -} - /** * Parse html of reportAction into text */ -function parseReportActionHtmlToText(reportAction: OnyxEntry): string{ - const {html, text} = reportAction?.message?.[0]; +function parseReportActionHtmlToText(reportAction: OnyxEntry, reportID: string): string { + const {html, text} = reportAction?.message?.[0] ?? {}; if (!html) { - return text ?? ""; + return text ?? ''; } const mentionReportRegex = //gi; const matches = html.matchAll(mentionReportRegex); - const reportIdToName = {}; + const reportIdToName: Record = {}; for (const match of matches) { - reportIdToName[match[1]] = getReportName(getReport(match[1])); + if (match[1] !== reportID) { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + reportIdToName[match[1]] = getReportName(getReport(match[1])) ?? ''; + } } const mentionUserRegex = //gi; - const accountIdToName = {}; - const accountIds = Array.from(html.matchAll(mentionUserRegex), (m) => m[1]); + const accountIdToName: Record = {}; + const accountIds = Array.from(html.matchAll(mentionUserRegex), (m) => Number(m[1])); const logins = PersonalDetailsUtils.getLoginsByAccountIDs(accountIds); - accountIds.forEach((id, index) => accountIdToName[id] = logins[index]); + accountIds.forEach((id, index) => (accountIdToName[id] = logins[index])); const parser = new ExpensiMark(); - return parser.htmlToText(html, {reportIdToName, accountIdToName}); + return parser.htmlToText(html, {reportIdToName}); +} + +/** + * Get the report action message for a report action. + */ +function getReportActionMessage(reportAction: ReportAction | EmptyObject, reportID: string, parentReportID?: string) { + if (isEmptyObject(reportAction)) { + return ''; + } + if (ReportActionsUtils.isApprovedOrSubmittedReportAction(reportAction)) { + return ReportActionsUtils.getReportActionMessageText(reportAction); + } + if (ReportActionsUtils.isReimbursementQueuedAction(reportAction)) { + return getReimbursementQueuedActionMessage(reportAction, getReport(parentReportID), false); + } + + const text = parseReportActionHtmlToText(reportAction, reportID); + return Str.removeSMSDomain(text); } /** @@ -3155,7 +3157,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } const isAttachment = ReportActionsUtils.isReportActionAttachment(!isEmptyObject(parentReportAction) ? parentReportAction : null); - const parentReportActionMessage = getReportActionMessage(parentReportAction, report?.parentReportID).replace(/(\r\n|\n|\r)/gm, ' '); + const parentReportActionMessage = getReportActionMessage(parentReportAction, report?.reportID ?? '', report?.parentReportID).replace(/(\r\n|\n|\r)/gm, ' '); if (isAttachment && parentReportActionMessage) { return `[${Localize.translateLocal('common.attachment')}]`; } From 1153dca7b46c2ff7ae3205a217c9634832d0e8ab Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Sat, 25 May 2024 08:10:24 +0700 Subject: [PATCH 3/6] Add report action message cache Signed-off-by: Tsaqif --- src/libs/ReportUtils.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 065edaca86e2..c6bf37416eec 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -487,6 +487,7 @@ let currentUserEmail: string | undefined; let currentUserPrivateDomain: string | undefined; let currentUserAccountID: number | undefined; let isAnonymousUser = false; +const reportActionMessageCache: Record = {}; const defaultAvatarBuildingIconTestID = 'SvgDefaultAvatarBuilding Icon'; @@ -3110,8 +3111,14 @@ function getReportActionMessage(reportAction: ReportAction | EmptyObject, report return getReimbursementQueuedActionMessage(reportAction, getReport(parentReportID), false); } - const text = parseReportActionHtmlToText(reportAction, reportID); - return Str.removeSMSDomain(text); + const key = `${reportAction.reportActionID}_${reportAction.lastModified}`; + const cachedMessage = reportActionMessageCache[key]; + if (cachedMessage !== undefined) { + return cachedMessage; + } + const message = Str.removeSMSDomain(parseReportActionHtmlToText(reportAction, reportID)); + reportActionMessageCache[key] = message; + return message; } /** From 4bdd2808c3955676de458a3b8d3081a4834f20ea Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Tue, 28 May 2024 08:57:08 +0700 Subject: [PATCH 4/6] Change variable names, add comment and move cache check inside parse function Signed-off-by: Tsaqif --- src/libs/ReportUtils.ts | 51 ++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c6bf37416eec..6228397d1766 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -487,7 +487,11 @@ let currentUserEmail: string | undefined; let currentUserPrivateDomain: string | undefined; let currentUserAccountID: number | undefined; let isAnonymousUser = false; -const reportActionMessageCache: Record = {}; + +// This cache is used to save parse result of report action html message into text +// to prevent unnecessary parsing when the report action is not changed/modified. +// Example case: when we need to get a report name of a thread which is dependent on a report action message. +const parsedReportActionMessageCache: Record = {}; const defaultAvatarBuildingIconTestID = 'SvgDefaultAvatarBuilding Icon'; @@ -3069,7 +3073,16 @@ function getInvoicePayerName(report: OnyxEntry): string { /** * Parse html of reportAction into text */ -function parseReportActionHtmlToText(reportAction: OnyxEntry, reportID: string): string { +function parseReportActionHtmlToText(reportAction: OnyxEntry, reportID: string, childReportID?: string): string { + if (!reportAction) { + return ''; + } + const key = `${reportID}_${reportAction.reportActionID}_${reportAction.lastModified}`; + const cachedText = parsedReportActionMessageCache[key]; + if (cachedText !== undefined) { + return cachedText; + } + const {html, text} = reportAction?.message?.[0] ?? {}; if (!html) { @@ -3079,28 +3092,31 @@ function parseReportActionHtmlToText(reportAction: OnyxEntry, repo const mentionReportRegex = //gi; const matches = html.matchAll(mentionReportRegex); - const reportIdToName: Record = {}; + const reportIDToName: Record = {}; for (const match of matches) { - if (match[1] !== reportID) { + if (match[1] !== childReportID) { // eslint-disable-next-line @typescript-eslint/no-use-before-define - reportIdToName[match[1]] = getReportName(getReport(match[1])) ?? ''; + reportIDToName[match[1]] = getReportName(getReport(match[1])) ?? ''; } } const mentionUserRegex = //gi; - const accountIdToName: Record = {}; - const accountIds = Array.from(html.matchAll(mentionUserRegex), (m) => Number(m[1])); - const logins = PersonalDetailsUtils.getLoginsByAccountIDs(accountIds); - accountIds.forEach((id, index) => (accountIdToName[id] = logins[index])); + const accountIDToName: Record = {}; + const accountIDs = Array.from(html.matchAll(mentionUserRegex), (mention) => Number(mention[1])); + const logins = PersonalDetailsUtils.getLoginsByAccountIDs(accountIDs); + accountIDs.forEach((id, index) => (accountIDToName[id] = logins[index])); const parser = new ExpensiMark(); - return parser.htmlToText(html, {reportIdToName}); + const textMessage = Str.removeSMSDomain(parser.htmlToText(html, {reportIDToName, accountIDToName})); + parsedReportActionMessageCache[key] = textMessage; + + return textMessage; } /** * Get the report action message for a report action. */ -function getReportActionMessage(reportAction: ReportAction | EmptyObject, reportID: string, parentReportID?: string) { +function getReportActionMessage(reportAction: ReportAction | EmptyObject, reportID?: string, childReportID?: string) { if (isEmptyObject(reportAction)) { return ''; } @@ -3108,17 +3124,10 @@ function getReportActionMessage(reportAction: ReportAction | EmptyObject, report return ReportActionsUtils.getReportActionMessageText(reportAction); } if (ReportActionsUtils.isReimbursementQueuedAction(reportAction)) { - return getReimbursementQueuedActionMessage(reportAction, getReport(parentReportID), false); + return getReimbursementQueuedActionMessage(reportAction, getReport(reportID), false); } - const key = `${reportAction.reportActionID}_${reportAction.lastModified}`; - const cachedMessage = reportActionMessageCache[key]; - if (cachedMessage !== undefined) { - return cachedMessage; - } - const message = Str.removeSMSDomain(parseReportActionHtmlToText(reportAction, reportID)); - reportActionMessageCache[key] = message; - return message; + return parseReportActionHtmlToText(reportAction, reportID, childReportID); } /** @@ -3164,7 +3173,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } const isAttachment = ReportActionsUtils.isReportActionAttachment(!isEmptyObject(parentReportAction) ? parentReportAction : null); - const parentReportActionMessage = getReportActionMessage(parentReportAction, report?.reportID ?? '', report?.parentReportID).replace(/(\r\n|\n|\r)/gm, ' '); + const parentReportActionMessage = getReportActionMessage(parentReportAction, report?.parentReportID, report?.reportID ?? '').replace(/(\r\n|\n|\r)/gm, ' '); if (isAttachment && parentReportActionMessage) { return `[${Localize.translateLocal('common.attachment')}]`; } From 64c8e1f49156f108c22bad5da4a01c8a9c477de6 Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Tue, 28 May 2024 10:29:14 +0700 Subject: [PATCH 5/6] Fix type checks error Signed-off-by: Tsaqif --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 6228397d1766..1b7782db3e91 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3127,7 +3127,7 @@ function getReportActionMessage(reportAction: ReportAction | EmptyObject, report return getReimbursementQueuedActionMessage(reportAction, getReport(reportID), false); } - return parseReportActionHtmlToText(reportAction, reportID, childReportID); + return parseReportActionHtmlToText(reportAction, reportID ?? "", childReportID); } /** From 969e400a43ec94f7c9c19107d9859f6e6dc93cca Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Thu, 6 Jun 2024 06:31:17 +0700 Subject: [PATCH 6/6] Fix prettier Signed-off-by: Tsaqif --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 49e580398d7b..f1f7bee340e8 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3216,7 +3216,7 @@ function getReportActionMessage(reportAction: ReportAction | EmptyObject, report return getReimbursementQueuedActionMessage(reportAction, getReport(reportID), false); } - return parseReportActionHtmlToText(reportAction, reportID ?? "", childReportID); + return parseReportActionHtmlToText(reportAction, reportID ?? '', childReportID); } /**