Skip to content

feat(FR-3271): add detailed permissions tab with per-scope-type tables and grant-state tags#8191

Open
ironAiken2 wants to merge 3 commits into
mainfrom
feat/FR-3271-detailed-permissions-tab
Open

feat(FR-3271): add detailed permissions tab with per-scope-type tables and grant-state tags#8191
ironAiken2 wants to merge 3 commits into
mainfrom
feat/FR-3271-detailed-permissions-tab

Conversation

@ironAiken2

@ironAiken2 ironAiken2 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Resolves #8179 (FR-3271)

Summary

Introduces the new "Detailed Permissions" tab in RoleDetailDrawer, merging the former Scopes and Permissions views into per-scope-type cards. This is PR 1 of the FR-3264 stack — the at-a-glance view (grant-state tags). The scope-level permission edit modal follows in PR 2/3.

What changed

  • New react/src/components/RolePermissionDetailTab.tsx — a thin orchestrator. Reads the role via a single roleNodeFrgmt (id + totalScopes: scopes(first: 1) { count } for the tab-level empty state) and renders one card per scope type that rbacPermissionMatrix reports — scope types are not hardcoded client-side, so a type added on the server needs no code change here. Each card gets its type's configurable entity × operation set derived from the matrix.
  • New react/src/components/ScopedRolePermissionCard.tsx — one scope-type card. Issues its own query from the first render (so per-card search/refresh never refetches the others, FR-3):
    • adminRole.scopes(filter: { scopeType: { equals: <type> } }, limit, offset)server-filtered by scope type and server-paginated via the EntityFilter.scopeType filter added in manager 26.8.0, with useBAIPaginationOptionState driving limit/offset (offset mode only, per .claude/rules/graphql-pagination.md).
    • adminRole.permissions(filter: { scopeType: ... }, limit: 100) — the role's grants for tag-state computation (the role is implicit via the connection; no roleId filter).
    • Scope-id search via BAIGraphQLPropertyFilter<EntityFilter> (scopeId, default iContains, 26.8.0) paired with BAIFetchKeyButton in the card body (UserFolderPermissionPanelV2 pattern). The resolved scope name is not searchableEntityFilter exposes no name field (see Notes).
    • Renders nothing when the role has no scope of its type (count === 0 and no active filter) — FR-2's "only assigned scope types" holds per card.
    • Columns: scope name via BAINameActionCell (the FR-6 edit action attaches here in PR 2), raw scope id via BAIId, and display-only entity-type Tags color-coded by grant state — green (all matrix operations granted), gold (some), gray (none — still shown, never hidden).
  • New react/src/helper/rbacGrantState.ts (+ unit test) — pure computeRBACGrantState() comparing the role's granted operations against the full matrix operation set for a (scopeType, entityType) pair → full / partial / none.
  • data/schema.graphql synced to manager 26.8.0EntityFilter gains scopeType / scopeId filter fields ("Added in 26.8.0"), which is what makes server-side scope-type filtering possible (the previously escalated backend gap is resolved). The schema bump also refreshes unrelated generated artifacts.
  • Removed the standalone Scopes and Permissions tabs and their components: RoleScopeTab.tsx, RolePermissionTab.tsx, and CreatePermissionModal.tsx are deleted (superseded by the merged tab; permission editing returns as the edit modal in PR 2). RoleFormModal now sources RBACElementType from its own generated types.
  • Refactored react/src/components/RoleAssignmentTab.tsx — fragment moved from on Query (adminRoleAssignments(filter: { roleId })) to on Role using the Role.users connection; node-based refetch; roleId/projectScopeId derived from its own fragment; a* nuqs keys → local React state (nothing inside the drawer writes to the URL — only the page-level roleDetail param remains); typed RoleAssignmentFilter.
  • Simplified react/src/components/RoleDetailDrawer.tsxRoleDetailDrawerQuery takes only $id; all tab data flows through RoleDetailDrawerContentFragment on adminRole.
  • Naming — every prop carrying a Role-node-derived fragment is uniformly roleNodeFrgmt (RoleDetailDrawerContent, RoleAssignmentTab, RolePermissionDetailTab, ScopedRolePermissionCard, RoleFormModal).
  • No legacy filter branching — the baiClient.supports('rbac-filter-wrapper') guard is dropped in the new components; 26.4.x LTS always supports the wrapped operator form.
  • i18n — adds rbac.DetailedPermissions, rbac.FullyAllowed, rbac.PartiallyAllowed, rbac.NotAllowed; rewords rbac.NoScopesToDisplay to convey "no scopes are configured for this role"; removes the now-unused rbac.SearchScopes — all 21 locales.
  • Spec documents for the stack live in .specs/FR-3264-role-detail-drawer-detailed-permissions/ (to be refreshed once the stack settles).

