Skip to content

feat(FR-3010): split storage host permissions into project/user folder tabs with domain-scoped project effective permissions#7658

Open
ironAiken2 wants to merge 1 commit into
fr-2991-storage-permission-multi-selectfrom
fr-3010-storage-permission-folder-tabs
Open

feat(FR-3010): split storage host permissions into project/user folder tabs with domain-scoped project effective permissions#7658
ironAiken2 wants to merge 1 commit into
fr-2991-storage-permission-multi-selectfrom
fr-3010-storage-permission-folder-tabs

Conversation

@ironAiken2
Copy link
Copy Markdown
Contributor

@ironAiken2 ironAiken2 commented May 29, 2026

Resolves FR-3010GitHub issue clone pending; Resolves #N (FR-3010) will be added once the Jira→GitHub webhook clones the issue.

Stacked on #7626 (fr-2991-storage-permission-multi-select).

Overview

Splits the Storage Host detail drawer's permissions by target into three top-level tabs: Project Folder Permissions / User Folder Permissions / Capacity.

Project Folder Permissions tab

  • Top Alert explains the union(domain, project) semantics.
  • A single-select domain scopes the view.
  • A server-side paginated / filterable / sortable project table lists the selected domain's projects via domainProjectsV2(scope, limit, offset, filter, orderBy):
    • Pagination: limit / offset (+ count for total), local component state.
    • Filter: BAIGraphQLPropertyFilter on name.
    • Sort: server-side on the Name column → ProjectV2OrderField.NAME.
  • Each project row shows the effective permission = union(domain, project) with tri-state icon colors:
    • 🟢 green (colorSuccess) — granted on the project
    • 🟣 purple (token.purple5) — inherited from the domain only
    • ⚪ gray (colorTextDisabled) — granted on neither
  • Host-allowed status is likewise tri-state (project-allowed / inherited-from-domain / not allowed).
  • The selected domain's permission set is owned by the panel (single source of truth) and read by both tables via a Domain fragment, so editing the domain recomputes every project's union.
  • Bulk edit: select rows → "Edit Permissions" opens the shared modal, which defaults to all-selected and overwrites the chosen permission set onto every selected project. (This replaced an earlier per-key tri-state / apply-only-toggled design — simplified deliberately during review.)

User Folder Permissions tab

  • Keypair-resource-policy table. Writes use the V2 adminUpdateKeypairResourcePolicyV2 mutation; the response selects the updated allowedVfolderHosts, so Relay updates the row in place — no manual refetch.

Mutations

  • Domain / Project writes use V1 modify_domain / modify_group + a fetchKey refetch — no V2 project mutation can write allowed_vfolder_hosts (UpdateProjectInputGQL has no such field).
  • Keypair resource policy writes use V2 (entity returned → Relay auto-update).

Plumbing

  • storageHostId is read from a StorageVolume fragment at every consumer (drawer → panels → tables) instead of being prop-drilled.
  • The drawer is admin-only, so the redundant in-component superadmin check was removed.

Notes

  • Added .claude/rules/graphql-pagination.md: a connection's pagination args are one of (first/after) / (last/before) / (limit/offset) — never mix; Strawberry V2 connections reject mixed modes at runtime.

Verification

bash scripts/verify.sh=== ALL PASS === (Relay / Lint / Format / TypeScript). i18n: en/ko hand-written, the other 18 languages auto-translated.

Screenshots

Project Folder Permissions tab

Domain and project tables with unified SettingOutlined edit icons and tri-state effective-permission cells (green = set on the project, purple = inherited from the domain).

project-folder-tab

Selection toolbar inline with the filter

Selecting project rows reveals the selection toolbar (N selected + clear + edit) on the same line as the property filter.

selection-toolbar

Edit Permissions modal — long-name title ellipsis

A long entity name in the modal title now ellipsizes as Edit Permissions (name…) — it stays within the modal width and the trailing ) never collides with the close button.

edit-modal-title

🤖 Generated with Claude Code

Copy link
Copy Markdown
Contributor Author

ironAiken2 commented May 29, 2026

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more


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 added the size:XL 500~ LoC label May 29, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 29, 2026

Coverage Report for react-coverage (./react)

