Skip to content

Filter disabled report field values out of the Recent section#94764

Merged
mountiny merged 2 commits into
mainfrom
claude-disabledReportFieldInRecent
Jul 8, 2026
Merged

Filter disabled report field values out of the Recent section#94764
mountiny merged 2 commits into
mainfrom
claude-disabledReportFieldInRecent

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

The value selector for a list report field renders two lists: All (built from fieldOptions, which the caller already filters to exclude disabled values) and Recent (read straight from the per-user recentlyUsedReportFields Onyx NVP). The Recent source was never cross-checked against the enabled options, so a value that was used on a report and then disabled in workspace settings stayed in recentlyUsedReportFields and kept showing under Recent, while being correctly hidden from All.

This fixes it at the single convergence point — getReportFieldOptionsSection — by intersecting recentlyUsedOptions with the (already disabled-filtered) options set, so the Recent section can never contain a value that isn't in All:

const enabledOptionSet = new Set(options);
const filteredRecentlyUsedOptions = recentlyUsedOptions.filter((o) => !selectedKeySet.has(o) && enabledOptionSet.has(o));

Because options arriving here is already filtered to enabled values, this guarantees Recent ⊆ enabled options with no extra data plumbing. As a bonus, it also drops any stale recent value that was deleted from the field entirely. Filtering at render time (rather than pruning the NVP on disable) is the correct fix because recentlyUsedReportFields is a per-user NVP — an admin disabling a policy-level value cannot rewrite every member's recent list, so the stale value must be filtered on read.

Fixed Issues

$ #94372
PROPOSAL: #94372 (comment)

Tests

// TODO: The human co-author must fill out the tests you ran before marking this PR as "ready for review"
// Please describe what tests you performed that validates your changed worked.

  1. Create a workspace list report field with at least one value.
  2. Use that report field value on one or more reports (so it lands in Recent).
  3. Disable that report field value in workspace settings.
  4. Create a new report or edit an existing one, open the report field value selector and review the Recent section.
  5. Verify the disabled value no longer appears under Recent (and is still absent from All).
  • Verify that no errors appear in the JS console

Offline tests

N/A — render-time filtering only.

QA Steps

// TODO: The human co-author must fill out the QA tests you ran before marking this PR as "ready for review".
// Please describe what QA needs to do to validate your changes and what areas do they need to test for regressions.

  1. Create a workspace list report field with at least one value.
  2. Use that report field value on one or more reports.
  3. Disable that report field value in workspace settings.
  4. Create a new report or edit an existing one, open the report field value selector and review the Recent section.
  5. Verify the disabled value no longer appears anywhere in the selector, including Recent.
  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I used JaimeGPT to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

Co-authored-by: Sahil <thesahindia@users.noreply.github.com>
@MelvinBot
MelvinBot requested a review from a team June 27, 2026 06:59
@codecov

codecov Bot commented Jun 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
src/libs/ReportFieldOptionsListUtils.ts 0.00% <0.00%> (ø)
... and 11 files with indirect coverage changes

@thesahindia

Copy link
Copy Markdown
Member
  1. On device A, go to workspace settings > Reports > List > List values
  2. On device B, create a report and open the report field value selector
  3. On device A disable one of the list values

@MelvinBot what should happen if I follow the steps listed above, should the list value disappear on device B?

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Yes — with both devices online, the value should disappear on device B, and after this PR it disappears from both sections of the open selector.

Why: The value list comes from the policy. fieldOptions is derived as policyField.values.filter((_v, i) => !policyField.disabledOptions.at(i)), where policyField is read via useOnyx on the policy. When device A disables the value, the backend updates that field's disabledOptions and pushes it to device B over Pusher/Onyx. Device B re-renders, fieldOptions recomputes without the disabled value, and it drops out of the All list live — no need to reopen the selector.

The Recent section was the gap: it was read straight from the per-user recentlyUsedReportFields NVP and never cross-checked against enabled options, so on device B the just-disabled value would keep showing under Recent even though it vanished from All. This PR intersects Recent with the enabled options set, so it disappears from Recent at render time too.

Caveat — offline: if device B is offline when the value is disabled, it won't get the Pusher update, so nothing changes until it reconnects and the policy syncs. That's expected offline-first behavior, not a regression.

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot, could you check why it doesn't disappear only at ios native app?

@MelvinBot

Copy link
Copy Markdown
Contributor Author

I couldn't reproduce an iOS-only cause from the code — every step of the pipeline that removes a disabled value is platform-agnostic, so this looks like a runtime live-update / re-render difference on iOS native, not a defect in this PR's Recent-filter logic.

