feat(FR-3010): split storage host permissions into project/user folder tabs with domain-scoped project effective permissions#7658
Conversation
|
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.
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. |
Coverage Report for react-coverage (./react)
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
75c1f82 to
e6f9a4f
Compare
There was a problem hiding this comment.
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
e6f9a4f to
bcaadc8
Compare
bcaadc8 to
25f12f8
Compare
287e7a2 to
b2b40a4
Compare
…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>
b2b40a4 to
d645391
Compare
25f12f8 to
50c608e
Compare
| // 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 => |
There was a problem hiding this comment.
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 }} /> |
There was a problem hiding this comment.
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.
agatha197
left a comment
There was a problem hiding this comment.
Please check comments;; I unintentionally added reviews as comments.

Resolves FR-3010 — GitHub 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
Alertexplains the union(domain, project) semantics.domainProjectsV2(scope, limit, offset, filter, orderBy):limit/offset(+countfor total), local component state.BAIGraphQLPropertyFilteronname.ProjectV2OrderField.NAME.colorSuccess) — granted on the projecttoken.purple5) — inherited from the domain onlycolorTextDisabled) — granted on neitherDomainfragment, so editing the domain recomputes every project's union.User Folder Permissions tab
adminUpdateKeypairResourcePolicyV2mutation; the response selects the updatedallowedVfolderHosts, so Relay updates the row in place — no manual refetch.Mutations
modify_domain/modify_group+ afetchKeyrefetch — no V2 project mutation can writeallowed_vfolder_hosts(UpdateProjectInputGQLhas no such field).Plumbing
storageHostIdis read from aStorageVolumefragment at every consumer (drawer → panels → tables) instead of being prop-drilled.Notes
.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/kohand-written, the other 18 languages auto-translated.Screenshots
Project Folder Permissions tab
Domain and project tables with unified
SettingOutlinededit icons and tri-state effective-permission cells (green = set on the project, purple = inherited from the domain).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.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.🤖 Generated with Claude Code