Skip to content

Conversation

MichaelUnkey
Copy link
Collaborator

@MichaelUnkey MichaelUnkey commented Sep 9, 2025

What does this PR do?

Fixes # (issue)

ENG-1890

If there is not an issue for this, please create one first. This is used to tracking purposes and also helps use understand why this PR exists

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

  • Role and Permission can be deleted from footer when rows are selected
  • Does not cause issues with other components

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

Summary by CodeRabbit

  • Bug Fixes

    • Improved selection reliability in Roles and Permissions tables by using stable IDs, ensuring checkboxes and selection state remain accurate even when names are duplicated or changed.
    • More consistent behavior when selecting or deselecting items within the tables.
  • Refactor

    • Updated internal selection logic to use IDs instead of names for roles and permissions.

Copy link

linear bot commented Sep 9, 2025

Copy link

changeset-bot bot commented Sep 9, 2025

⚠️ No Changeset found

Latest commit: c05acc6

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

vercel bot commented Sep 9, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
dashboard Ready Ready Preview Comment Sep 9, 2025 5:46pm
1 Skipped Deployment
Project Deployment Preview Comments Updated (UTC)
engineering Ignored Ignored Preview Sep 9, 2025 5:46pm

Copy link
Contributor

coderabbitai bot commented Sep 9, 2025

📝 Walkthrough

Walkthrough

Selection identity switched from names to stable IDs in permissions and roles tables. Checkbox handlers and selection checks now use permissionId and roleId when toggling and rendering selection state. Hover logic remains name-based. No exported/public API changes.

Changes

Cohort / File(s) Summary
Selection by ID in authorization tables
apps/dashboard/app/(app)/authorization/permissions/components/table/permissions-list.tsx, apps/dashboard/app/(app)/authorization/roles/components/table/roles-list.tsx
Replace name-based selection with ID-based selection. Update toggleSelection handlers and isSelected checks to use permissionId/roleId. Checkbox onCheckedChange now passes IDs. Hover logic unchanged. No exported signatures modified.

Sequence Diagram(s)

sequenceDiagram
  participant U as User
  participant T as Table Row Checkbox
  participant C as Component State
  participant R as UI Row

  U->>T: Click checkbox
  T->>C: toggleSelection(id)
  alt ID not selected
    C->>C: Add id to selected Set
  else ID selected
    C->>C: Remove id from selected Set
  end
  C-->>R: Re-render with isSelected(id)
  R->>U: Checkbox state and row selection updated
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested labels

Bug

Suggested reviewers

  • chronark
  • mcstepp
  • perkinsjr
  • ogzhanolguncu

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The current description omits a summary of the changes, motivational context, and a properly formatted issue reference, only listing "ENG-1890" under "Fixes # (issue)" without using the required "#" syntax. While the testing steps and checklist are populated, the template’s first section lacks the necessary details such as change overview, dependencies, and context to help reviewers understand why and how the fix was implemented. Please add a concise summary in the "What does this PR do?" section that outlines the selection ID migration and deletion fix, format the issue reference as "Fixes #1890", and include any relevant motivation or context and dependencies required for this change.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title focuses on fixing the deletion of permissions or roles from the footer, which aligns with the main goal of the changeset enabling delete functionality in the bottom dialog and migrating selection logic to use IDs. It is concise, references the bug being addressed, and matches the core change without unnecessary detail.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch eng-1890-roles-and-permissions-deletion-fails-from-bottom-dialog

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

github-actions bot commented Sep 9, 2025

Thank you for following the naming conventions for pull request titles! 🙏

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
apps/dashboard/app/(app)/authorization/roles/components/table/roles-list.tsx (2)

27-40: Selection must use stable IDs, not object identity (prevents “phantom” selections).

Using Set<RoleBasic> with .has(role) relies on referential equality. After refetch/pagination/virtualization, row objects are re-instantiated, causing:

  • Previously selected rows to render as unselected.
  • Footer still showing N selected and attempting to delete stale refs.

Store roleId (string) in the Set instead. Toggle by ID and derive UI selection from role.roleId.

Apply:

-  const [selectedRoles, setSelectedRoles] = useState<Set<RoleBasic>>(new Set());
+  const [selectedRoles, setSelectedRoles] = useState<Set<string>>(new Set());

