Repository navigation
Show the cardholder in From for deleted card expenses - #103903
Conversation
Co-authored-by: Manan <mananjadhav@users.noreply.github.com>
|
I created this PR from an upstream branch since I don't have push access to your fork. To take ownership of this branch and be able to push updates, run: Then you can close this PR and open a new one from your fork. |
|
Reviewing the PR. |
|
🤖 Android test steps verification results: The PR's Tests section is still a TODO, so I ran the repro steps from the linked issue. Workspace setup and the member invite worked. Adding a company card stopped at an account validation screen that needs a magic code from email, so steps 4–11 were blocked and the From column fix was not exercised.
Steps 4–11 are marked ❌ because they couldn't run. They don't show that the PR fails. To test the fix, use an account that is already validated and has a company card feed assigned to a member. |
|
🤖 Web test steps verification results: I signed in as the workspace owner. Inviting a member opened an "Add payment card" modal that ignored every click, so steps 2–11 were blocked and the From column fix was not exercised.
Steps 2–11 are marked ❌ because they couldn't run. They don't show that the PR fails. The stuck modal looks like a problem with the test browser, not this PR. To test the fix, use an account with a payment card on file and a company card feed already assigned to a member. |
| const from = fromAccountID ? (personalDetailsMap.get(fromAccountID.toString()) ?? emptyPersonalDetails) : emptyPersonalDetails; | ||
| // Deleted expenses have no report or IOU action, so fall back to the holder of the card they were charged to | ||
| const cardHolderAccountID = transactionItem.cardID ? cardList?.[transactionItem.cardID]?.accountID : undefined; | ||
| const fromAccountID = reportAction?.actorAccountID ?? report?.ownerAccountID ?? cardHolderAccountID; |
There was a problem hiding this comment.
@MelvinBot The cardholder fallback runs for every row that has no IOU action and no report owner, not only deleted rows. An unreported card expense used to render a blank From and will now show the cardholder. It also reads the card's current assignee, so a reassigned card shows the new holder on an old deleted expense.
|
|
||
| // The ref stops a failure from looping, since the loading flag clears on failure too. A later mount retries. | ||
| if (isOffline || areCardsLoaded || hasRequestedCardDataRef.current) { | ||
| if (!shouldLoad || isOffline || areCardsLoaded || hasRequestedCardDataRef.current) { |
There was a problem hiding this comment.
@MelvinBot isLoadingCards sets hasRequestedCardDataRef before shouldLoad is checked. On a normal Spend view this hook is mounted with shouldLoad false. Opening the card filter or selecting a row sets isLoadingCards, and this effect treats that as its own request. If that request fails, filtering to Deleted later does not call openSearchCardFiltersPage again, so the cardholder never loads and From stays blank. Return when shouldLoad is false before writing the ref.
Unreported card expenses also have no report or IOU action, so the fallback was changing their From too. Co-authored-by: Manan <mananjadhav@users.noreply.github.com>
|
Thanks, 1. Fallback now applies only to deleted rows (fixed in dac05bc). The cardholder is now used only when 2. Reassigned cards. App has no action that changes the holder on an existing card. Unassigning a company card removes that Checks: Search unit and UI tests (77 suites, 2,182 tests), |
|
The code changes are fine. Working on the checklist now. Also adding the test steps and testing it out. |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppiOS: HybridAppios-delete-exp-from-user.movMacOS: Chrome / Safariweb-from-deleted-card-expense.mov |
mananjadhav
left a comment
There was a problem hiding this comment.
Approving based on the current scope and testing. Asked a question here about the Native/Mobile behavior.
Tests
Offline tests
QA StepsSame as tests. Use a wide browser window. The phone layout does not show a From column for deleted expenses. @arosiclair Can you add the test steps to the PR body? I don't have the access to update the PR body. |
|
@MelvinBot mark this PR ready for review |
|
🤖 Done. This PR is now marked ready for review. |
|
@mananjadhav 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 SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dac05bc659
ℹ️ 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".
| const isDeletedStatusQuery = !statusFilter.isNegated && !!statusFilter.value?.includes(CONST.SEARCH.STATUS.EXPENSE.DELETED); | ||
| useLoadSearchCardData(hasDeletedTransaction || isDeletedStatusQuery); |
There was a problem hiding this comment.
Load cards for negated grouped status searches
When a grouped expense query uses a negated status filter that still admits deleted rows, such as status!:drafts, isDeletedStatusQuery is forced to false. Grouped stage-one snapshots contain only group containers, so hasDeletedTransaction also remains false even after a child snapshot supplies a deleted transaction. Consequently useLoadSearchCardData never requests the complete card list and the newly added cardholder fallback still renders From blank for those deleted company-card expenses. Treat any status predicate that can include deleted expenses as requiring card data, or propagate the deleted flag from grouped child snapshots.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch, mananjadhav. This one is valid, and I fixed it in c69501a.
A negated status filter now loads the full card list unless it also excludes deleted. So status!:drafts loads cards, and status!:deleted doesn't. See src/components/Search/index.tsx:328-331.
This matches how App already treats negated filters. isEligibleForStatus counts a trashed expense as a match for any negated status that doesn't exclude deleted.
Checks: Search unit and UI tests passed (77 suites, 2,182 tests), and so did npm run typecheck. ESLint and the React Compiler check show only the errors already on that file before this change.
Your earlier comment on useLoadSearchCardData.ts (the ref is set while shouldLoad is false) still has no reply or fix. Tag me there if you want it fixed too.
Co-authored-by: Manan <mananjadhav@users.noreply.github.com>
|
@mananjadhav 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 We've fixed the From only for the Wide layout. We now want to show |
Co-authored-by: Manan <mananjadhav@users.noreply.github.com>
|
🤖 Pushed 15267be. Deleted expenses now show From on mobile web and native too. The narrow row hid the user info for every deleted expense, so I removed that check at Checks: a new test in I didn't run an Android test. The test account can't add a company card, so it can't create a deleted card expense. Please update the QA note in your test steps, which says the phone layout has no From for deleted expenses. |
|
I updated the test steps in the description and removed the part about only checking the wide layout. Let me know when this is ready for review |
|
Testing out the latest changes. Will upload the checklist soon. |
mananjadhav
left a comment
There was a problem hiding this comment.
Getting an error on Android build but tested on other platforms. Posted an explanation about the From field on the mobile view.
|
@arosiclair friendly bump here. |
Co-authored-by: Manan <mananjadhav@users.noreply.github.com>
|
MelvinBot has made 5 consecutive commits trying to fix CI failures. Automatic CI fix dispatches are now paused for this PR. Next steps: A human should review the recent commits and resolve the conflicting CI errors manually. |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚧 arosiclair has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/arosiclair in version: 9.5.21-0 🚀
|
|
No help site update needed. This is a bug fix: deleted card expenses on Spend > Expenses now show the cardholder in From, which is what you'd expect, and no help article says otherwise. |
|
🚀 Deployed to staging by https://github.com/arosiclair in version: 9.5.23-0 🚀
|
|
No help site update needed. This is a bug fix: deleted card expenses on Spend > Expenses now show the cardholder in From, which is what you'd expect, and no help article says otherwise. |








Explanation of Change
When a card expense is deleted, it's report is set to -1 and it is no longer linked to an IOU action. The spend page determines the transaction owner using these properties, so it displays with no owner in this state. This PR updates the spend page to use the owner of the linked card instead (this is actually the canonical way to determine the owner of a transaction).
AI Tests (run by MelvinBot)
npm test -- tests/unit/Search tests/ui/Search: 77 suites, 2,181 tests passed, including 4 new tests inSearchUIUtilsTest.ts.npm run typecheck: passed.npm run linton the changed files: passed.npm run fmt: no extra changes.react-compiler-compliance-check:useSearchSnapshot.tsanduseLoadSearchCardData.tspass.Search/index.tsxreports the same 9 (Babel) / 10 (OXC) errors onmain, so they're pre-existing and not from this change.Fixed Issues
$ #103480
PROPOSAL: #103480 (comment)
Tests
Offline tests
QA Steps
Same as tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, I verified the components usingAvatarare working as expected)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.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: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari