-
Notifications
You must be signed in to change notification settings - Fork 3.1k
perf: create reportAttributes derived value #58476
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
perf: create reportAttributes derived value #58476
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
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.
LGTM
All yours @mjasikowski |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for your comment, mind checking if it is easier to read now?
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.
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
reportAttributes
derived value to store report-specific attributes and improve performance of heavy calculations.Before:

getReportName
was calculated each timegetOrderedReportIDs
was 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 Issues
section aboveTests
sectionOffline steps
sectionQA steps
sectiontoggleReport
and notonIconClick
)myBool && <MyComponent />
.src/languages/*
files and using the translation methodSTYLE.md
) were followedAvatar
, I verified the components usingAvatar
are working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
)Avatar
is modified, I verified thatAvatar
is working as expected in all cases)Design
label and/or tagged@Expensify/design
so the design team can review the changes.ScrollView
component to make it scrollable when more elements are added to the page.main
branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTest
steps.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