Fix home Awaiting approval count to exclude IOU and personal-policy expenses#91847
Conversation
…xpenses The Home widget search query for "Awaiting approval" used type:expense which the backend expands to include IOU reports. Scope the query to the user's policies that pass hasApprovalFlow by adding a policyID filter, matching the same predicate the row's visibility gate already uses. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@ZhenjaHorbach 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] |
|
@codex review |
There was a problem hiding this comment.
Pull request overview
This PR fixes the Home “Awaiting approval” widget so its count/total only reflects outstanding reimbursable expenses from workspaces that actually have an approval flow, excluding IOU (DM) expenses and personal-policy expenses. It aligns the widget’s underlying Search query with the same policy applicability predicate already used to decide whether the row should be shown.
Changes:
- Updated
buildAwaitingApprovalQueryto optionally scope results bypolicyID:[...]when approval-flow workspace IDs are available. - Refactored
getYourSpendApplicabilityto compute a sortedapprovalPolicyIDslist (stable hash) alongside approval/payment applicability. - Added/updated unit tests to cover the new query scoping behavior and the sorted policy ID plumbing through
useYourSpendData.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
src/pages/home/YourSpendSection/queries.ts |
Adds optional policyID filter to the awaiting-approval Search query. |
src/pages/home/YourSpendSection/useYourSpendData.ts |
Computes sorted approval-policy IDs and uses them to build the awaiting-approval query + effect dependency key. |
tests/unit/Search/yourSpendQueryBuildersTest.ts |
Adds coverage for policyID emission/omission and hash changes when scoped. |
tests/unit/Search/yourSpendApplicabilityTest.ts |
Adds coverage for approvalPolicyIDs derivation/sorting and applicability behavior. |
tests/unit/HomePage/YourSpendSection/useYourSpendDataTest.ts |
Verifies the hook passes the expected sorted policy IDs into the query builder. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Codex Review: Didn't find any major issues. You're on a roll. ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
Replace the hasApprovalFlow predicate with isGroupPolicy so the policyID filter includes any non-personal workspace (Team/Corporate/Submit). The count now matches the user's mental model: OUTSTANDING reports they own on a group workspace, regardless of whether that workspace has an approval workflow configured. Rename approvalPolicyIDs to groupPolicyIDs to reflect the broader predicate, and update tests accordingly. Co-authored-by: Cursor <cursoragent@cursor.com>
Switch the predicate from isGroupPolicy back to isPaidGroupPolicy so the policyID filter only includes Team and Corporate workspaces. Submit workspaces are intentionally excluded from this PR even though they default to ADVANCED approval mode — to be revisited separately once the UX has been confirmed. Rename groupPolicyIDs to paidGroupPolicyIDs to reflect the tighter scope, and update the applicability and hook tests accordingly. The paymentRowState tests now explicitly opt the corporate fixture into isPaidGroupPolicy since the helper is mocked at the module level. Co-authored-by: Cursor <cursoragent@cursor.com>
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
The sort was defensive against unrelated Onyx mutations rearranging the policy collection, but Onyx preserves insertion order in practice, so the snapshot key churn it was guarding against doesn't happen. Trim the verbose comments on YourSpendApplicability, the applicabilityKey, and the policyID filter in queries.ts down to one-liners in plain English. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: de47e2b6f5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
When the user's paid-workspace set changed, the approval search query hash changed but cachedApprovalReady wasn't keyed by hash, so a fresh snapshot with count still missing would briefly fall back to the previous workspace set's total. Cache the hash alongside the totals and only reuse the cache when the current query hash matches. Add a regression test that fails without the fix. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@MelvinBot Please review the PR |
Co-authored-by: Cursor <cursoragent@cursor.com>
PR ReviewThe fix is well-motivated and the approach (scoping by 1.
|
Replace the {hash, totals} wrapper with the React-recommended
conditional-setState-in-render pattern: store the cached hash
alongside the totals state and reset both when approvalHash
changes. Matches the existing setState-in-render style this
hook already uses for the cache writes and keeps the consume
sites untouched.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Okay
@mountiny |
|
Sorry the comment is not loading for me, can you repost? |
|
I will update the steps, I think the widget should not be visible when there are no reports pending approval |
Good choice 😁 |
|
But actually no 2026-06-01.14.27.55.mov |
|
@ZhenjaHorbach updated |
@mountiny |
A zero-result awaiting-approval search returns no count (undefined, not 0), so the approval cache kept showing a stale total after the user approved their last outstanding expense. Gate the cache on the outstanding-reports signature so the row hides immediately once nothing is awaiting approval. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@ZhenjaHorbach good catch, this is now fixed in f4fb146. Root cause: a zero-result awaiting-approval search comes back from Auth with Fix: gate the cache reuse on the |
|
Okay |
|
LGTM! |
|
@mjasikowski 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] |
|
🚧 @mjasikowski has triggered a test Expensify/App build. You can view the workflow run here. |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/mjasikowski in version: 9.3.99-0 🚀
Bundle Size Analysis (Sentry): |
Help site review: no changes requiredI reviewed the changes in this PR against Why:
Since no documented behavior changed and the widget itself is undocumented, I did not create a draft docs PR. |
|
That one is unrelated to this pr |
|
🚀 Deployed to production by https://github.com/lakchote in version: 9.3.99-9 🚀
|


Explanation of Change
The Home page "Awaiting approval" widget was counting IOU expenses (DM money requests) and personal-policy expenses in its total/count. Repro:
Root cause: the awaiting-approval query used
type:expense, which the Auth backend expands to include IOU reports.Fix: scope the query to the user's paid group workspaces (Team/Corporate) by adding a
policyID:[...]filter, which excludes IOU reports (nopolicyID) and personal-policy expenses.policyIDis the Workspace chip in the Search UI, so tapping the row opens a Search page with that chip pre-filled and a result set that matches the count.Implementation details:
getYourSpendApplicabilityreturns the user'spaidGroupPolicyIDsalongside the existing approval/payment applicability flags.buildAwaitingApprovalQuerytakes those IDs and emits thepolicyID:filter when non-empty.The row still only renders when the scoped search returns a non-zero count (
READY); zero outstanding workspace expenses keep the row hidden (HIDDEN_EMPTY).Fixed Issues
$ #91392
PROPOSAL: Internal fix — no external proposal
Tests
Automated coverage:
npx jest tests/unit/Search/yourSpendQueryBuildersTest.ts tests/unit/Search/yourSpendApplicabilityTest.ts tests/unit/HomePage/YourSpendSection/useYourSpendDataTest.ts(77 tests pass, including the newpolicyIDandpaidGroupPolicyIDscases).Verify that no errors appear in the JS console
Offline tests
N/A — no offline-specific behavior changed. The widget already short-circuits the search when offline and reuses cached totals (existing behavior unchanged by this PR).
QA Steps
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand 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: HybridApp
Android: mWeb Chrome
iOS: HybridApp
iOS: mWeb Safari
MacOS: Chrome / Safari