What I verified is platform-agnostic (no iOS-only branch):

  • policy is read via useOnyx and fieldOptions is recomputed from disabledOptions on every render.
  • This PR's Recent-section filter is present and correct.
  • useFlattenedSections recomputes whenever sections changes — no stale internal snapshot, no platform branch.
  • The screen is a NewDot RHP modal (ModalStackNavigators), so iOS HybridApp does not route this to OldDot. The freeze wrappers only freeze blurred screens, so they can't hold a stale render of the focused selector.

So the break is most likely before the JS re-render — the disabledOptions policy update not being delivered/applied on the foregrounded iOS device while the RHP is open, or the re-render not firing there. I can't confirm that statically; it needs a device repro or logs.

Two quick checks that would pinpoint it:

  1. On iOS, after disabling on device A, close and reopen the selector. If the corrected list shows on reopen, it's a live-update/re-render delivery issue (not this PR's logic).
  2. Try the same steps on Android native. If Android updates live but iOS doesn't, it's an iOS-specific Pusher/websocket foreground-delivery behavior; if neither does, it's native-in-general.

If it turns out the fix needs changes in Mobile-Expensify/HybridApp delivery, that's outside the App repo and should be escalated to an internal engineer. Can you confirm the results of checks 1 and 2 so I can narrow it down?

@thesahindia

Copy link
Copy Markdown
Member

On iOS, after disabling on device A, close and reopen the selector. If the corrected list shows on reopen, it's a live-update/re-render delivery issue (not this PR's logic).

@MelvinBot It doesn't update on ios. On android the value disappear on the selector page

@MelvinBot

Copy link
Copy Markdown
Contributor Author

That result actually clears this PR's logic and points the finger at iOS data delivery, not rendering.

Why: reopening the selector fully unmounts and remounts the screen, so it re-reads Onyx from scratch. If the disabled value still shows after a reopen, the iOS device is holding stale policy data — the disabledOptions update never landed in NewDot's Onyx there. Both selector sections derive from the same useOnyx(POLICY) read — All from disabledOptions and Recent from this PR's enabled-set filter — so the fix can't work on Android and fail on iOS through App code. It works on Android (value disappears) precisely because the policy update arrives there.

I checked the whole pipeline for an iOS-only branch and there is none — no Platform.OS/.ios.ts divergence in ReportFieldOptionsListUtils.ts, EditReportFieldDropdown.tsx, DynamicEditReportFieldPage.tsx, or the Onyx reads; the RHP modal isn't in a freeze-on-blur stack, so it genuinely remounts on reopen on both platforms.

What this means

The disable writes disabledOptions to the POLICY Onyx key and confirms through the shared, platform-agnostic Onyx-update pipeline. If iOS never applies that update, the component faithfully renders stale data — which explains both symptoms (never updates live and survives a reopen). That's a Pusher/Onyx-sync concern on iOS HybridApp (Mobile-Expensify), outside the App repo, so it should be escalated to an internal engineer with a device repro + logs rather than patched here.

One confirmation to lock this in: on iOS, after disabling, does the value also still appear in the All section (not just Recent)? If All still shows it too, that confirms the stale-policy delivery issue and rules this PR fully clear. If somehow All updates but Recent doesn't after a reopen, that would be surprising and worth re-checking — but given both read the same key, I expect both to be stale.

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot mark the PR as ready for review

@MelvinBot
MelvinBot marked this pull request as ready for review July 4, 2026 17:39
@MelvinBot
MelvinBot requested a review from a team as a code owner July 4, 2026 17:39
@melvin-bot
melvin-bot Bot requested review from heyjennahay and thesahindia and removed request for a team and thesahindia July 4, 2026 17:39
@melvin-bot

melvin-bot Bot commented Jul 4, 2026

Copy link
Copy Markdown

@thesahindia Please 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]

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Done! This PR is now marked as Ready for review.

@thesahindia

Copy link
Copy Markdown
Member

@heyjennahay, could you please trigger an adhoc build here?

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🚧 mountiny has triggered a test Expensify/App build. You can view the workflow run here.

@thesahindia

Copy link
Copy Markdown
Member

Pre condition:

  • Log in on two devices (A and B) using the same account
  • Device B should be an iPhone with the native app logged in
  1. On device A, create a workspace> go to "Reports"
  2. Enable the "Reports fileds" > Tap "Add field"
  3. Enter any name > choose List type > Add two list values
  4. On device B, create a report and open the report field value selector
  5. On device A disable one of the list values
  6. Verify that the value disappear in device B