Status Category Percentage Covered / Total
🔵 Lines 6.52% 1808 / 27690
🔵 Statements 5.3% 2004 / 37763
🔵 Functions 5.36% 300 / 5591
🔵 Branches 3.7% 1306 / 35293
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
react/src/components/DomainStoragePermissionTable.tsx 0% 0% 0% 0% 54-140
react/src/components/KeypairResourcePolicyStoragePermissionTable.tsx 0% 0% 0% 0% 65-181
react/src/components/ProjectFolderPermissionPanel.tsx 0% 0% 0% 0% 39-96
react/src/components/ProjectStoragePermissionTable.tsx 0% 0% 0% 0% 81-265
react/src/components/StorageHostDetailDrawer.tsx 0% 0% 0% 0% 26-53
react/src/components/StorageHostDetailDrawerContent.tsx 0% 0% 0% 0% 25-55
react/src/components/StoragePermissionEditModal.tsx 0% 0% 0% 0% 24-211
react/src/components/UserFolderPermissionPanel.tsx 0% 0% 0% 0% 29-81
Generated in workflow #1314 for commit 50c608e by the Vitest Coverage Report Action

@ironAiken2 ironAiken2 force-pushed the fr-3010-storage-permission-folder-tabs branch 13 times, most recently from 75c1f82 to e6f9a4f Compare June 1, 2026 05:51
@ironAiken2 ironAiken2 marked this pull request as ready for review June 1, 2026 05:57
Copilot AI review requested due to automatic review settings June 1, 2026 05:57
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 refactors the Storage Host detail drawer permission UI to separate permissions by target (project folders vs. user folders), add a domain-scoped project listing with domain+project “effective” permission visualization, and update related i18n and Relay artifacts.

Changes:

  • Split the previous combined “Permissions” content into Project Folder Permissions and User Folder Permissions tabs in the storage host drawer.
  • Rework the project permissions table to list projects under a selected domain via domainProjectsV2(...), showing effective permissions (project ∪ domain) with distinct visual states.
  • Update the shared permission edit modal API to accept multiple targets, and migrate KRP writes to a V2 update mutation.

Reviewed changes