Notes / caveats

  • Requires manager ≥ 26.8.0 for the EntityFilter.scopeType / scopeId filters. On older managers the scopes connection rejects/ignores these fields — the tab is not version-gated (the whole RBAC detail surface targets new managers).
  • Scope search matches the raw scope id only (iContains); the resolved display name (project name / email / …) is not server-searchable. If name search is needed, it requires a backend EntityFilter name field (follow-up).
  • Tag-state computation fetches up to 100 permission rows per card (adminRole.permissions has no per-scopeId list filter to page exactly against the visible rows); a known bound for very large roles.
  • One query per matrix scope type is issued in parallel when the tab opens; cards for unassigned types self-hide after their (empty) response.

Verification

bash scripts/verify.sh → Relay, Lint, Format, TypeScript all pass. Unit tests react/src/helper/rbacGrantState.test.ts pass. Server-filter behavior (scopeType / scopeId iContains, server pagination) needs a live check against a 26.8.0 manager.

Stacked on

This is the base PR of the FR-3264 stack (main → FR-3271 → FR-3272 → FR-3273). PR 2 (FR-3272) adds the single-scope permission edit modal wired into ScopedRolePermissionCard; PR 3 (FR-3273) adds multi-scope bulk edit (row selection + 'Keep as is' semantics).

Screenshots

Detailed Permissions tab — per-scope-type tables with grant-state tags

Captured before the 26.8.0 server-filter rework — the search input row is now a BAIGraphQLPropertyFilter + refresh button; table content is unchanged. Will be recaptured against a 26.8.0 manager.

Detailed Permissions tab

🤖 Generated with Claude Code

@github-actions github-actions Bot added area:ux UI / UX issue. area:i18n Localization size:XL 500~ LoC labels Jul 7, 2026

ironAiken2 commented Jul 7, 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 Jul 7, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for react-coverage (./react)

Status Category Percentage Covered / Total
🔵 Lines 7.34% 2127 / 28958
🔵 Statements 6.07% 2430 / 39982
🔵 Functions 6.17% 350 / 5672
🔵 Branches 4.03% 1517 / 37639
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
react/src/components/RoleAssignmentTab.tsx 0% 0% 0% 0% 49-429
react/src/components/RoleDetailDrawer.tsx 0% 0% 0% 0% 26-102
react/src/components/RoleDetailDrawerContent.tsx 0% 0% 0% 0% 19-49
react/src/components/RoleFormModal.tsx 0% 0% 0% 0% 48-488
react/src/components/RolePermissionDetailTab.tsx 0% 0% 0% 0% 30-87
react/src/components/ScopedRolePermissionCard.tsx 0% 0% 0% 0% 45-373
react/src/helper/rbacGrantState.ts 100% 100% 100% 100%
Generated in workflow #2719 for commit db65cf9 by the Vitest Coverage Report Action

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 introduces the new "Detailed Permissions" tab in RoleDetailDrawer, the base (view-only) PR of the FR-3264 stack. It merges the former Scopes view into a per-scope-type BAICard table layout where each row shows a concrete scope instance and color-coded, display-only grant-state tags per selectable entity type (green = fully / gold = partially / gray = not allowed). Each scope-type section self-fetches its rows, the role's grants, and (once at tab level) the rbacPermissionMatrix, so filtering/refreshing one card doesn't disturb others. This lands the at-a-glance read view; the scope-level edit modal follows in PRs 2–3.

