-
Notifications
You must be signed in to change notification settings - Fork 0
Perf/lhn options cache #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
173d127
17ee5be
c89e04a
e267744
bac0e47
832b3b6
89eb0f4
00b0a59
4149f87
a8d5ff5
71cfc0c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -92,6 +92,8 @@ | |||||
| isTaskAction, | ||||||
| isThreadParentMessage, | ||||||
| isUnapprovedAction, | ||||||
| isWhisperAction, | ||||||
| shouldReportActionBeVisible, | ||||||
| withDEWRoutedActionsArray, | ||||||
| } from '@libs/ReportActionsUtils'; | ||||||
| import {computeReportName} from '@libs/ReportNameUtils'; | ||||||
|
|
@@ -197,13 +199,13 @@ | |||||
| */ | ||||||
|
|
||||||
| let allPersonalDetails: OnyxEntry<PersonalDetailsList>; | ||||||
| Onyx.connect({ | ||||||
| Onyx.connectWithoutView({ | ||||||
| key: ONYXKEYS.PERSONAL_DETAILS_LIST, | ||||||
| callback: (value) => (allPersonalDetails = isEmptyObject(value) ? {} : value), | ||||||
| }); | ||||||
|
|
||||||
| const policies: OnyxCollection<Policy> = {}; | ||||||
| Onyx.connect({ | ||||||
| Onyx.connectWithoutView({ | ||||||
| key: ONYXKEYS.COLLECTION.POLICY, | ||||||
| callback: (policy, key) => { | ||||||
| if (!policy || !key || !policy.name) { | ||||||
|
|
@@ -215,14 +217,14 @@ | |||||
| }); | ||||||
|
|
||||||
| let allPolicies: OnyxCollection<Policy> = {}; | ||||||
| Onyx.connect({ | ||||||
| Onyx.connectWithoutView({ | ||||||
| key: ONYXKEYS.COLLECTION.POLICY, | ||||||
| waitForCollectionCallback: true, | ||||||
| callback: (val) => (allPolicies = val), | ||||||
| }); | ||||||
|
|
||||||
| let allReports: OnyxCollection<Report>; | ||||||
| Onyx.connect({ | ||||||
| Onyx.connectWithoutView({ | ||||||
| key: ONYXKEYS.COLLECTION.REPORT, | ||||||
| waitForCollectionCallback: true, | ||||||
| callback: (value) => { | ||||||
|
|
@@ -231,18 +233,98 @@ | |||||
| }); | ||||||
|
|
||||||
| let allReportNameValuePairsOnyxConnect: OnyxCollection<ReportNameValuePairs>; | ||||||
| Onyx.connect({ | ||||||
|
|
||||||
| const lastReportActions: ReportActions = {}; | ||||||
| const allSortedReportActions: Record<string, ReportAction[]> = {}; | ||||||
| let allReportActions: OnyxCollection<ReportActions>; | ||||||
| const lastVisibleReportActions: ReportActions = {}; | ||||||
| const filteredReportActionsCache: Record<string, ReportAction[]> = {}; | ||||||
| const lastMessageTextCache: Record<string, string> = {}; | ||||||
|
|
||||||
| /** | ||||||
| * Invalidates and recomputes cache entries for a specific report. | ||||||
| * This is called when report actions, metadata, or archived status changes. | ||||||
| * | ||||||
| * @param reportID - The ID of the report to invalidate cache for | ||||||
| */ | ||||||
| function invalidateCacheForReport(reportID: string) { | ||||||
| if (!reportID) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| delete lastMessageTextCache[reportID]; | ||||||
| delete filteredReportActionsCache[reportID]; | ||||||
|
|
||||||
| if (allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]) { | ||||||
| const reportActionsArray = Object.values(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? {}); | ||||||
| const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; | ||||||
| const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.chatReportID}`]; | ||||||
| const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]); | ||||||
|
|
||||||
| let sortedReportActions = getSortedReportActions(withDEWRoutedActionsArray(reportActionsArray), true); | ||||||
| if (transactionThreadReportID) { | ||||||
| const transactionThreadReportActionsArray = Object.values(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`] ?? {}); | ||||||
| sortedReportActions = getCombinedReportActions(sortedReportActions, transactionThreadReportID, transactionThreadReportActionsArray, reportID); | ||||||
| } | ||||||
| allSortedReportActions[reportID] = sortedReportActions; | ||||||
|
|
||||||
| const reportNameValuePairs = allReportNameValuePairsOnyxConnect?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`]; | ||||||
| const isReportArchived = !!reportNameValuePairs?.private_isArchived; | ||||||
| const isWriteActionAllowed = canUserPerformWriteAction(report, isReportArchived); | ||||||
|
|
||||||
| const reportActionsForDisplay = sortedReportActions.filter( | ||||||
| (reportAction) => | ||||||
| (!(isWhisperAction(reportAction) && !isReportPreviewAction(reportAction) && !isMoneyRequestAction(reportAction)) || isActionableMentionWhisper(reportAction)) && | ||||||
| shouldReportActionBeVisible(reportAction, reportAction.reportActionID, isWriteActionAllowed) && | ||||||
| reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED && | ||||||
| reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, | ||||||
| ); | ||||||
|
Comment on lines
+275
to
+281
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we could make this part more readable, or maybe explain it somehow. I'm afraid it might be difficult to maintain. Wdyt? |
||||||
| filteredReportActionsCache[reportID] = reportActionsForDisplay; | ||||||
|
|
||||||
| const reportActionForDisplay = reportActionsForDisplay.at(0); | ||||||
| if (!reportActionForDisplay) { | ||||||
| delete lastVisibleReportActions[reportID]; | ||||||
| return; | ||||||
| } | ||||||
| lastVisibleReportActions[reportID] = reportActionForDisplay; | ||||||
|
|
||||||
| const lastActorAccountID = report?.lastActorAccountID; | ||||||
| let lastActorDetails: Partial<PersonalDetails> | null = lastActorAccountID && allPersonalDetails?.[lastActorAccountID] ? allPersonalDetails[lastActorAccountID] : null; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Would that work? |
||||||
|
|
||||||
| if (!lastActorDetails && reportActionForDisplay) { | ||||||
| const lastActorDisplayName = reportActionForDisplay.person?.[0]?.text; | ||||||
| lastActorDetails = lastActorDisplayName | ||||||
| ? { | ||||||
| displayName: lastActorDisplayName, | ||||||
| accountID: lastActorAccountID, | ||||||
| } | ||||||
| : null; | ||||||
| } | ||||||
| } | ||||||
|
Comment on lines
+294
to
+303
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it a dead code? Is lastActorDetails used somewhere? |
||||||
| } | ||||||
|
|
||||||
| Onyx.connectWithoutView({ | ||||||
| key: ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, | ||||||
| waitForCollectionCallback: true, | ||||||
| callback: (value) => { | ||||||
| allReportNameValuePairsOnyxConnect = value; | ||||||
| }, | ||||||
| }); | ||||||
|
|
||||||
| const lastReportActions: ReportActions = {}; | ||||||
| const allSortedReportActions: Record<string, ReportAction[]> = {}; | ||||||
| let allReportActions: OnyxCollection<ReportActions>; | ||||||
| Onyx.connect({ | ||||||
| Onyx.connectWithoutView({ | ||||||
| key: ONYXKEYS.COLLECTION.REPORT_METADATA, | ||||||
| callback: (value, key) => { | ||||||
| if (!key) { | ||||||
| return; | ||||||
| } | ||||||
| const reportID = key.split('_').at(1); | ||||||
| if (reportID) { | ||||||
| invalidateCacheForReport(reportID); | ||||||
| } | ||||||
| }, | ||||||
| }); | ||||||
|
|
||||||
| Onyx.connectWithoutView({ | ||||||
| key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, | ||||||
| waitForCollectionCallback: true, | ||||||
| callback: (actions) => { | ||||||
|
|
@@ -265,7 +347,7 @@ | |||||
| const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; | ||||||
| const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.chatReportID}`]; | ||||||
|
|
||||||
| // If the report is a one-transaction report and has , we need to return the combined reportActions so that the LHN can display modifications | ||||||
| // If the report is a one-transaction report, we need to return the combined reportActions so that the LHN can display modifications | ||||||
| // to the transaction thread or the report itself | ||||||
| const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, actions[reportActions[0]]); | ||||||
| if (transactionThreadReportID) { | ||||||
|
|
@@ -280,12 +362,13 @@ | |||||
| } else { | ||||||
| lastReportActions[reportID] = firstReportAction; | ||||||
| } | ||||||
| invalidateCacheForReport(reportID); | ||||||
| } | ||||||
| }, | ||||||
| }); | ||||||
|
|
||||||
| let activePolicyID: OnyxEntry<string>; | ||||||
| Onyx.connect({ | ||||||
| Onyx.connectWithoutView({ | ||||||
| key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, | ||||||
| callback: (value) => (activePolicyID = value), | ||||||
| }); | ||||||
|
|
@@ -321,6 +404,27 @@ | |||||
| return personalDetailsForAccountIDs; | ||||||
| } | ||||||
|
|
||||||
| function getCachedReportActionsForDisplay(reportID: string): ReportAction[] { | ||||||
| return filteredReportActionsCache[reportID] ?? []; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Get cached last message text for a report, or compute and cache it if missing. | ||||||
| * This uses lazy computation to avoid using deprecated translateLocal in module-level Onyx.connect callbacks. | ||||||
| * | ||||||
| * @param reportID - The report ID to get cached message text for | ||||||
| * @param computeFn - Optional function to compute the message text if cache miss. If provided, result will be cached. | ||||||
| * @returns Cached message text, or undefined if cache miss and no computeFn provided | ||||||
| */ | ||||||
| function getCachedLastMessageText(reportID: string, computeFn?: () => string): string | undefined { | ||||||
| let cached = lastMessageTextCache[reportID]; | ||||||
| if (!cached && computeFn) { | ||||||
| cached = computeFn(); | ||||||
| lastMessageTextCache[reportID] = cached; | ||||||
| } | ||||||
| return cached; | ||||||
| } | ||||||
|
Comment on lines
+419
to
+426
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about empty string? Not sure if its a real issue but it would cause cache miss. Would be nice to have a unit tests for that |
||||||
|
|
||||||
| /** | ||||||
| * Return true if personal details data is ready, i.e. report list options can be created. | ||||||
| */ | ||||||
|
|
@@ -467,11 +571,11 @@ | |||||
| const formattedLastMessageTextWithPrefix = reportPrefix + formattedLastMessageText; | ||||||
|
|
||||||
| if (isExpenseThread || option.isMoneyRequestReport) { | ||||||
| return showChatPreviewLine && formattedLastMessageText ? formattedLastMessageTextWithPrefix : translateFn('iou.expense'); | ||||||
|
Check failure on line 574 in src/libs/OptionsListUtils/index.ts
|
||||||
| } | ||||||
|
|
||||||
| if (option.isThread) { | ||||||
| return showChatPreviewLine && formattedLastMessageText ? formattedLastMessageTextWithPrefix : translateFn('threads.thread'); | ||||||
|
Check failure on line 578 in src/libs/OptionsListUtils/index.ts
|
||||||
| } | ||||||
|
|
||||||
| if (option.isChatRoom && !isAdminRoom && !isAnnounceRoom) { | ||||||
|
|
@@ -483,11 +587,11 @@ | |||||
| } | ||||||
|
|
||||||
| if (option.isTaskReport) { | ||||||
| return showChatPreviewLine && formattedLastMessageText ? formattedLastMessageTextWithPrefix : translateFn('task.task'); | ||||||
|
Check failure on line 590 in src/libs/OptionsListUtils/index.ts
|
||||||
| } | ||||||
|
|
||||||
| if (isGroupChat) { | ||||||
| return showChatPreviewLine && formattedLastMessageText ? formattedLastMessageTextWithPrefix : translateFn('common.group'); | ||||||
|
Check failure on line 594 in src/libs/OptionsListUtils/index.ts
|
||||||
| } | ||||||
|
|
||||||
| return showChatPreviewLine && formattedLastMessageText | ||||||
|
|
@@ -3216,6 +3320,8 @@ | |||||
| getLastActorDisplayName, | ||||||
| getLastActorDisplayNameFromLastVisibleActions, | ||||||
| getLastMessageTextForReport, | ||||||
| getCachedReportActionsForDisplay, | ||||||
| getCachedLastMessageText, | ||||||
|
Comment on lines
+3323
to
+3324
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see usage of these 2. Is it for follow up PRs or something? |
||||||
| getManagerMcTestParticipant, | ||||||
| getMemberInviteOptions, | ||||||
| getParticipantsOption, | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one also relies on allPersonalDetails, allReports, and allReportNameValuePairsOnyxConnect. Is it intentional that the cache is invalidated only for REPORT_METADATA and REPORT_ACTIONS? Will this not introduce stale data if eg only allPersonalDetails changes?