perf: create reportAttributes derived value#58476
perf: create reportAttributes derived value#58476mjasikowski merged 33 commits intoExpensify:mainfrom
Conversation
…attributes-derived-value
…attributes-derived-value
…tributes-derived-value
…attributes-derived-value
|
Tests are fixed, Typescript check should be green once we merge Onyx bump from #59191 |
|
@hoangzinh I addressed the comments, just had some problems with writing tests for the |
|
Thanks @TMisiukiewicz. Can you take this last comment please #58476 (comment) |
| Log.info(`[OnyxDerived] value for key ${key} changed, updating it in Onyx`, false, {old: derivedValue ?? null, new: newDerivedValue ?? null}); | ||
| derivedValue = newDerivedValue; | ||
| Onyx.set(key, derivedValue ?? null); | ||
| const recomputeDerivedValue = (sourceKey?: string, sourceValue?: unknown) => { |
There was a problem hiding this comment.
Previously, the compute function received only the current derived value as its second parameter. Now it gets a context object that includes sourceValue, keyed by the dependency that triggered the change. This allows tracking which specific dependency caused the recomputation.
This unlocks smarter downstream logic - compute can selectively recompute only affected items instead of the entire collection, like reportAttributes already does.
This behavior only applies to collections with waitForCollectionCallback: true. For non-collection dependencies or those without the flag, sourceValue is undefined since it’s always equal to the current value anyway
src/libs/ReportUtils.ts
Outdated
| // Basic report name is generated in derived value, so if only report was passed to the function and it's cached, we can immediately return the cached value | ||
| if (report && !policy && !parentReportActionParam && !personalDetails && !invoiceReceiverPolicy && reportAttributes?.[report.reportID]) { |
There was a problem hiding this comment.
Yeah good idea 👍
|
|
||
| /** | ||
| * This derived value is used to get the report attributes for the report. | ||
| * Dependency on ONYXKEYS.PERSONAL_DETAILS_LIST is to ensure that the report attributes are generated after the personal details are available. |
There was a problem hiding this comment.
Some report names are generated based on information stored in personal details - this ensures we calculate them only if this information is available so we do not miss anything
|
@hoangzinh huh, I think all my comments were not visible to you before? I just noticed all of them have status |
Reviewer Checklist
Screenshots/VideosAndroid: NativeScreen.Recording.2025-04-14.at.21.59.11.android.movAndroid: mWeb ChromeScreen.Recording.2025-04-14.at.22.01.04.android.chrome.moviOS: NativeScreen.Recording.2025-04-14.at.22.05.34.ios.moviOS: mWeb SafariScreen.Recording.2025-04-14.at.22.03.47.ios.safari.movMacOS: Chrome / SafariScreen.Recording.2025-04-14.at.20.42.03.web.movMacOS: DesktopScreen.Recording.2025-04-14.at.20.47.08.desktop.mov |
|
@TMisiukiewicz @mountiny, with derived value reportName, should we clean up the existing cache mechanism? Or should we leave it there? Lines 4570 to 4578 in 604acb6 |
|
@TMisiukiewicz It seems we have to invalidate/recalculate the cache when the language is changed. Screen.Recording.2025-04-11.at.22.25.34.mov |
yeah seems like it's now redundant, removed it |
@hoangzinh I've updated the dependencies to recalculate on locale change as well |
|
All yours @mjasikowski |
mjasikowski
left a comment
There was a problem hiding this comment.
Just a small NAB comment
| type DerivedValueContext<Key extends OnyxKey, Deps extends NonEmptyTuple<Exclude<OnyxKey, Key>>> = { | ||
| currentValue?: OnyxValue<Key>; | ||
| sourceValues?: { | ||
| [K in Deps[number] as K extends OnyxCollectionKey ? K : never]?: K extends keyof OnyxCollectionValuesMapping ? OnyxCollection<OnyxCollectionValuesMapping[K]> : never; | ||
| }; | ||
| }; | ||
|
|
There was a problem hiding this comment.
NAB, but is there any way to simplify this by creating helper types, with Partial and/or adding a comment? I'm getting old and I would have to whip out my notepad to solve the boolean logic to get the resulting type right now.
There was a problem hiding this comment.
thanks for your comment, mind checking if it is easier to read now?
There was a problem hiding this comment.
thanks @TMisiukiewicz - it still borders on arcane, but is way easier to understand what's going on. Thank you!
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚀 Deployed to staging by https://github.com/mjasikowski in version: 9.1.29-0 🚀
|
|
🚀 Deployed to production by https://github.com/marcaaron in version: 9.1.29-10 🚀
|
Explanation of Change
Create
reportAttributesderived value to store report-specific attributes and improve performance of heavy calculations.Before:

getReportNamewas calculated each timegetOrderedReportIDswas called. For sending 5 messages on chat, it took 2.5s in total to generate report namesAfter:

report name is called once when the app is being opened, and then whenever report changes, it re-generates only it's name instead of generating all. Updating a single report name took 10ms and reading it 5 times was 24ms in total.
Fixed Issues
$ #59350
PROPOSAL:
Tests
Offline tests
QA Steps
// TODO: These must be filled out, or the issue title must include "[No QA]."
Same as tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)myBool && <MyComponent />.src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
android.mov
Android: mWeb Chrome
iOS: Native
ios.mp4
iOS: mWeb Safari
MacOS: Chrome / Safari
web.mov
MacOS: Desktop
desktop.mov