Copilot reviewed 32 out of 47 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
resources/i18n/de.json Adds/updates storage host permission strings and new tab labels (DE).
resources/i18n/el.json Adds/updates storage host permission strings and new tab labels (EL).
resources/i18n/en.json Adds/updates storage host permission strings and new tab labels (EN).
resources/i18n/es.json Adds/updates storage host permission strings and new tab labels (ES).
resources/i18n/fi.json Adds/updates storage host permission strings and new tab labels (FI).
resources/i18n/fr.json Adds/updates storage host permission strings and new tab labels (FR).
resources/i18n/id.json Adds/updates storage host permission strings and new tab labels (ID).
resources/i18n/it.json Adds/updates storage host permission strings and new tab labels (IT).
resources/i18n/ja.json Adds/updates storage host permission strings and new tab labels (JA).
resources/i18n/ko.json Adds/updates storage host permission strings and new tab labels (KO).
resources/i18n/mn.json Adds/updates storage host permission strings and new tab labels (MN).
resources/i18n/ms.json Adds/updates storage host permission strings and new tab labels (MS).
resources/i18n/pl.json Adds/updates storage host permission strings and new tab labels (PL).
resources/i18n/pt-BR.json Adds/updates storage host permission strings and new tab labels (PT-BR).
resources/i18n/pt.json Adds/updates storage host permission strings and new tab labels (PT).
resources/i18n/ru.json Adds/updates storage host permission strings and new tab labels (RU).
resources/i18n/th.json Adds/updates storage host permission strings and new tab labels (TH).
resources/i18n/tr.json Adds/updates storage host permission strings and new tab labels (TR).
resources/i18n/vi.json Adds/updates storage host permission strings and new tab labels (VI).
resources/i18n/zh-CN.json Adds/updates storage host permission strings and new tab labels (ZH-CN).
resources/i18n/zh-TW.json Adds/updates storage host permission strings and new tab labels (ZH-TW).
react/src/components/UserFolderPermissionPanel.tsx New panel for user-folder permissions (KRP select + table), moved out of the previous combined panel.
react/src/components/ProjectFolderPermissionPanel.tsx New panel for project-folder permissions (single-select domain + domain row + domain-scoped project table).
react/src/components/StoragePermissionEditModal.tsx Refactors modal props to accept multiple edit targets; resets behavior on open.
react/src/components/ProjectStoragePermissionTable.tsx Switches to domainProjectsV2 listing, adds filtering/sorting/pagination, row-selection bulk edit, and effective permission visualization (project ∪ domain).
react/src/components/DomainStoragePermissionTable.tsx Changes domain table to consume a domain fragment from the parent (single source of truth) and signal parent refetch on save.
react/src/components/KeypairResourcePolicyStoragePermissionTable.tsx Reads storage host id from a fragment and migrates writes to adminUpdateKeypairResourcePolicyV2.
react/src/components/StorageHostDetailDrawerContent.tsx Replaces the old single Permissions tab with two permission tabs and wires in the new panels.
react/src/components/StorageHostDetailDrawer.tsx Removes redundant storageHostId prop passing (drawer content reads id from fragment).
react/src/components/StorageHostPermissionPanel.tsx Removes the previous combined permissions panel implementation (replaced by the two new panels).
react/src/generated/UserFolderPermissionPanelQuery.graphql.ts Relay artifact for the user-folder panel permission-key query.
react/src/generated/UserFolderPermissionPanel_storageVolumeFrgmt.graphql.ts Relay fragment artifact for user-folder panel.
react/src/generated/ProjectFolderPermissionPanelQuery.graphql.ts Relay artifact for the project-folder panel (domain + permission key list).
react/src/generated/ProjectFolderPermissionPanel_storageVolumeFrgmt.graphql.ts Relay fragment artifact for project-folder panel.
react/src/generated/ProjectStoragePermissionTableQuery.graphql.ts Relay artifact updated for domainProjectsV2 + pagination/filter/orderBy.
react/src/generated/ProjectStoragePermissionTable_storageVolumeFrgmt.graphql.ts Relay fragment artifact to read storage host id in the project table.
react/src/generated/ProjectStoragePermissionTable_domainFrgmt.graphql.ts Relay fragment artifact to read domain permissions in the project table.
react/src/generated/DomainStoragePermissionTable_storageVolumeFrgmt.graphql.ts Relay fragment artifact to read storage host id in the domain table.
react/src/generated/DomainStoragePermissionTable_domainFrgmt.graphql.ts Relay fragment artifact for domain table (name + allowed_vfolder_hosts).
react/src/generated/DomainStoragePermissionTableQuery.graphql.ts Removes obsolete Relay artifact for the old domain table query.
react/src/generated/KeypairResourcePolicyStoragePermissionTable_storageVolumeFrgmt.graphql.ts Relay fragment artifact to read storage host id in the KRP table.
react/src/generated/KeypairResourcePolicyStoragePermissionTableUpdateMutation.graphql.ts New Relay artifact for the V2 KRP update mutation.
react/src/generated/KeypairResourcePolicyStoragePermissionTableModifyMutation.graphql.ts Removes obsolete Relay artifact for the old V1 KRP mutation.
react/src/generated/StorageHostDetailDrawerContentFragment.graphql.ts Relay fragment updated to include the new permission panel fragments.
react/src/generated/StorageHostDetailDrawerFragment.graphql.ts Relay fragment updated to stop selecting redundant id scalar.
react/src/generated/StorageProxyListQuery.graphql.ts Relay query artifact updated due to fragment changes pulled into the drawer.
.claude/rules/graphql-pagination.md Adds a repository rule doc about mutually-exclusive pagination argument modes for V2 connections.
Files not reviewed (15)
  • react/src/generated/DomainStoragePermissionTableQuery.graphql.ts: Language not supported
  • react/src/generated/DomainStoragePermissionTable_domainFrgmt.graphql.ts: Language not supported
  • react/src/generated/DomainStoragePermissionTable_storageVolumeFrgmt.graphql.ts: Language not supported
  • react/src/generated/KeypairResourcePolicyStoragePermissionTableModifyMutation.graphql.ts: Language not supported
  • react/src/generated/KeypairResourcePolicyStoragePermissionTableUpdateMutation.graphql.ts: Language not supported
  • react/src/generated/KeypairResourcePolicyStoragePermissionTable_storageVolumeFrgmt.graphql.ts: Language not supported
  • react/src/generated/ProjectFolderPermissionPanelQuery.graphql.ts: Language not supported
  • react/src/generated/ProjectFolderPermissionPanel_storageVolumeFrgmt.graphql.ts: Language not supported
  • react/src/generated/ProjectStoragePermissionTableQuery.graphql.ts: Language not supported
  • react/src/generated/ProjectStoragePermissionTable_domainFrgmt.graphql.ts: Language not supported
  • react/src/generated/ProjectStoragePermissionTable_storageVolumeFrgmt.graphql.ts: Language not supported
  • react/src/generated/StorageHostDetailDrawerContentFragment.graphql.ts: Language not supported
  • react/src/generated/StorageHostDetailDrawerFragment.graphql.ts: Language not supported
  • react/src/generated/StorageProxyListQuery.graphql.ts: Language not supported
  • react/src/generated/UserFolderPermissionPanel_storageVolumeFrgmt.graphql.ts: Language not supported

Comment thread react/src/components/StoragePermissionEditModal.tsx
Comment thread react/src/components/ProjectStoragePermissionTable.tsx
Comment thread react/src/components/ProjectStoragePermissionTable.tsx
Comment thread react/src/components/ProjectStoragePermissionTable.tsx
Comment thread react/src/components/ProjectFolderPermissionPanel.tsx
Comment thread react/src/components/ProjectStoragePermissionTable.tsx
Comment thread react/src/components/ProjectStoragePermissionTable.tsx
Comment thread react/src/components/ProjectFolderPermissionPanel.tsx
Comment thread react/src/components/DomainStoragePermissionTable.tsx
Comment thread react/src/components/DomainStoragePermissionTable.tsx
@ironAiken2 ironAiken2 force-pushed the fr-3010-storage-permission-folder-tabs branch from e6f9a4f to bcaadc8 Compare June 1, 2026 08:25
ironAiken2 added a commit that referenced this pull request Jun 1, 2026
ironAiken2 added a commit that referenced this pull request Jun 1, 2026
ironAiken2 added a commit that referenced this pull request Jun 1, 2026
@ironAiken2 ironAiken2 force-pushed the fr-3010-storage-permission-folder-tabs branch from bcaadc8 to 25f12f8 Compare June 2, 2026 00:09
@ironAiken2 ironAiken2 force-pushed the fr-2991-storage-permission-multi-select branch from 287e7a2 to b2b40a4 Compare June 2, 2026 00:09
…r tabs with domain-scoped project effective permissions

Replace the single flat permission panel (domain/project/keypair cards) with
three top-level drawer tabs: Project Folder Permissions, User Folder
Permissions, and Capacity.

Project Folder Permissions tab applies the union of domain + project grants to
project folders. A single-select domain scopes a domainProjectsV2 listing of
every project in that domain; each project row shows the effective permission
with tri-state icon colors (project-granted = green, domain-inherited = teal,
none = gray). The selected domain's permission set is owned by the panel as a
single source of truth so editing the domain re-computes every project's union.
Projects support bulk edit via table row selection.

User Folder Permissions tab hosts the keypair-resource-policy table (controls
users' personal folders), relocated from the old panel.

The shared edit modal now supports multi-target editing with per-key tri-state
(indeterminate for keys that differ across selected projects), applying only
the keys the user explicitly toggled to every target.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ironAiken2 ironAiken2 force-pushed the fr-2991-storage-permission-multi-select branch from b2b40a4 to d645391 Compare June 2, 2026 00:35
@ironAiken2 ironAiken2 force-pushed the fr-3010-storage-permission-folder-tabs branch from 25f12f8 to 50c608e Compare June 2, 2026 00:35
// the query already as enum values (keep verbatim); the edited host's
// newKeys are kebab keys, so convert them to enum values.
// (`set-user-specific-permission` maps asymmetrically to `SET_USER_PERM`.)
const keyToV2Permission = (key: string): string =>
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.

The keyToV2Permission inverse mapping is reimplemented inline here instead of deriving it from the canonical V2_TO_V1_PERMISSION map already in react/src/helper/storageHostPermission.ts.

Currently all V1 kebab keys happen to round-trip correctly through key.toUpperCase().replace(/-/g, '_') except for set-user-specific-permission → SET_USER_PERM, which is handled by the special case. But the asymmetric entry lives in the helper — if a new asymmetric permission is added there in the future and this function is not updated in sync, the wrong enum value will be sent to the mutation silently.

Suggest exporting an inverse helper (e.g. keyToV2Enum) from storageHostPermission.ts and using it here, so the source of truth stays in one place:

// storageHostPermission.ts
const V1_TO_V2_PERMISSION = Object.fromEntries(
  Object.entries(V2_TO_V1_PERMISSION).map(([v2, v1]) => [v1, v2])
);
export const keyToV2Enum = (key: string): string =>
  V1_TO_V2_PERMISSION[key] ?? key.toUpperCase().replace(/-/g, '_');

<BAIFlex gap="xxs" align="center">
{isHostAllowed ? (
<>
<CheckCircleOutlined style={{ color: token.purple5 }} />
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.

The domain row's "Host Allowed" badge uses token.purple5 (the same color used in the legend for "Inherited from domain"), but the domain is the source of inheritance — it is not itself inheriting from anywhere.

The permission-column icons also use purple, but those are covered by the legend. This HostAllowed badge sits outside the permission columns and has no dedicated legend entry, so a user reading it would likely interpret it as "this host access was inherited from... the domain?" — which is circular.

Suggestion: use token.colorSuccess here (matching the project row's direct-granted case) to signal "this domain grants access to this host", reserving purple exclusively for the inherited state shown on project rows.

Copy link
Copy Markdown
Contributor

@agatha197 agatha197 left a comment

Choose a reason for hiding this comment

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

Please check comments;; I unintentionally added reviews as comments.

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.

3 participants