Skip to content

refactor(FR-3019): migrate Purge/Update user modals to UserV2 fragment#7673

Open
ironAiken2 wants to merge 12 commits into
mainfrom
fr-3019-purge-update-modals-v2-fragment
Open

refactor(FR-3019): migrate Purge/Update user modals to UserV2 fragment#7673
ironAiken2 wants to merge 12 commits into
mainfrom
fr-3019-purge-update-modals-v2-fragment

Conversation

@ironAiken2

@ironAiken2 ironAiken2 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

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 via UserV2 Relay fragments and drive the admin*V2 mutations, replacing the { id, email }[] props / v1 user(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: plural UserV2 fragments (@relay(plural: true)) read with useFragment, replacing the plain { id, email }[] prop.
  • UserInfoModal: replaced its own v1 user(email:) query with a UserV2 fragment. AdminUserManagement spreads ...UserInfoModalFragment on the list query's edges and looks up the selected node in the already-loaded adminUsersV2.edges — no second query, no limit: 1 filter.
  • UserSettingModal (edit): same fragment-on-list-query pattern via ...UserSettingModalFragment on adminUsersV2.edges; create / bulk-create render the modal with no fragment.

Shared TOTP modal on a single UserV2 fragment

  • TOTPActivateModalFragment lives on UserV2 (basicInfo.email, security.totpActivated) and is the single source of data for the modal across all consumers: admin (UserSettingModal, via adminUsersV2) and self / profile / login (UserProfileSettingModal, ForceTOTPChecker).
  • The self / profile / login flows now read the current user via the myUserV2 root field — it returns a UserV2 and is not superadmin-gated — instead of the v1 user(email:) query. UserDropdownMenu and ForceTOTPChecker query myUserV2. Because both adminUsersV2 and myUserV2 yield UserV2 nodes, one colocated on UserV2 fragment serves every caller (no scalar prop bridge, no broken colocation).

Removed all update-user-v2 v1 fallbacks (manager >= 26.4.3 only)

  • Dropped every supports('update-user-v2') branch and its legacy modify_user path, so the v2 mutations always run:
    • UpdateUsersModal (bulk): adminBulkUpdateUsersV2.
    • UserSettingModal (edit): adminUpdateUserV2.
    • AdminUserManagement (activate/deactivate toggle): adminUpdateUserV2.
    • UserProfileSettingModal (full-name / password): updateUserV2.
  • Purge (adminBulkPurgeUsersV2) was already v2; the legacy purge_user loop and supports('bulk-purge-users') gate are now removed too.

User creation on v2 (keypair reveal)

  • Single create now runs adminCreateUserV2: its CreateUserV2Payload returns the user together with the generated keypair (one-time secretKey).
  • Bulk create runs adminBulkCreateUsersWithKeypairV2, which replaces the now-deprecated adminBulkCreateUsersV2 and 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.
  • GeneratedKeypairListModal reads a CreateKeypairPayload fragment (secretKey, keypair.accessKey, keypair.user.basicInfo.email) instead of the v1 KeyPair fragment. It is used by UserSettingModal and BulkCreateUserFromCSVModal (FR-1818, merged on main during review); the CSV flow was migrated to adminBulkCreateUsersWithKeypairV2 accordingly, dropping its per-email keypairs(email:) re-fetch.

Notable decisions

  • User creation now runs on v2 (revised). Create was initially left on v1 create_user because adminCreateUserV2's payload returned only user (no keypair), which would have dropped the "reveal generated keypair (secret key, shown once)" flow. The schema has since added keypair (with the one-time secret key) to CreateUserV2Payload, and adminBulkCreateUsersWithKeypairV2 superseded the deprecated adminBulkCreateUsersV2. Both single and bulk create therefore now run on v2 while preserving — and, for bulk, newly adding — the keypair-reveal flow.
  • TOTP activation is self-service (baiClient.initialize_totp / activate_totp act on the session user), so a single UserV2 fragment fed from either adminUsersV2 (admin) or myUserV2 (self) is sufficient — the modal keeps its colocated fragment rather than receiving plain scalars.
  • Form values are mapped to the camelCase UpdateUserV2Input / CreateUserV2Input with status / role enum conversion, and project node ids are converted to UUIDs (toLocalId) for group_ids.

Review feedback (post-rebase)

  • UserInfoModal / UpdateUsersModal bodies are split into same-file *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 (same pattern as ResourcePresetSettingModal).
  • UserProfileSettingModalFragment is inlined into UserProfileSettingModal (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 v1 purge_user fallback and supports('bulk-purge-users') gate; adminBulkPurgeUsersV2 is unconditional.
  • UpdateUsersModal: fixed _.isEmpty predicate 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

@github-actions github-actions Bot added the size:L 100~500 LoC label Jun 1, 2026

ironAiken2 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • flow:merge-queue - adds this PR to the back of the merge queue
  • flow:hotfix - for urgent changes, fast-track this PR to the front of 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.

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for react-coverage (./react)

Status Category Percentage Covered / Total
🔵 Lines 6.46% 1875 / 29004
🔵 Statements 5.29% 2116 / 39977
🔵 Functions 5.34% 305 / 5707
🔵 Branches 3.56% 1342 / 37608
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
react/src/components/AdminUserManagement.tsx 0% 0% 0% 0% 63-662
react/src/components/BulkCreateUserFromCSVModal.tsx 0% 0% 0% 0% 90-1475
react/src/components/ForceTOTPChecker.tsx 0% 0% 0% 0% 13-55
react/src/components/GeneratedKeypairListModal.tsx 0% 0% 0% 0% 37-141
react/src/components/PurgeUsersModal.tsx 0% 0% 0% 0% 33-146
react/src/components/TOTPActivateModal.tsx 0% 0% 0% 0% 32-139
react/src/components/UpdateUsersModal.tsx 0% 0% 0% 0% 45-387
react/src/components/UserDropdownMenu.tsx 0% 0% 0% 0% 46-262
react/src/components/UserInfoModal.tsx 0% 0% 0% 0% 27-163
react/src/components/UserProfileSettingModal.tsx 0% 0% 0% 0% 50-320
react/src/components/UserSettingModal.tsx 0% 0% 0% 0% 62-1173
Generated in workflow #2149 for commit b126dff by the Vitest Coverage Report Action

@ironAiken2 ironAiken2 marked this pull request as ready for review June 1, 2026 09:52
Copilot AI review requested due to automatic review settings June 1, 2026 09:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 PurgeUsersModal and UpdateUsersModal to accept usersFrgmt (plural UserV2 fragment refs) and read { id, basicInfo.email } via useFragment.
  • Updated AdminUserManagement to spread the new modal fragments on adminUsersV2.edges.node and 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

@github-actions github-actions Bot added size:XL 500~ LoC and removed size:L 100~500 LoC labels Jun 2, 2026
@graphite-app graphite-app Bot changed the base branch from fr-3016-user-list-v2-migration to graphite-base/7673 June 2, 2026 08:05
@graphite-app graphite-app Bot force-pushed the graphite-base/7673 branch from 3513119 to c30de63 Compare June 2, 2026 08:06
@graphite-app graphite-app Bot force-pushed the fr-3019-purge-update-modals-v2-fragment branch from b51e16a to afdd6ac Compare June 2, 2026 08:06
@graphite-app graphite-app Bot changed the base branch from graphite-base/7673 to main June 2, 2026 08:07
@graphite-app graphite-app Bot force-pushed the fr-3019-purge-update-modals-v2-fragment branch from afdd6ac to e49ea63 Compare June 2, 2026 08:07

@yomybaby yomybaby left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please resolve merg conflicts

@ironAiken2 ironAiken2 force-pushed the fr-3019-purge-update-modals-v2-fragment branch from e95ab4b to c1937b2 Compare June 9, 2026 03:29
@github-actions github-actions Bot added area:ux UI / UX issue. area:i18n Localization labels Jun 9, 2026
@ironAiken2 ironAiken2 force-pushed the fr-3019-purge-update-modals-v2-fragment branch from 74a2898 to f13ac5a Compare June 9, 2026 04:19
Comment thread react/src/components/UserInfoModal.tsx
Comment thread react/src/components/UserInfoModal.tsx Outdated
Comment thread react/src/components/UpdateUsersModal.tsx Outdated
Comment thread react/src/components/UserProfileSettingModalFragment.graphql.ts Outdated
@ironAiken2 ironAiken2 force-pushed the fr-3019-purge-update-modals-v2-fragment branch from f13ac5a to f0a56b8 Compare June 12, 2026 05:02
@ironAiken2

Copy link
Copy Markdown
Contributor Author

@yomybaby Rebased this branch onto the latest main via gt sync — the branch is now up to date with main (0 commits behind) and merges cleanly, so the merge conflict is resolved. PTAL when you have a chance.

@ironAiken2 ironAiken2 requested a review from nowgnuesLee June 12, 2026 05:36
Comment thread react/src/components/GeneratedKeypairListModal.tsx

@agatha197 agatha197 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. [HIGH] BulkCreateUserFromCSVModal silently discards the one-time secret keys of successfully-created users on a partial-failure batch (unrecoverable). Diverges from UserSettingModal, which reveals them.
  2. [HIGH] UserDropdownMenu removes the @skipOnClient version gate on myClientIp and 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.

Comment thread react/src/components/BulkCreateUserFromCSVModal.tsx
Comment thread react/src/components/UserDropdownMenu.tsx
Comment thread react/src/components/PurgeUsersModal.tsx
Comment thread react/src/components/UpdateUsersModal.tsx
Comment thread react/src/components/BulkCreateUserFromCSVModal.tsx
Comment thread react/src/components/UserSettingModal.tsx
Comment thread react/src/components/UserSettingModal.tsx
Comment thread react/src/components/AdminUserManagement.tsx Outdated
@ironAiken2 ironAiken2 force-pushed the fr-3019-purge-update-modals-v2-fragment branch from b2657d2 to cf78009 Compare June 16, 2026 05:17
@ironAiken2 ironAiken2 requested a review from agatha197 June 16, 2026 05:20
@ironAiken2 ironAiken2 force-pushed the fr-3019-purge-update-modals-v2-fragment branch from cf78009 to b94dfaa Compare June 18, 2026 07:03
ironAiken2 and others added 12 commits June 18, 2026 16:42
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>
@ironAiken2 ironAiken2 force-pushed the fr-3019-purge-update-modals-v2-fragment branch from b94dfaa to b126dff Compare June 18, 2026 08:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:i18n Localization area:ux UI / UX issue. size:XL 500~ LoC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate Purge/Update user modals to receive a v2 UserV2 fragment

5 participants