-
Notifications
You must be signed in to change notification settings - Fork 3.4k
fix: prevent report field value from referencing itself #72375
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
base: main
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is
... and 11 files with indirect coverage changes 🚀 New features to boost your workflow:
|
|
I'll add unit test soon |
|
@dubielzyk-expensify @DylanDylann One of you needs to copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
|
Videos are looking good to me 👍 |
|
I have bumped in Slack for a C+ review |
|
Reviewing ... |
|
@NJ-2020 I get this error in the second test. Do you also have it? Screen.Recording.2025-10-27.at.17.18.51.movCan you please merge from main? and see if you still have the issue? |
|
Hey, I noticed you changed Please look at the code and make sure there are no malicious changes before running the workflow. If you have the K2 extension, you can simply click: [this button] |
@abzokhattab Done, fixed ☑️ Screen.Recording.2025-10-28.at.10.42.15.mov |
|
We just added a new formula type. Today I will check again to see if there is any conflict with our change |
|
|
||
| const hasAccountingConnections = hasAccountingConnectionsPolicyUtils(policy); | ||
| const reportField = policy?.fieldList?.[getReportFieldKey(reportFieldID)] ?? null; | ||
|
|
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.
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.
☑️
| }); | ||
| } | ||
|
|
||
| if (type === CONST.REPORT_FIELD_TYPES.TEXT && hasCircularReferences(formInitialValue, name, policy?.fieldList)) { |
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.
Also need to check hasCircularReferences for fomula type
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.
Done ☑️
| if (!fieldList || isEmptyObject(fieldList)) { | ||
| return formulaValues.some((formula) => { | ||
| const fieldPath = parsePart(formula).fieldPath; | ||
| return fieldPath.includes(fieldName); | ||
| }); | ||
| } |
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 looks redundant because we always have a default report field
src/libs/Formula.ts
Outdated
| } | ||
|
|
||
| // Get the referenced field name (first element in fieldPath) | ||
| const referencedFieldName = part.fieldPath.at(0); |
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.
| const referencedFieldName = part.fieldPath.at(0); | |
| const referencedFieldName = part.fieldPath.at(0)?.trim(); |
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.
It works if we enter {field:A} but doesn't work if we enter {field: A}
--> need to trim the value
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.
👍, Done ☑️
| if (referencedField?.defaultValue) { | ||
| // Recursively check the referenced field | ||
| if (hasCircularReferencesRecursive(referencedField.defaultValue, referencedFieldName)) { | ||
| visitedLists.delete(currentFieldName); |
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.
Why do we need to delete the value in Set?
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.
The visitedLists Set is function-scoped and short‑lived; it’s garbage‑collected after hasCircularReferences returns. @NJ-2020 Please correct me if you intend to do that for some reason?
src/libs/Formula.ts
Outdated
| /** | ||
| * Check if the report field formula value is containing circular references, e.g example: A -> A, A->B->A, A->B->C->A, etc | ||
| */ | ||
| function hasCircularReferences(fieldValue: string, fieldName: string, fieldList?: FieldList) { |
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.
Please add returned type
| function hasCircularReferences(fieldValue: string, fieldName: string, fieldList?: FieldList) { | |
| function hasCircularReferences(fieldValue: string, fieldName: string, fieldList?: FieldList): boolean { |
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, done ☑️
src/libs/Formula.ts
Outdated
| return true; | ||
| } | ||
|
|
||
| const referencedField = Object.values(fieldList).find((field) => field.name === referencedFieldName); |
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.
We can optimize this by creating a Map outside hasCircularReferencesRecursive function
const fieldsByName = new Map<string, {name: string; defaultValue: string}>(
Object.values(fieldList).map((field) => [field.name, field]),
);
and in here we should use get method
const referencedField = fieldsByName.get(referencedFieldName);
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 reduces per-lookup from O(n) to O(1).
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, done ☑️
|
@NJ-2020 Left some comment |
|
Hey, I noticed you changed If you want to automatically generate translations for other locales, an Expensify employee will have to:
Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running: npx ts-node ./scripts/generateTranslations.ts --helpTypically, you'd want to translate only what you changed by running |
|
Confirming 🇪🇸 translation here: https://expensify.slack.com/archives/C01GTK53T8Q/p1761983344629779 |
Explanation of Change
Add validation to prevent formulas from referencing themselves
Fixed Issues
$ #70775
PROPOSAL: #70775 (comment)
Tests
Initial/Default value
Self Reference
Circular Reference
2 circular/references
{field:B} or field Bis not created yet)More than 2 circular/references
{field:B} or field Bis not created yet){field:C} or field Cis not created yet)No references
{field:B}is created and the default/initial value is set to empty >""or no formula circular field)Report field value / report expense
A, A > A, A > B > AOffline tests
Same as Tests
QA Steps
Same as Tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectioncanBeMissingparam foruseOnyxtoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)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
Screen.Recording.2025-10-16.at.10.online-video-cutter.com.mp4
Android: mWeb Chrome
Screen.Recording.2025-10-16.at.09.49.55.mp4
iOS: Native
Screen.Recording.2025-10-16.at.10.44.52.mp4
iOS: mWeb Safari
Screen.Recording.2025-10-16.at.10.49.38.mp4
MacOS: Chrome / Safari
Screen.Recording.2025-10-16.at.09.25.18.mp4
MacOS: Desktop
Screen.Recording.2025-10-16.at.09.18.15.mp4