Changes:

  • New RolePermissionAssignmentTab.tsx (per-scope-type sections, offset pagination in local React state, grant-state tags) plus the pure rbacGrantState.ts helper and its unit tests.
  • Restructures RoleDetailDrawerContent.tsx (default tab → Detailed Permissions, drops s* nuqs keys) and RoleDetailDrawer.tsx (removes scope query variables/fragment); deletes the absorbed RoleScopeTab.tsx.
  • Adds rbac.DetailedPermissions, FullyAllowed, PartiallyAllowed, NotAllowed, NoScopesToDisplay to en.json/ko.json.

Reviewed changes

Copilot reviewed 10 out of 17 changed files in this pull request and generated no comments.

Show a summary per file
File Description
react/src/components/RolePermissionAssignmentTab.tsx New merged tab: per-scope-type cards, self-owned queries, grant-state tag cells.
react/src/helper/rbacGrantState.ts Pure computeRBACGrantState() (full/partial/none via matrix ∩ granted ops).
react/src/helper/rbacGrantState.test.ts 8 unit cases covering full/partial/none, empty matrix, dedup, stale grants.
react/src/components/RoleDetailDrawerContent.tsx Adds default Detailed Permissions tab, removes Scopes tab + s* nuqs keys.
react/src/components/RoleDetailDrawer.tsx Drops RoleScopeTabFragment spread and scope* query variables.
react/src/components/RoleScopeTab.tsx Deleted (fully absorbed into the new tab).
resources/i18n/en.json, resources/i18n/ko.json New rbac.* grant-state / empty-state labels.
.specs/FR-3264-.../spec.md, dev-plan.md Design spec and phased PR-stack plan (docs only).
react/src/__generated__/*.graphql.ts Relay compiler output consistent with the query/fragment changes.

The change is clean and follows repository conventions, but it restructures GraphQL queries across multiple files in the security-adjacent RBAC surface, changes the default drawer tab, and relies on old-backend filter-wrapper branching and a documented 500-row tag-computation bound whose runtime behavior can't be fully verified statically. This warrants final human verification of the drawer end-to-end.

Files not reviewed (7)
  • react/src/generated/RoleDetailDrawerContentFragment.graphql.ts: Generated file
  • react/src/generated/RoleDetailDrawerQuery.graphql.ts: Generated file
  • react/src/generated/RolePermissionAssignmentTabMatrixQuery.graphql.ts: Generated file
  • react/src/generated/RolePermissionAssignmentTabSectionQuery.graphql.ts: Generated file
  • react/src/generated/RolePermissionAssignmentTab_roleScopeFragment.graphql.ts: Generated file
  • react/src/generated/RoleScopeTabFragment.graphql.ts: Generated file
  • react/src/generated/RoleScopeTabRefetchQuery.graphql.ts: Generated file

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

Copilot reviewed 29 out of 36 changed files in this pull request and generated 1 comment.

Files not reviewed (7)
  • react/src/generated/RoleDetailDrawerContentFragment.graphql.ts: Generated file
  • react/src/generated/RoleDetailDrawerQuery.graphql.ts: Generated file
  • react/src/generated/RolePermissionAssignmentTabMatrixQuery.graphql.ts: Generated file
  • react/src/generated/RolePermissionAssignmentTabSectionQuery.graphql.ts: Generated file
  • react/src/generated/RolePermissionAssignmentTab_roleScopeFragment.graphql.ts: Generated file
  • react/src/generated/RoleScopeTabFragment.graphql.ts: Generated file
  • react/src/generated/RoleScopeTabRefetchQuery.graphql.ts: Generated file

Comment thread resources/i18n/de.json Outdated
@ironAiken2 ironAiken2 force-pushed the feat/FR-3271-detailed-permissions-tab branch 7 times, most recently from 08e9413 to 2ea90cc Compare July 9, 2026 05:49
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for backend-ai-ui-coverage (./packages/backend.ai-ui)

Status Category Percentage Covered / Total
🔵 Lines 11.59% 607 / 5237
🔵 Statements 9.82% 680 / 6918
🔵 Functions 12.86% 151 / 1174
🔵 Branches 8.2% 558 / 6799
File CoverageNo changed files found.
Generated in workflow #2719 for commit db65cf9 by the Vitest Coverage Report Action

@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.

Reviewed the Detailed Permissions tab. Solid refactor overall — fragment architecture, single $id query, pagination single-mode compliance, i18n key parity, and no dangling references to the deleted components all look good. No blockers. One primary request (fixed scope-type card ordering) plus a medium correctness caveat and a few cleanups, left inline.

Comment thread react/src/components/RolePermissionDetailTab.tsx
Comment thread react/src/components/RolePermissionDetailTab.tsx Outdated
Comment thread react/src/components/ScopedRolePermissionCard.tsx
Comment thread react/src/components/RoleDetailDrawer.tsx Outdated
Comment thread react/src/components/RoleDetailDrawerContent.tsx Outdated
@ironAiken2 ironAiken2 force-pushed the feat/FR-3271-detailed-permissions-tab branch from 2ea90cc to ac195dd Compare July 9, 2026 08:10
ironAiken2 and others added 3 commits July 10, 2026 13:59
…s and grant-state tags

# Conflicts:
#	react/src/components/CreatePermissionModal.tsx
#	react/src/components/RolePermissionTab.tsx
…ush empty-state card body, correct stale permission-cap comment

- Remove always-undefined onDataChange/onAssignmentChange/onTabReset props
  (RoleDetailDrawer, RoleDetailDrawerContent, RoleAssignmentTab)
- Add styles={{ body: { paddingTop: 0 } }} to the empty-state BAICard
  (RolePermissionDetailTab)
- Correct PERMISSION_FETCH_LIMIT comment: PermissionFilter.scopeId { in }
  exists on 26.8.0; exact paging deferred as follow-up (ScopedRolePermissionCard)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- ScopedRolePermissionCard table: name column '적용 대상'→'이름' (rbac.Name,
  new), raw id column '적용 대상 ID'→'ID' (general.ID), permissions column
  '세부 권한'→'권한'
- Detailed Permissions tab label now reuses rbac.Permissions ('권한');
  rbac.DetailedPermissions removed from all 21 locales
- rbac.ScopeRawId ko value '적용 대상 ID'→'적용 범위 ID' (role list column and
  scope-id search label)
- 19 non-source locales retranslated for rbac.Name/Permissions/ScopeRawId

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ironAiken2 ironAiken2 force-pushed the feat/FR-3271-detailed-permissions-tab branch from 480b7e3 to db65cf9 Compare July 10, 2026 04:59
@ironAiken2 ironAiken2 requested a review from agatha197 July 10, 2026 05:02
import { useSuspendedBackendaiClient } from '../hooks';
import RoleDetailDrawerContent from './RoleDetailDrawerContent';
import RoleFormModal from './RoleFormModal';
import { Alert, Drawer, Skeleton, Tooltip, Typography, theme } from 'antd';

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.

[Re: line +23]

  roleId: string || null;

I think null would be better than undefined when the value is absent.

See this comment inline on Graphite.

@@ -62,97 +61,43 @@ const RoleDetailDrawer: React.FC<RoleDetailDrawerProps> = ({
interface RoleDetailDrawerInnerProps {

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.

[Re: lines +52 to +56]

Instead of separating them, please use a deferred value and drawer.loading.

See this comment inline on Graphite.

Comment on lines +95 to 99
if (!adminRole) {
return (
<Alert type="warning" showIcon message={t('general.ErrorOccurred')} />
<Alert type="warning" showIcon description={t('general.ErrorOccurred')} />
);
}

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.

Instead of this approach, the @required directive seems like a better fit.

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.

I don't think this function contains logic that warrants splitting it out.

Comment on lines +42 to +46
for (const operation of uniqueFullOperations) {
if (grantedOperations.has(operation)) {
grantedWithinMatrix += 1;
}
}

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.

Since it's a Set, the intersection method should be enough.

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. rbac size:XL 500~ LoC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add "Detailed Permissions" tab: per-scope-type tables with grant-state tags (FR-3264)

4 participants