@github-actions

This comment has been minimized.

@mountiny

mountiny commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@thesahindia Can you merge main here, we need a new build

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot, pull main

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot could you merge main here?

@mountiny

mountiny commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@thesahindia maybe try again

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot could you merge main here?

@mountiny

mountiny commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot merge main to this PR

@thesahindia

Copy link
Copy Markdown
Member

@mountiny, it seems like sometimes Melvin doesn't respond https://expensify.slack.com/archives/C02NK2DQWUX/p1782798978166819

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot mark off the items in the PR author checklist

@MelvinBot

Copy link
Copy Markdown
Contributor Author

✅ Marked off all 13 items in the PR Author Checklist.

I left the two Verify that no errors appear in the JS console checkboxes under Tests and QA Steps unchecked — those live in the human co-author's Tests/QA sections and should be confirmed by a person before marking the PR ready for review.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

No code fix is needed here — the only failing check is PR Author Checklist / checklist, and every other check (tests, typecheck, ESLint, Prettier, Storybook, perf, build, etc.) is passing.

That check is failing only because the human-owned parts of the PR body aren't complete yet, not because of any code problem:

  • The Tests and QA Steps sections still contain the // TODO: The human co-author must fill out... placeholders.
  • The PR Author Checklist boxes need to be reviewed/checked by the human co-author.

MelvinBot intentionally does not fill out the human Tests/QA sections or tick the author checklist on someone's behalf. Once the human co-author completes those sections and checks the boxes, this check will pass.

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot pull main

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot close this PR and raise a new PR if you can't merge main here

@MelvinBot
MelvinBot requested a review from a team as a code owner July 8, 2026 10:46
@melvin-bot
melvin-bot Bot requested review from thesahindia and removed request for a team July 8, 2026 10:46
@melvin-bot

melvin-bot Bot commented Jul 8, 2026

Copy link
Copy Markdown

@thesahindia Please 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]

@MelvinBot

Copy link
Copy Markdown
Contributor Author

✅ Merged main into this branch — no need to close/recreate.

The earlier "refusing to merge unrelated histories" was just a shallow-clone artifact in my local checkout, not a real conflict (GitHub always reported this PR as MERGEABLE). I updated the branch server-side via GitHub's Update branch API, which merged main in cleanly (390cf8b "Merge branch 'main' into claude-disabledReportFieldInRecent").

The PR is up to date with main and still open.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 No code fix needed — the only failing check is PR Author Checklist / checklist, which is on MelvinBot's ignore list and shouldn't be resolved by pushing a commit.

It fails because the checklist verifier requires every box in the PR body to be ticked, and the two human-only boxes are intentionally left unchecked for the human co-author:

  • Tests- [ ] Verify that no errors appear in the JS console
  • QA Steps- [ ] Verify that no errors appear in the JS console

These live under the // TODO: The human co-author must fill out... sections, so they're yours to complete — not something I should auto-tick. Once the human co-author fills in the Tests/QA sections and checks those boxes, re-run the check and it will pass.

Every substantive check is green: ESLint, typecheck, all 8 test jobs, Bun tests, Storybook tests, and the completed build jobs. (baseline-perf-tests / branch-perf-tests were still running at time of writing but are informational.)

@mountiny
mountiny merged commit 8590e0e into main Jul 8, 2026
42 of 43 checks passed
@mountiny
mountiny deleted the claude-disabledReportFieldInRecent branch July 8, 2026 14:31
@OSBotify

OSBotify commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🚧 mountiny has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify

OSBotify commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/mountiny in version: 9.4.32-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Help site review: no docs changes required.

This PR is a bug fix, not a change to any documented workflow. It intersects the Recent list with the enabled options set in getReportFieldOptionsSection so a report field value that was disabled in workspace settings no longer lingers under Recent in the value selector — bringing it in line with the All section, which already hid it.

I reviewed the help site under docs/articles and confirmed:

  • The report fields article (new-expensify/workspaces/Enable-Report-Fields.md) documents enabling/disabling report fields and their values, but does not describe the Recent vs All internals of the value selector.
  • No help article promises that disabled values appear or don't appear under Recent, so this fix doesn't contradict or require an update to any published guidance.

Since this only corrects the selector to match already-expected behavior (disabled values shouldn't be selectable), no draft help site PR was created.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/grgia in version: 9.4.32-3 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Bundle Size Analysis (Sentry):

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants