refactor(FR-3019): migrate Purge/Update user modals to UserV2 fragment#7673
refactor(FR-3019): migrate Purge/Update user modals to UserV2 fragment#7673ironAiken2 wants to merge 12 commits into
Conversation
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has required the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
There was a problem hiding this comment.
Pull request overview
This PR completes the FR-3019 follow-up by migrating the super-admin Purge Users and Update Users modals to consume v2 UserV2 data via Relay plural fragments, now that AdminUserManagement is already using the v2 adminUsersV2 query (from the stacked FR-3016 work).
Changes:
- Updated
PurgeUsersModalandUpdateUsersModalto acceptusersFrgmt(pluralUserV2fragment refs) and read{ id, basicInfo.email }viauseFragment. - Updated
AdminUserManagementto spread the new modal fragments onadminUsersV2.edges.nodeand pass fragment refs directly (removing the old{id,email}mapping). - Added Relay generated fragment artifacts for the two new modal fragments and updated the query artifact accordingly.
Reviewed changes
Copilot reviewed 3 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| react/src/components/UpdateUsersModal.tsx | Replaces {id,email}[] prop with plural UserV2 fragment refs and reads basicInfo.email via useFragment. |
| react/src/components/PurgeUsersModal.tsx | Replaces {id,email}[] prop with plural UserV2 fragment refs and uses fragment data for both legacy and bulk purge paths. |
| react/src/components/AdminUserManagement.tsx | Spreads new modal fragments on adminUsersV2 nodes and passes node fragment refs to the modals (removes local {id,email} mapper). |
| react/src/generated/UpdateUsersModalFragment.graphql.ts | New generated Relay fragment artifact for UpdateUsersModal (plural UserV2). |
| react/src/generated/PurgeUsersModalFragment.graphql.ts | New generated Relay fragment artifact for PurgeUsersModal (plural UserV2). |
| react/src/generated/AdminUserManagementQuery.graphql.ts | Updates generated query artifact to include the two new fragment spreads. |
Files not reviewed (3)
- react/src/generated/AdminUserManagementQuery.graphql.ts: Language not supported
- react/src/generated/PurgeUsersModalFragment.graphql.ts: Language not supported
- react/src/generated/UpdateUsersModalFragment.graphql.ts: Language not supported
3513119 to
c30de63
Compare
b51e16a to
afdd6ac
Compare
afdd6ac to
e49ea63
Compare
yomybaby
left a comment
There was a problem hiding this comment.
Please resolve merg conflicts
e95ab4b to
c1937b2
Compare
74a2898 to
f13ac5a
Compare
f13ac5a to
f0a56b8
Compare
|
@yomybaby Rebased this branch onto the latest |
agatha197
left a comment
There was a problem hiding this comment.
Workflow review — FR-3019 UserV2 fragment migration
The v2 fragment migration is well-structured in its core design: detail/edit modals are fed from fragments spread on the list query (sidestepping the first+offset pagination footgun), PurgeUsersModal correctly uses the type-to-confirm BAIDeleteConfirmModal, and the create paths surface the one-time secret keys from the mutation payload.
There are two blockers to resolve before merge:
- [HIGH]
BulkCreateUserFromCSVModalsilently discards the one-time secret keys of successfully-created users on a partial-failure batch (unrecoverable). Diverges fromUserSettingModal, which reveals them. - [HIGH]
UserDropdownMenuremoves the@skipOnClientversion gate onmyClientIpand can replace the entire header with an error UI on managers < 26.4.2 — the "manager >= 26.4.3 only" assumption is not enforced anywhere.
One [MEDIUM]: PurgeUsersModal still ships a dead v1 purge_user fallback (with wrong 26.3.0 version comments) that this "remove all v1 fallbacks" refactor should have deleted.
The remaining items are low-severity nits / a PR-description fix — see inline comments.
Generated by a multi-agent workflow review (5 dimensions → adversarial verification → synthesis); the two HIGH findings were additionally hand-verified against the checked-out branch.
b2657d2 to
cf78009
Compare
cf78009 to
b94dfaa
Compare
PurgeUsersModal and UpdateUsersModal received a plain { id, email }[] prop after FR-3016 moved the super-admin user list to adminUsersV2, so the v2 list could drive them without a v1 fragment. Now that the list is on v2, both modals consume a plural UserV2 Relay fragment passed from AdminUserManagement, consistent with the rest of the v2 migration.
- Add PurgeUsersModalFragment / UpdateUsersModalFragment (plural UserV2 fragments selecting id + basicInfo.email) and read them via useFragment.
- AdminUserManagement spreads both fragments on the adminUsersV2 node and passes node fragment refs instead of mapping to a { id, email } list; the now-unused explicit basicInfo selection and the selectedUsersForModal mapper are removed.
UpdateUsersModal now uses adminBulkUpdateUsersV2 (single round-trip keyed by userId) and the AdminUserManagement activate/deactivate toggle uses adminUpdateUserV2, both gated by supports('update-user-v2') with the legacy modify_user path retained for < 26.4.0 backends. Field-inclusion rules are preserved; v1 snake_case props map to the camelCase UpdateUserV2Input and the UserStatusV2 enum.
UserInfoModal no longer runs its own v1 user(email:) query. AdminUserManagement fetches the selected user on demand (adminUsersV2 filtered by uuid, limit 1) and passes a UserV2 fragment ref, so the list query stays lean instead of over-fetching detail fields for every row. The modal reads identity/status/security/organization/projects from the v2 nested shape.
TOTPActivateModal took a v1 User Relay fragment (email + totp_activated) shared across UserSettingModal, UserProfileSettingModal, and ForceTOTPChecker. To let those modals source data from a v2 UserV2 fragment without forcing the login/profile flows onto the superadmin-only adminUsersV2 query, the modal now takes plain userEmail / totpActivated props. Each caller passes the two scalars it already has; the shared TOTPActivateModalFragment is removed.
The edit flow no longer runs its own v1 user(email:) query. AdminUserManagement fetches the selected user on demand (adminUsersV2 filtered by uuid) and passes a UserV2 fragment; create / bulk-create render the modal with no fragment. On submit, edits use adminUpdateUserV2 (gated by supports('update-user-v2'), keyed by userId, with the legacy modify_user path retained for older managers); user creation stays on v1 create_user so the generated keypair can still be revealed. Form values map to the camelCase UpdateUserV2Input with status/role enum conversion, and project node ids convert to UUIDs for group_ids.
…-user-v2 fallbacks
TOTPActivateModalFragment is restored as a single `on UserV2` fragment, shared by the admin (adminUsersV2) and self/profile/login (myUserV2) consumers, replacing the source-agnostic scalar props. ForceTOTPChecker and UserDropdownMenu now read the current user via myUserV2 instead of the v1 user(email:) query.
All supports('update-user-v2') v1-fallback write branches are removed (modify_user in UserSettingModal / AdminUserManagement / UpdateUsersModal, and the legacy password path in UserProfileSettingModal); the WebUI now targets manager >= 26.4.3 only. create_user stays on v1 for the keypair-reveal flow.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ypair reveal CreateUserV2Payload now returns the generated keypair (one-time secret key), and adminBulkCreateUsersV2 is deprecated in favor of adminBulkCreateUsersWithKeypairV2. Move both single and bulk user creation fully onto v2: GeneratedKeypairListModal reads a CreateKeypairPayload fragment, single create runs adminCreateUserV2, and bulk create runs adminBulkCreateUsersWithKeypairV2 — both revealing the generated keypairs.
…per-row fetches The list query (BAIAdminUserV2TableFragment) already fetches basicInfo / organization / security / status / container for every row, so the per-row UserInfo/UserSetting queries only added 'projects'. Colocate UserInfoModalFragment and UserSettingModalFragment on the list node and pass the selected row's node straight to the modals — the same pattern the Purge modal already uses. Removes the UserInfoModalWithUser / UserSettingModalWithUser wrappers and their two AdminUsersV2 queries. Edit freshness now relies on the existing updateFetchKey() refresh after a successful edit.
…ailable UpdateUsersModal silently dropped missing entries via filterOutNullAndUndefined and UserInfoModal rendered an empty modal when its fragment ref was null. Since UserV2's id/basicInfo/status/organization/security are all non-null, @required adds nothing — the real 'no data' case is a null/empty fragment ref. Handle it explicitly: both modals now show a 'failed to load user information' alert, and UpdateUsersModal disables its OK button when there are no target users.
…line profile fragment - Split UserInfoModal/UpdateUsersModal bodies into *Contents components that throw when user data is unavailable; the modal shell catches via ErrorBoundary and renders the error alert inside the still-open modal. - Inline UserProfileSettingModalFragment into UserProfileSettingModal, removing the separate fragment file to match the project pattern. - Drop redundant nullish coalescing on the UserInfoModal fragment ref.
- BulkCreateUserFromCSVModal: surface keypairs from partial-failure bulk
create (secret keys are one-time and must be shown regardless of
partial failure); fix stale comment about per-email re-fetch
- PurgeUsersModal: remove v1 purge_user fallback (manager >= 26.4.3;
supports('bulk-purge-users') always true since 26.3.0)
- UpdateUsersModal: fix _.isEmpty predicate stripping numeric fields
(containerUid / containerMainGid); use !_.isNumber guard
- UserSettingModal, TOTPActivateModal, TOTPActivateForm,
GeneratedKeypairListModal: add missing 'use memo' directive
- Control UserInfoModal / UserSettingModal / create modal visibility via the `open` prop wrapped in `BAIUnmountAfterClose` so the antd open/close animation runs, instead of conditionally mounting/unmounting them. - Store the selected user node (fragment) resolved from the list query via `findUserNode` rather than an id; the modals open based on fragment presence. - Inline UserInfoModal into a single component and drop the ErrorBoundary + Alert split: the nested fields are non-null in the schema, so a valid fragment always carries them. - `projects` is the only nullable field on UserV2; when it fails to load show a WarningOutlined tooltip instead of throwing and erroring the whole page. - Add `credential.FailedToLoadProjects` across all 21 locales. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
b94dfaa to
b126dff
Compare

Resolves #7669 (FR-3019)
Builds on FR-3016 (#7666, merged), which moved the super-admin user list to
adminUsersV2.Summary
This PR migrates all modals used by
AdminUserManagement— together with the shared TOTP / profile / forced-2FA flows — onto the v2 GraphQL surface, and removes the legacy v1 fallbacks now that the WebUI targets manager >= 26.4.3 only. Modals receive their data viaUserV2Relay fragments and drive theadmin*V2mutations, replacing the{ id, email }[]props / v1user(email:)queries /modify_user-style mutations. User creation (single and bulk) also moves to v2 now that the v2 create payload exposes the generated keypair.Changes
Fragment-driven data (UserV2)
PurgeUsersModal/UpdateUsersModal: pluralUserV2fragments (@relay(plural: true)) read withuseFragment, replacing the plain{ id, email }[]prop.UserInfoModal: replaced its own v1user(email:)query with aUserV2fragment.AdminUserManagementspreads...UserInfoModalFragmenton the list query's edges and looks up the selected node in the already-loadedadminUsersV2.edges— no second query, nolimit: 1filter.UserSettingModal(edit): same fragment-on-list-query pattern via...UserSettingModalFragmentonadminUsersV2.edges; create / bulk-create render the modal with no fragment.Shared TOTP modal on a single
UserV2fragmentTOTPActivateModalFragmentliveson UserV2(basicInfo.email,security.totpActivated) and is the single source of data for the modal across all consumers: admin (UserSettingModal, viaadminUsersV2) and self / profile / login (UserProfileSettingModal,ForceTOTPChecker).myUserV2root field — it returns aUserV2and is not superadmin-gated — instead of the v1user(email:)query.UserDropdownMenuandForceTOTPCheckerquerymyUserV2. Because bothadminUsersV2andmyUserV2yieldUserV2nodes, one colocatedon UserV2fragment serves every caller (no scalar prop bridge, no broken colocation).Removed all
update-user-v2v1 fallbacks (manager >= 26.4.3 only)supports('update-user-v2')branch and its legacymodify_userpath, so the v2 mutations always run:UpdateUsersModal(bulk):adminBulkUpdateUsersV2.UserSettingModal(edit):adminUpdateUserV2.AdminUserManagement(activate/deactivate toggle):adminUpdateUserV2.UserProfileSettingModal(full-name / password):updateUserV2.adminBulkPurgeUsersV2) was already v2; the legacypurge_userloop andsupports('bulk-purge-users')gate are now removed too.User creation on v2 (keypair reveal)
adminCreateUserV2: itsCreateUserV2Payloadreturns the user together with the generatedkeypair(one-timesecretKey).adminBulkCreateUsersWithKeypairV2, which replaces the now-deprecatedadminBulkCreateUsersV2and returns each created user's keypair — so bulk create now reveals the generated keypairs too (previously it did not). Keypairs are surfaced on both full-success and partial-failure so one-time secret keys are never silently lost.GeneratedKeypairListModalreads aCreateKeypairPayloadfragment (secretKey,keypair.accessKey,keypair.user.basicInfo.email) instead of the v1KeyPairfragment. It is used byUserSettingModalandBulkCreateUserFromCSVModal(FR-1818, merged on main during review); the CSV flow was migrated toadminBulkCreateUsersWithKeypairV2accordingly, dropping its per-emailkeypairs(email:)re-fetch.Notable decisions
create_userbecauseadminCreateUserV2's payload returned onlyuser(no keypair), which would have dropped the "reveal generated keypair (secret key, shown once)" flow. The schema has since addedkeypair(with the one-time secret key) toCreateUserV2Payload, andadminBulkCreateUsersWithKeypairV2superseded the deprecatedadminBulkCreateUsersV2. Both single and bulk create therefore now run on v2 while preserving — and, for bulk, newly adding — the keypair-reveal flow.baiClient.initialize_totp/activate_totpact on the session user), so a singleUserV2fragment fed from eitheradminUsersV2(admin) ormyUserV2(self) is sufficient — the modal keeps its colocated fragment rather than receiving plain scalars.UpdateUserV2Input/CreateUserV2Inputwithstatus/roleenum conversion, and project node ids are converted to UUIDs (toLocalId) forgroup_ids.Review feedback (post-rebase)
UserInfoModal/UpdateUsersModalbodies are split into same-file*Contentscomponents thatthrowwhen user data is unavailable; the modal shell catches viaErrorBoundaryand renders the error alert inside the still-open modal (same pattern asResourcePresetSettingModal).UserProfileSettingModalFragmentis inlined intoUserProfileSettingModal(separate fragment file removed).Review feedback (agatha197 pass)
BulkCreateUserFromCSVModal: keypairs are now surfaced on partial-failure as well (one-time secret keys must never be silently dropped).PurgeUsersModal: removed the v1purge_userfallback andsupports('bulk-purge-users')gate;adminBulkPurgeUsersV2is unconditional.UpdateUsersModal: fixed_.isEmptypredicate that was erroneously stripping numeric fields (containerUid,containerMainGid).UserSettingModal,TOTPActivateModal,TOTPActivateForm,GeneratedKeypairListModal: added missing'use memo'directives.Verification
bash scripts/verify.sh→=== ALL PASS ===(Relay, Lint, Format, TypeScript, Vite warmup).🤖 Generated with Claude Code