Repository navigation
Hide Expensify team members from the company card and Expensify Card assignee pickers - #101073
Conversation
Co-authored-by: Eugene Voloshchak <eVoloshchak@users.noreply.github.com>
Co-authored-by: Eugene Voloshchak <eVoloshchak@users.noreply.github.com>
|
🤖 Android test steps verification results: I landed on a signed-in Inbox, created an owned workspace, invited test@team.expensify.com, and confirmed the existing Members-list filter hides it. I could not reach the Assign card or Issue new card assignee screens: this test account has no company card feed or business bank account connected, and both setup paths are gated behind an account-validation magic code that CI cannot receive. That means the actual change in this PR — hiding Expensify team members from the cardholder pickers — is still unverified on Android.
Two things worth flagging,
The unit tests added in this PR ( view run · no recording available |
|
Fixed — Cause: two The one remaining failure is What ran locally before the push
Post-push CI: every check passes except Next Steps: Reply with view run · no recording available |
|
🤖 Web test steps verification results: Steps 1–3 verified successfully: I landed on a signed-in Inbox, created an owned workspace, invited test@team.expensify.com alongside an ordinary member, and confirmed only the ordinary member appears in the Members list. Steps 4–6 — the actual PR behavior in the "Choose the cardholder" list — were blocked: both the company card feed connection and the Expensify Card bank account connection require a "Validate your account" email magic code that this session cannot retrieve.
No console errors were observed during the driven flows. Two things worth flagging,
The unit tests added in this PR ( view run · no recording available |
|
@MelvinBot, merge the latest main |
…eamFromCardAssignees Co-authored-by: Eugene Voloshchak <eVoloshchak@users.noreply.github.com>
|
I merged the latest Is it still reproducible? Yes, based on the code. No merged PR fixes it. Current
The linked issue, [Exploratory] Company card - Can assign card to an Expensify team Members, is still open. I didn't rerun the live test. Earlier runs got blocked at the account-validation magic code before reaching the picker. CI after merge: typecheck, ESLint, and both of this PR's test files pass. One Jest shard fails in What I checked
|
|
The failing check is unrelated to this PR, so I didn't push a fix. Analysis: Evidence:
The test needs a fix on |
|
The failing check is still unrelated to this PR, so I didn't push a fix. Nothing has changed since my last check. Analysis: Evidence:
Next step: This test needs a fix on |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppscreen-20261012-195322-1791827582575.mp4Android: mWeb ChromeScreen.Recording.2026-10-12.at.18.52.19.moviOS: HybridAppScreen.Recording.2026-10-12.at.20.01.37.moviOS: mWeb SafariScreen.Recording.2026-10-12.at.18.40.20.movMacOS: Chrome / SafariScreen.Recording.2026-10-12.at.18.38.50.mov |
@MelvinBot, populate the Tests/QA Tests sections with the test steps above |
|
I added your test steps to the Tests and QA Steps sections of the PR description. Both sections now have the preconditions, steps 1–5, and the existing JS console checkbox. I didn't change anything else in the description. |
|
@codex, review |
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: be900aef2b
ℹ️ 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".
| if (shouldFilterExpensifyStaff && email !== initialAssigneeEmail && isExpensifyTeam(email)) { | ||
| continue; | ||
| } |
There was a problem hiding this comment.
Filter team accounts from the merged search results
When an admin who can invite members searches for an Expensify-team login, this guard removes it only from membersDetails; the search branch subsequently appends availableOptions.personalDetails, recentOptions, and userToInvite without applying the domain filter. Because the selector is not given these accounts in excludeLogins, the hidden account reappears and can be selected and assigned a card. The issue-new-card picker has the same gap, so filter the merged search options or hard-exclude team logins while preserving only the initial assignee.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Done in fb4dabd. Expensify team accounts no longer come back through search in either picker.
Each picker now runs one check, isHiddenExpensifyStaff, on its member list and on its merged search results. The merged results include recent chats, personal details, and the invite option. The current cardholder stays visible, same as before.
I added a search-results test to each test file. Both new tests fail without the fix and pass with it.
Checks I ran locally
npm teston both test files: 14/14 passednpm run linton all four files: passednpm run typecheck: passednpm run fmt: no extra changesnpm run spell-changeddidn't run because this checkout is shallow. CI's spellcheck covers it.
| expect(props?.data.at(0)?.value).toBe(INITIAL_ASSIGNEE); | ||
| }); | ||
|
|
||
| it('hides Expensify team members, matching the Workspace Members page', () => { |
There was a problem hiding this comment.
Add Given/When/Then documentation to the new tests
The three newly added test cases here, as well as the matching three cases in IssueNewCardAssigneeStepTest.tsx, omit the required // Given, // When, and // Then comments. Add those annotations to each new case so the tests comply with the repository's documented testing requirements.
AGENTS.md reference: AGENTS.md:L1-L3
Useful? React with 👍 / 👎.
|
The failing check is still unrelated to this PR, so I didn't push a fix. Re-running Analysis: Evidence:
|
|
The failing Analysis: Nothing has changed since my last comment. The PR branch is still at the same commit, and the failure comes from the same run. It fails only in Evidence:
|
|
The failing Next step: After that PR merges, merge Details
|
|
Nothing has changed since my last comment, so I didn't push a fix. The only failing test, Status: The fix, [NoQA] Mock fetch in `CategoryTagPickerAutoFocusTest`, is still open and unmerged. Next step: After that PR merges, merge Details
|
The member-list filter didn't cover the search branch, which appends recent reports, personal details, and the user-to-invite option. Filter the merged search options with the same check, keeping only the already-assigned cardholder. Co-authored-by: Eugene Voloshchak <eVoloshchak@users.noreply.github.com>
eVoloshchak
left a comment
There was a problem hiding this comment.
LGTM!
@MelvinBot, check off every checkbox in the PR author checklist
|
We did not find an internal engineer to review this PR, trying to assign a random engineer to #98941 as well as to this PR... Please reach out for help on Slack if no one gets assigned! |
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fb4dabd798
ℹ️ 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 shouldFilterExpensifyStaff = shouldFilterExpensifyTeam(policy?.owner, session?.email); | ||
| const isHiddenExpensifyStaff = (login: string | null | undefined) => shouldFilterExpensifyStaff && !!login && login !== initialAssigneeEmail && isExpensifyTeam(login); |
There was a problem hiding this comment.
Exempt the saved assignee from the legacy staff filter
When the existing cardholder is also the policy's assigned guide or account manager, this new exception adds them to membersDetails, but the subsequent filterGuideAndAccountManager() call removes them again. The matching Expensify Card picker has the same ordering, so editing those existing assignments still produces a list without the saved assignee, contrary to the preservation this guard is intended to provide; the downstream filter must also exempt the initial assignee.
Useful? React with 👍 / 👎.
joekaufmanexpensify
left a comment
There was a problem hiding this comment.
Good for product
| // Expensify staff (guides, support) are hidden from the Workspace Members page, so they must not be offered as | ||
| // cardholders either, whether they come from the member list or from search results. The already-assigned | ||
| // cardholder stays visible so editing an assignment doesn't blank out. |
There was a problem hiding this comment.
| // Expensify staff (guides, support) are hidden from the Workspace Members page, so they must not be offered as | |
| // cardholders either, whether they come from the member list or from search results. The already-assigned | |
| // cardholder stays visible so editing an assignment doesn't blank out. | |
| // Hide Expensify staff from member and search results, but keep the current cardholder visible while editing. |
| // Expensify staff (guides, support) are hidden from the Workspace Members page, so they must not be offered as | ||
| // cardholders either, whether they come from the member list or from search results. The already-assigned | ||
| // cardholder stays visible so editing an assignment doesn't blank out. | ||
| const shouldFilterExpensifyStaff = shouldFilterExpensifyTeam(policy?.owner, session?.email); | ||
| const isHiddenExpensifyStaff = (login: string | null | undefined) => shouldFilterExpensifyStaff && !!login && login !== initialAssigneeEmail && isExpensifyTeam(login); |
There was a problem hiding this comment.
This logic/code is repeated. Can we centralize it somewhere?
There was a problem hiding this comment.
Done in 7e147d9. I moved the logic into a new shouldHideExpensifyTeamCardholder helper in src/libs/PolicyUtils/members.ts:106, next to isExpensifyTeam and shouldFilterExpensifyTeam. Both AssigneeStep files now call it, and it has unit tests in PolicyUtilsTest.ts.
…eamFromCardAssignees Co-authored-by: Eugene Voloshchak <eVoloshchak@users.noreply.github.com> # Conflicts: # src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx # src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx # tests/ui/AssignCardAssigneeStepTest.tsx # tests/ui/IssueNewCardAssigneeStepTest.tsx
Move the duplicated isHiddenExpensifyStaff logic from both card AssigneeSteps into shouldHideExpensifyTeamCardholder and shorten the comment per review feedback. Co-authored-by: Eugene Voloshchak <eVoloshchak@users.noreply.github.com>
|
@eVoloshchak 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] |
|
Merged the latest
Checks
|
|
MelvinBot has made 6 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. |
|
@MelvinBot, ESLint check is failing in AssignCardAssigneeStepTest.tsx:5:13 and IssueNewCardAssigneeStepTest.tsx:5:13 due to
|
… assignee tests Co-authored-by: Eugene Voloshchak <eVoloshchak@users.noreply.github.com>
|
Fixed the failing ESLint check in f6a4201. The I checked the fix locally: ESLint passes on both files, |
|
🚧 MonilBhavsar 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! 🧪🧪
|
|
✋ 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/MonilBhavsar in version: 9.5.24-1 🚀
|
|
No help site update needed. This is a bug fix that hides Expensify team accounts from the company card and Expensify Card assignee lists, matching what the Members page already shows, and no help article sentence becomes incorrect. |
|
🚀 Deployed to production by https://github.com/jasperhuangg in version: 9.5.24-8 🚀
Bundle Size Analysis (Sentry): |










Explanation of Change
The Assign card → "Choose the cardholder" list was built straight from
policy.employeeListand never applied the Expensify-team domain filter that the Workspace Members page applies, so an admin could assign a company card to a member the Members page deliberately hides.Two different helpers are involved, and the card flow was using the wrong one:
WorkspaceMembersPage.tsxcallsshouldFilterExpensifyTeam+isExpensifyTeam, which match any@expensify.comor@team.expensify.comaddress.filterGuideAndAccountManager, which does exact-string comparisons againstpolicy.assignedGuide.emailand the account manager's login. There is no domain check, sotest@team.expensify.compassed through untouched.This PR applies the same domain filter the Members page uses when building
membersDetails, in both assignee pickers:src/pages/workspace/companyCards/assignCard/AssigneeStep.tsxsrc/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx— the Expensify Card "Issue new card" step had the identical gap and would issue a real card to internal staff.The already-assigned cardholder is kept visible (compared against the frozen
initialAssigneeEmail) so editing an existing assignment doesn't blank out the selection.shouldFilterExpensifyTeamalso keeps the existing behavior where Expensify-owned policies, and Expensify staff themselves, still see the full list.filterGuideAndAccountManagerwas deliberately not extended — it has many callers with different intents, and its doc comment scopes it to the guide/account-manager pair. A local guard in each picker is the smaller, more predictable change.Scope note: this hides Expensify-team accounts from the picker, which is the stated expected result. It does not stop the backend from accepting such an assignment — server-side enforcement would be a separate, internal change.
Unit tests were added to both existing test suites covering: the Expensify-team member is hidden, the already-assigned Expensify-team member stays visible, and Expensify-owned policies still show the full list.
Fixed Issues
$ #98941
PROPOSAL: #98941 (comment)
Tests
Preconditions: Create a new account with Manage My Team 1-4, and connect to the Mock Bank company card (or have an account with a workspace, that has a company card already)
Automated checks Melvin ran locally
npx eslinton all four changed files — passednpm run typecheck— passednpm run spell-changed— passed (4 files, 0 issues)npm run react-compiler-compliance-check check <both AssigneeStep files>— passednpm test tests/ui/AssignCardAssigneeStepTest.tsx tests/ui/IssueNewCardAssigneeStepTest.tsx— 12/12 passedMelvin could not verify this in a live browser session: the test session rendered a blank page (0 DOM nodes) and could not be driven, so the flows above still need manual verification.
Offline tests
QA Steps
Preconditions: Create a new account with Manage My Team 1-4, and connect to the Mock Bank company card (or have an account with a workspace, that has a company card already)
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, 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.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