-  const toggleSelection = useCallback((role: RoleBasic) => {
+  const toggleSelection = useCallback((roleId: string) => {
     setSelectedRoles((prevSelected) => {
       const newSelected = new Set(prevSelected);
-      if (newSelected.has(role)) {
-        newSelected.delete(role);
+      if (newSelected.has(roleId)) {
+        newSelected.delete(roleId);
       } else {
-        newSelected.add(role);
+        newSelected.add(roleId);
       }
       return newSelected;
     });
   }, []);

Follow-ups in this file and its callsite are covered below.


146-147: Dependency hygiene (optional).

columns depends on selectedRoles Set identity. If you later memoize the Set or mutate in place, consider depending on selectedRoles.size instead to avoid stale closures.

-  [selectedRoles, toggleSelection, hoveredRoleName, selectedRole?.roleId],
+  [selectedRoles.size, toggleSelection, hoveredRoleName, selectedRole?.roleId],
apps/dashboard/app/(app)/authorization/permissions/components/table/permissions-list.tsx (2)

178-181: Clear selection after successful bulk delete.

To prevent stale footer state, ensure the delete mutation clears the selection set and the single selectedPermission highlight on success.

If handled here, wire a success callback via props:

-          <SelectionControls
-            selectedPermissions={selectedPermissions}
-            setSelectedPermissions={setSelectedPermissions}
-          />
+          <SelectionControls
+            selectedPermissionIds={selectedPermissionIds}
+            setSelectedPermissionIds={setSelectedPermissionIds}
+            onDeleted={() => {
+              setSelectedPermission(null);
+              setSelectedPermissionIds(new Set());
+            }}
+          />

28-41: Replace object-based selection with ID-based selection
Use Set<string> of permissionId in permissions-list.tsx (instead of Set<Permission>) to avoid reference‐equality breaks across refetches/virtualization. Update the toggle callback, checkbox handlers, and loadMoreFooterProps to use selectedPermissionIds/setSelectedPermissionIds, and adjust SelectionControls’ props and its performPermissionDelete mapping accordingly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0086690 and f059070.

📒 Files selected for processing (4)
  • apps/dashboard/app/(app)/authorization/permissions/components/table/components/selection-controls/index.tsx (2 hunks)
  • apps/dashboard/app/(app)/authorization/permissions/components/table/permissions-list.tsx (3 hunks)
  • apps/dashboard/app/(app)/authorization/roles/components/table/components/selection-controls/index.tsx (2 hunks)
  • apps/dashboard/app/(app)/authorization/roles/components/table/roles-list.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-06-19T11:48:05.070Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3324
File: apps/dashboard/app/(app)/authorization/roles/components/table/components/actions/keys-table-action.popover.constants.tsx:17-18
Timestamp: 2025-06-19T11:48:05.070Z
Learning: In the authorization roles refactor, the RoleBasic type uses `roleId` as the property name for the role identifier, not `id`. This is consistent throughout the codebase in apps/dashboard/lib/trpc/routers/authorization/roles/query.ts.

Applied to files:

  • apps/dashboard/app/(app)/authorization/roles/components/table/roles-list.tsx
  • apps/dashboard/app/(app)/authorization/roles/components/table/components/selection-controls/index.tsx
📚 Learning: 2024-10-08T15:33:04.290Z
Learnt from: AkshayBandi027
PR: unkeyed/unkey#2215
File: apps/dashboard/app/(app)/@breadcrumb/authorization/roles/[roleId]/page.tsx:28-29
Timestamp: 2024-10-08T15:33:04.290Z
Learning: In `authorization/roles/[roleId]/update-role.tsx`, the tag `role-${role.id}` is revalidated after updating a role to ensure that the caching mechanism is properly handled for roles.

Applied to files:

  • apps/dashboard/app/(app)/authorization/roles/components/table/roles-list.tsx
  • apps/dashboard/app/(app)/authorization/roles/components/table/components/selection-controls/index.tsx
🧬 Code graph analysis (4)
apps/dashboard/app/(app)/authorization/roles/components/table/roles-list.tsx (1)
apps/dashboard/lib/trpc/routers/authorization/roles/query.ts (1)
  • RoleBasic (16-16)
apps/dashboard/app/(app)/authorization/permissions/components/table/permissions-list.tsx (1)
apps/dashboard/lib/trpc/routers/authorization/permissions/query.ts (1)
  • Permission (19-19)
apps/dashboard/app/(app)/authorization/permissions/components/table/components/selection-controls/index.tsx (1)
apps/dashboard/lib/trpc/routers/authorization/permissions/query.ts (1)
  • Permission (19-19)
apps/dashboard/app/(app)/authorization/roles/components/table/components/selection-controls/index.tsx (1)
apps/dashboard/lib/trpc/routers/authorization/roles/query.ts (1)
  • RoleBasic (16-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Build / Build
🔇 Additional comments (3)
apps/dashboard/app/(app)/authorization/roles/components/table/roles-list.tsx (1)

165-166: Confirm ID-based selection across consumers. Searches for useState<Set<RoleBasic>>, .has(role), and all SelectionControls callsites didn’t surface (TSX types unrecognized). Manually verify every consumer—including footer actions and toggleSelection handlers—now uses Set<string> of IDs and that no Set<RoleBasic> remains.

apps/dashboard/app/(app)/authorization/roles/components/table/components/selection-controls/index.tsx (1)

19-21: Confirm deletion hook contracts. Verified useDeleteRole and useDeletePermission clear selection on success via their onSuccess callbacks (receiving { roleIds: string[] | string; message: string } and { permissionIds: string[] | string; message: string }, respectively). Manually confirm the underlying TRPC procedures accept only { roleIds: string[] } / { permissionIds: string[] } as input and adjust if single-string cases sneak through.

apps/dashboard/app/(app)/authorization/permissions/components/table/permissions-list.tsx (1)

171-173: Good: stable keys and selection checks use permissionId.

Using permission.permissionId for keys and row highlighting is correct and avoids referential pitfalls.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/dashboard/app/(app)/authorization/permissions/components/table/permissions-list.tsx (2)

64-71: Prevent accidental row selection when clicking the checkbox

If VirtualTable rows handle onClick, the checkbox click may bubble and also change selectedItem. Stop propagation on the checkbox to avoid dual selection changes.

Apply within this block:

   {(isSelected || isHovered) && (
     <Checkbox
       checked={isSelected}
       className="size-4 [&_svg]:size-3"
+      onClick={(e) => e.stopPropagation()}
       onCheckedChange={() => toggleSelection(permission.permissionId)}
     />
   )}

27-29: Make hover identity ID-based to avoid duplicate-name collisions

Two permissions can share the same name; hovering one would light up both. Track hover by permissionId for 1:1 behavior.

- const [hoveredPermissionName, setHoveredPermissionName] = useState<string | null>(null);
+ const [hoveredPermissionId, setHoveredPermissionId] = useState<string | null>(null);
- const isHovered = hoveredPermissionName === permission.name;
+ const isHovered = hoveredPermissionId === permission.permissionId;
- onMouseEnter={() => setHoveredPermissionName(permission.name)}
- onMouseLeave={() => setHoveredPermissionName(null)}
+ onMouseEnter={() => setHoveredPermissionId(permission.permissionId)}
+ onMouseLeave={() => setHoveredPermissionId(null)}
- [selectedPermissions, toggleSelection, hoveredPermissionName, selectedPermission?.permissionId],
+ [selectedPermissions, toggleSelection, hoveredPermissionId, selectedPermission?.permissionId],

Also applies to: 51-53, 61-63, 159-160

apps/dashboard/app/(app)/authorization/roles/components/table/roles-list.tsx (1)

63-69: Stop bubbling from the checkbox to the row click

Avoid unintended setSelectedRole when toggling the checkbox.

   {(isSelected || isHovered) && (
     <Checkbox
       checked={isSelected}
       className="size-4 [&_svg]:size-3"
+      onClick={(e) => e.stopPropagation()}
       onCheckedChange={() => toggleSelection(role.roleId)}
     />
   )}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f059070 and c05acc6.

📒 Files selected for processing (2)
  • apps/dashboard/app/(app)/authorization/permissions/components/table/permissions-list.tsx (3 hunks)
  • apps/dashboard/app/(app)/authorization/roles/components/table/roles-list.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3324
File: apps/dashboard/app/(app)/authorization/roles/components/table/components/actions/keys-table-action.popover.constants.tsx:17-18
Timestamp: 2025-06-19T11:48:05.070Z
Learning: In the authorization roles refactor, the RoleBasic type uses `roleId` as the property name for the role identifier, not `id`. This is consistent throughout the codebase in apps/dashboard/lib/trpc/routers/authorization/roles/query.ts.
📚 Learning: 2025-06-19T11:48:05.070Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3324
File: apps/dashboard/app/(app)/authorization/roles/components/table/components/actions/keys-table-action.popover.constants.tsx:17-18
Timestamp: 2025-06-19T11:48:05.070Z
Learning: In the authorization roles refactor, the RoleBasic type uses `roleId` as the property name for the role identifier, not `id`. This is consistent throughout the codebase in apps/dashboard/lib/trpc/routers/authorization/roles/query.ts.

Applied to files:

  • apps/dashboard/app/(app)/authorization/roles/components/table/roles-list.tsx
📚 Learning: 2024-10-08T15:33:04.290Z
Learnt from: AkshayBandi027
PR: unkeyed/unkey#2215
File: apps/dashboard/app/(app)/@breadcrumb/authorization/roles/[roleId]/page.tsx:28-29
Timestamp: 2024-10-08T15:33:04.290Z
Learning: In `authorization/roles/[roleId]/update-role.tsx`, the tag `role-${role.id}` is revalidated after updating a role to ensure that the caching mechanism is properly handled for roles.

Applied to files:

  • apps/dashboard/app/(app)/authorization/roles/components/table/roles-list.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Build / Build
  • GitHub Check: Analyze (actions)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (7)
apps/dashboard/app/(app)/authorization/permissions/components/table/permissions-list.tsx (4)

31-41: ID-based selection toggle: LGTM

Switching to permissionId and Set correctly decouples selection from mutable display fields and unblocks footer delete by ID.


51-53: UI check and handler now keyed by permissionId: LGTM

Using selectedPermissions.has(permission.permissionId) and passing permission.permissionId to toggleSelection brings the row checkbox in sync with footer actions.

Also applies to: 65-71


171-173: Key extractor switched to permissionId: LGTM

Stabilizes row identity across pagination and renames.


178-181: Verify SelectionControls prop types and delete mutation arguments
Confirm that SelectionControls now accepts a Set<string> for selectedPermissions and that the delete flow in permissions-list.tsx invokes the mutation with permission IDs (not names).

apps/dashboard/app/(app)/authorization/roles/components/table/roles-list.tsx (3)

27-41: Move to Set and roleId-based toggle: LGTM

This aligns with the Roles API (RoleBasic.roleId) per prior refactors and fixes footer delete by using stable IDs.


50-53: Selection check and handler by roleId: LGTM

Correctly uses selectedRoles.has(role.roleId) and passes role.roleId to toggleSelection.

Also applies to: 65-69


158-165: Approve SelectionControls usage and delete mutation
SelectionControlsProps correctly uses Set<string> and setSelectedRoles, and the delete TRPC mutation expects roleIds (string | string[]) matching the hook’s payload.

Comment on lines 28 to 29
const [hoveredRoleName, setHoveredRoleName] = useState<string | null>(null);

Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Prefer ID-based hover to prevent duplicate-name hover artifacts

Mirror the selection change for hover.

- const [hoveredRoleName, setHoveredRoleName] = useState<string | null>(null);
+ const [hoveredRoleId, setHoveredRoleId] = useState<string | null>(null);
- const isHovered = hoveredRoleName === role.name;
+ const isHovered = hoveredRoleId === role.roleId;
- onMouseEnter={() => setHoveredRoleName(role.name)}
- onMouseLeave={() => setHoveredRoleName(null)}
+ onMouseEnter={() => setHoveredRoleId(role.roleId)}
+ onMouseLeave={() => setHoveredRoleId(null)}
- [selectedRoles, toggleSelection, hoveredRoleName, selectedRole?.roleId],
+ [selectedRoles, toggleSelection, hoveredRoleId, selectedRole?.roleId],

Also applies to: 51-52, 60-62, 146-147

🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/authorization/roles/components/table/roles-list.tsx
around lines 28-29 (and similarly update usages at 51-52, 60-62, 146-147), the
hover state currently stores role.name which causes incorrect hover artifacts
for roles with duplicate names; change the hover state to store the role ID
(e.g. hoveredRoleId: string | null) and update all handlers and comparisons to
use role.id instead of role.name so hover logic mirrors selection (which already
uses IDs). Ensure setter names and variable references are updated consistently
(setHoveredRoleId, comparisons in JSX/handlers) and keep null as the cleared
state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant