Skip to content

feat: allow organization members to be assigned multiple groups#1919

Merged
wilsonrivera merged 32 commits intomainfrom
wilson/eng-7240-allow-users-to-be-part-of-multiple-groups
Jul 1, 2025
Merged

feat: allow organization members to be assigned multiple groups#1919
wilsonrivera merged 32 commits intomainfrom
wilson/eng-7240-allow-users-to-be-part-of-multiple-groups

Conversation

@wilsonrivera
Copy link
Copy Markdown
Contributor

@wilsonrivera wilsonrivera commented May 30, 2025

Motivation and Context

This PR introduces a way for organizations to assign members to multiple groups. This way organizations aren't forced to create different groups just to remove or add a subset of permissions.

Checklist

groups-dialog grouls-selector audit-logs

Summary by CodeRabbit

  • New Features

    • Added support for assigning multiple groups to users during invitation and membership updates.
    • Introduced a multi-select group UI component for managing user group assignments.
  • Improvements

    • Enhanced audit log descriptions for group membership changes.
    • Updated group management dialogs and forms to support multiple group selections.
    • Improved transactional safety and audit logging for group updates.
    • Refined group-related repository methods for better multi-group handling.
    • Optimized invitation group data handling with new database join table.
  • Bug Fixes

    • Fixed error handling and validation for group assignments in member management.
  • Tests

    • Expanded and refactored test coverage for multi-group scenarios and error cases.
  • Chores

    • Updated database schema to support many-to-many relationships between invitations and groups.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 30, 2025

Router image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-833e50a84370e113bd00bfe798ceff74b7741a9b

wilsonrivera and others added 11 commits May 30, 2025 12:23
…ltiple-groups

# Conflicts:
#	connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go
…e-part-of-multiple-groups' into wilson/eng-7240-allow-users-to-be-part-of-multiple-groups
…ltiple-groups

# Conflicts:
#	connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go
#	controlplane/src/core/bufservices/organization/leaveOrganization.ts
#	controlplane/src/core/repositories/OrganizationRepository.ts
@wilsonrivera wilsonrivera requested a review from JivusAyrus June 5, 2025 16:29
Comment thread controlplane/src/core/bufservices/user/updateOrgMemberGroup.ts Outdated
Comment thread controlplane/src/core/bufservices/user/updateOrgMemberGroup.ts Outdated
Comment thread controlplane/src/core/repositories/OrganizationInvitationRepository.ts Outdated
Comment thread controlplane/src/core/repositories/OrganizationRepository.ts Outdated
@wilsonrivera wilsonrivera requested a review from JivusAyrus June 21, 2025 03:46
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jul 1, 2025

Walkthrough

This change set migrates the organization invitation and member group management system from supporting a single group per invitation/member to supporting multiple groups. It updates protobuf definitions, database schema, repositories, service logic, and UI components to handle arrays of group IDs instead of single group IDs. Test suites and utility functions are refactored accordingly, and audit log types/actions are expanded for group membership changes.

Changes

File(s) Change Summary
proto/wg/cosmo/platform/v1/platform.proto, connect/src/wg/cosmo/platform/v1/platform_pb.ts Protobuf and generated TypeScript updated: InviteUserRequest and UpdateOrgMemberGroupRequest now use repeated groups fields instead of single groupId; field numbers and metadata updated.
controlplane/migrations/0125_acoustic_jackpot.sql, controlplane/migrations/meta/_journal.json, controlplane/src/db/schema.ts Database migration and schema: Added organization_invitation_groups join table for many-to-many invitation-group relation, migrated existing data, dropped group_id from organization_invitations, updated migration journal.
controlplane/src/types/index.ts OrganizationInvitationDTO interface changed: removed groupId, added groups array with groupId and kcGroupId.
controlplane/src/core/repositories/OrganizationInvitationRepository.ts Invitation repository refactored: now handles multiple groups per invitation, updates queries, transactional logic, and method signatures accordingly.
controlplane/src/core/repositories/OrganizationRepository.ts Method updateUserGroup renamed and refactored to updateMemberGroups, now accepts array of group IDs and performs batch insertions.
controlplane/src/core/repositories/OrganizationGroupRepository.ts Method changeMemberGroup refactored to update group memberships and invitations for multiple entries, now asynchronous and more comprehensive; added method byIds to fetch multiple groups.
controlplane/src/core/bufservices/user/inviteUser.ts, controlplane/src/core/bufservices/user/acceptOrDeclineInvitation.ts Invitation and acceptance logic updated to support multiple groups per invitation, iterating over group arrays and updating Keycloak accordingly.
controlplane/src/core/bufservices/user/updateOrgMemberGroup.ts Member group update logic refactored: supports multiple groups, transactional safety, detailed audit logging, and OIDC restrictions.
controlplane/src/bin/migrate-groups.ts Migration script updated to insert multiple group associations per invitation into new join table instead of direct updates.
controlplane/src/db/models.ts Audit log types expanded: added 'added', 'removed', 'member_group.added', and 'member_group.removed' actions; removed 'member_role.updated'.
studio/src/components/member-groups/delete-group-dialog.tsx, studio/src/pages/[organizationSlug]/apikeys.tsx, studio/src/components/group-select.tsx Refactored prop name in GroupSelect from onGroupChange to onValueChange in all usages.
studio/src/components/members/update-member-group-dialog.tsx Updated dialog to support selecting and updating multiple groups for a member; UI and mutation logic refactored.
studio/src/components/multi-group-select.tsx New MultiGroupSelect component added for multi-group selection in UI, with async loading and search.
studio/src/pages/[organizationSlug]/members.tsx Invitation form refactored to support multiple group selection; schema, state, mutation, and UI updated to use array of groups.
studio/src/components/audit-log-table.tsx Audit log table updated: new text fragments for member_group.added and member_group.removed actions.
studio/src/components/member-groups/group-resource-selector.tsx, studio/src/components/member-groups/group-rule-builder.tsx Fixed import paths for PopoverContentWithScrollableContent.
studio/src/components/layout/dashboard-layout.tsx Added separator property to "Feature Flags" nav item based on user role.
studio/src/hooks/use-check-user-access.ts Role collection changed to use a Set for membership checks instead of an array.
controlplane/test/delete-user.test.ts, controlplane/test/organization/leave-organization.test.ts Tests updated: group update API calls now use groups array instead of groupId, removed userID.
controlplane/test/organization-groups.test.ts Test suite refactored and expanded for multi-group support, error cases, and Keycloak integration; helper functions and assertions updated.
controlplane/test/test-util.ts Added createOrganizationGroup test utility for creating/updating groups with roles.

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 737ada1 and 5dcc8c4.

📒 Files selected for processing (5)
  • controlplane/src/core/bufservices/user/acceptOrDeclineInvitation.ts (1 hunks)
  • controlplane/src/core/bufservices/user/updateOrgMemberGroup.ts (2 hunks)
  • controlplane/src/core/repositories/OrganizationGroupRepository.ts (2 hunks)
  • controlplane/test/organization-groups.test.ts (13 hunks)
  • studio/src/pages/[organizationSlug]/members.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • controlplane/src/core/bufservices/user/acceptOrDeclineInvitation.ts
  • controlplane/src/core/bufservices/user/updateOrgMemberGroup.ts
  • studio/src/pages/[organizationSlug]/members.tsx
🧰 Additional context used
🧠 Learnings (2)
controlplane/src/core/repositories/OrganizationGroupRepository.ts (1)
Learnt from: wilsonrivera
PR: wundergraph/cosmo#1919
File: controlplane/src/core/repositories/OrganizationGroupRepository.ts:193-224
Timestamp: 2025-07-01T13:53:54.121Z
Learning: In the Cosmo codebase, database transactions are typically managed at the service layer (e.g., in buf services like deleteOrganizationGroup.ts), where repositories are instantiated with the transaction handle and all operations within those repositories are automatically part of the same transaction.
controlplane/test/organization-groups.test.ts (1)
Learnt from: wilsonrivera
PR: wundergraph/cosmo#1919
File: controlplane/src/core/repositories/OrganizationGroupRepository.ts:193-224
Timestamp: 2025-07-01T13:53:54.121Z
Learning: In the Cosmo codebase, database transactions are typically managed at the service layer (e.g., in buf services like deleteOrganizationGroup.ts), where repositories are instantiated with the transaction handle and all operations within those repositories are automatically part of the same transaction.
🧬 Code Graph Analysis (1)
controlplane/src/core/repositories/OrganizationGroupRepository.ts (1)
controlplane/src/types/index.ts (1)
  • OrganizationGroupDTO (254-267)
⏰ Context from checks skipped due to timeout of 90000ms (13)
  • GitHub Check: build-router
  • GitHub Check: build_push_image
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: image_scan
  • GitHub Check: integration_test (./events)
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: build_test
  • GitHub Check: Analyze (go)
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: build_test
  • GitHub Check: build_push_image
🔇 Additional comments (10)
controlplane/src/core/repositories/OrganizationGroupRepository.ts (2)

84-95: LGTM! Well-implemented method with good performance optimization.

The early return for empty arrays prevents unnecessary database queries, and the implementation correctly uses inArray for efficient querying of multiple group IDs.


206-237: LGTM! Method correctly refactored for multi-group support.

The refactored approach properly handles the migration from single group to multiple groups:

  • Delete and re-insert pattern correctly manages the many-to-many relationship
  • onConflictDoNothing() prevents duplicate key errors when members already belong to target groups
  • Addition of invitation group updates aligns with the new schema

Based on the retrieved learnings, this method is called within a transaction at the service layer, so the operations are properly atomic.

controlplane/test/organization-groups.test.ts (8)

3-6: LGTM! Imports properly updated for test refactoring.

The addition of createOrganizationGroup helper and cleanup of imports supports the test improvements.


23-24: LGTM! Using genID for unique test group names.

Good practice to use unique identifiers to prevent test interference.

Also applies to: 41-42


93-98: LGTM! Test properly uses helper function and validates group creation.

The test correctly validates that the group is created with the expected role configuration.


118-121: LGTM! Correctly updated for multi-group API.

The test properly uses the new array-based groups parameter instead of a single group ID.


144-151: LGTM! Comprehensive error validation.

Proper testing of cross-organization group assignment with appropriate error codes and descriptive error messages.


185-200: LGTM! Proper Keycloak integration testing.

The test correctly validates that group deletion is synchronized with Keycloak, ensuring external system consistency.


402-513: LGTM! Comprehensive test suite for multi-group functionality.

Excellent test coverage that validates:

  • Multiple group assignment and removal
  • Database and Keycloak synchronization
  • Edge cases like moving members to groups they already belong to
  • Proper cleanup during group deletion

The test assertions are thorough and verify both local database state and external Keycloak integration.


368-369: LGTM! Properly updated for new array-based API.

The test correctly uses the new groups array parameter while maintaining the same functional validation.

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
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

♻️ Duplicate comments (2)
controlplane/src/core/repositories/OrganizationRepository.ts (1)

1011-1031: Address the empty groups behavior and consider transaction safety.

The early return when groups.length === 0 prevents users from being removed from all groups. Based on past review comments, this seems to be a point of discussion - should users be allowed to have zero groups?

Additionally, consider wrapping the delete and insert operations in a transaction to ensure atomicity.

 public async updateMemberGroups(input: { orgMemberID: string; groups: string[] }) {
-  if (input.groups.length === 0) {
-    // Prevent updating the groups if no new groups were provided
-    return;
-  }
+  return this.db.transaction(async (tx) => {
+    await tx
+      .delete(schema.organizationGroupMembers)
+      .where(eq(schema.organizationGroupMembers.organizationMemberId, input.orgMemberID));

-  await this.db
-    .delete(schema.organizationGroupMembers)
-    .where(eq(schema.organizationGroupMembers.organizationMemberId, input.orgMemberID));
-
-  await this.db
-    .insert(schema.organizationGroupMembers)
-    .values(
-      input.groups.map((groupId) => ({
-        organizationMemberId: input.orgMemberID,
-        groupId,
-      })),
-    )
-    .onConflictDoNothing()
-    .execute();
+    if (input.groups.length > 0) {
+      await tx
+        .insert(schema.organizationGroupMembers)
+        .values(
+          input.groups.map((groupId) => ({
+            organizationMemberId: input.orgMemberID,
+            groupId,
+          })),
+        )
+        .onConflictDoNothing()
+        .execute();
+    }
+  });
 }
controlplane/src/core/repositories/OrganizationInvitationRepository.ts (1)

233-237: Verify groups exist before adding user to organization

The code adds the organization member before checking if invitation groups exist. This could leave the member without any groups if the invitation has no groups.

Consider checking groups before adding the member:

+const invitationGroups = await this.getPendingInvitationGroups(invitation[0].id);
+if (invitationGroups.length === 0) {
+  throw new Error('Invitation must have at least one group');
+}
+
 const insertedMember = await orgRepo.addOrganizationMember({
   userID: input.userId,
   organizationID: input.organizationId,
 });

-const invitationGroups = await this.getPendingInvitationGroups(invitation[0].id);
-if (invitationGroups.length === 0) {
-  return;
-}
🧹 Nitpick comments (4)
proto/wg/cosmo/platform/v1/platform.proto (1)

1366-1366: Fix field naming convention.

The field name orgMemberUserID should follow snake_case convention as per protobuf style guidelines.

Apply this diff to fix the naming convention:

-  string orgMemberUserID = 1;
+  string org_member_user_id = 1;
controlplane/migrations/0125_acoustic_jackpot.sql (1)

28-30: Improve readability by separating index creation statements.

The index creation logic is correct, but the formatting could be improved for better readability.

-CREATE INDEX IF NOT EXISTS "org_inv_invitation_idx" ON "organization_invitation_groups" USING btree ("invitation_id");--> statement-breakpoint
-CREATE INDEX IF NOT EXISTS "org_inv_group_id" ON "organization_invitation_groups" USING btree ("group_id");--> statement-breakpoint
+CREATE INDEX IF NOT EXISTS "org_inv_invitation_idx" ON "organization_invitation_groups" USING btree ("invitation_id");
+--> statement-breakpoint
+CREATE INDEX IF NOT EXISTS "org_inv_group_id" ON "organization_invitation_groups" USING btree ("group_id");
+--> statement-breakpoint
studio/src/components/multi-group-select.tsx (1)

88-103: Potential performance issue with array operations in onSelect

The current implementation creates a new array from the Set and then maps over it to find groups, which could be inefficient for large group lists. Consider using a Map for O(1) lookups.

Consider optimizing the group lookup:

+const groupsMap = new Map(availableGroups.map(g => [g.groupId, g]));

 onSelect={() => {
   const currentValue = new Set(value);
   if (currentValue.has(group.groupId)) {
     currentValue.delete(group.groupId);
   } else {
     currentValue.add(group.groupId);
   }

   onValueChange(
     currentValue.size === 0
       ? []
-      : Array.from(currentValue)
-        .map((id) => availableGroups.find((group) => group.groupId === id)!)
-        .filter((group) => !!group)
+      : Array.from(currentValue)
+        .map((id) => groupsMap.get(id))
+        .filter((group): group is OrganizationGroup => !!group)
   );
 }}
controlplane/src/core/bufservices/user/updateOrgMemberGroup.ts (1)

133-134: Consider using more descriptive variable names

The variable names groupsToAddTo and groupsToRemoveFrom could be more concise while maintaining clarity.

-const groupsToAddTo = newGroups.difference(existingGroups);
-const groupsToRemoveFrom = existingGroups.difference(newGroups);
+const groupsToAdd = newGroups.difference(existingGroups);
+const groupsToRemove = existingGroups.difference(newGroups);

Also update the usage in lines 136, 146, and 177.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6264bd and 737ada1.

⛔ Files ignored due to path filters (1)
  • connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go is excluded by !**/*.pb.go, !**/gen/**
📒 Files selected for processing (29)
  • connect/src/wg/cosmo/platform/v1/platform_pb.ts (4 hunks)
  • controlplane/migrations/0125_acoustic_jackpot.sql (1 hunks)
  • controlplane/migrations/meta/_journal.json (1 hunks)
  • controlplane/src/bin/migrate-groups.ts (1 hunks)
  • controlplane/src/core/bufservices/user/acceptOrDeclineInvitation.ts (1 hunks)
  • controlplane/src/core/bufservices/user/inviteUser.ts (3 hunks)
  • controlplane/src/core/bufservices/user/updateOrgMemberGroup.ts (2 hunks)
  • controlplane/src/core/repositories/OrganizationGroupRepository.ts (1 hunks)
  • controlplane/src/core/repositories/OrganizationInvitationRepository.ts (6 hunks)
  • controlplane/src/core/repositories/OrganizationRepository.ts (1 hunks)
  • controlplane/src/db/models.ts (2 hunks)
  • controlplane/src/db/schema.ts (1 hunks)
  • controlplane/src/types/index.ts (1 hunks)
  • controlplane/test/delete-user.test.ts (1 hunks)
  • controlplane/test/organization-groups.test.ts (13 hunks)
  • controlplane/test/organization/leave-organization.test.ts (2 hunks)
  • controlplane/test/test-util.ts (2 hunks)
  • proto/wg/cosmo/platform/v1/platform.proto (2 hunks)
  • studio/src/components/audit-log-table.tsx (1 hunks)
  • studio/src/components/group-select.tsx (2 hunks)
  • studio/src/components/layout/dashboard-layout.tsx (1 hunks)
  • studio/src/components/member-groups/delete-group-dialog.tsx (1 hunks)
  • studio/src/components/member-groups/group-resource-selector.tsx (1 hunks)
  • studio/src/components/member-groups/group-rule-builder.tsx (1 hunks)
  • studio/src/components/members/update-member-group-dialog.tsx (2 hunks)
  • studio/src/components/multi-group-select.tsx (1 hunks)
  • studio/src/hooks/use-check-user-access.ts (1 hunks)
  • studio/src/pages/[organizationSlug]/apikeys.tsx (2 hunks)
  • studio/src/pages/[organizationSlug]/members.tsx (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
studio/src/hooks/use-check-user-access.ts (1)
studio/src/lib/constants.ts (1)
  • roles (215-276)
controlplane/test/organization-groups.test.ts (4)
controlplane/src/core/test-util.ts (1)
  • genID (53-55)
controlplane/test/test-util.ts (2)
  • createOrganizationGroup (425-457)
  • SetupTest (66-391)
controlplane/src/db/schema.ts (1)
  • users (1062-1068)
controlplane/src/core/repositories/OrganizationGroupRepository.ts (1)
  • OrganizationGroupRepository (9-343)
studio/src/pages/[organizationSlug]/members.tsx (1)
studio/src/components/multi-group-select.tsx (1)
  • MultiGroupSelect (12-119)
🪛 Buf (1.54.0)
proto/wg/cosmo/platform/v1/platform.proto

1366-1366: Field name "orgMemberUserID" should be lower_snake_case, such as "org_member_user_id".

(FIELD_LOWER_SNAKE_CASE)

⏰ Context from checks skipped due to timeout of 90000ms (13)
  • GitHub Check: build-router
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: build_push_image
  • GitHub Check: build_test
  • GitHub Check: build_push_image
  • GitHub Check: integration_test (./. ./fuzzquery ./lifecycle ./modules)
  • GitHub Check: integration_test (./telemetry)
  • GitHub Check: build_test
  • GitHub Check: image_scan
  • GitHub Check: integration_test (./events)
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (34)
studio/src/components/member-groups/group-resource-selector.tsx (1)

8-8: LGTM! Import path updated correctly.

The import path adjustment for PopoverContentWithScrollableContent reflects the component reorganization and maintains consistency with similar changes across the codebase.

controlplane/migrations/meta/_journal.json (1)

879-886: Migration entry properly tracked.

The new migration entry for multi-group invitation support follows the established format and incremental indexing pattern correctly.

studio/src/components/member-groups/group-rule-builder.tsx (1)

15-15: LGTM! Consistent import path update.

The import path change aligns with the component reorganization seen in other member-groups components, maintaining consistency across the codebase.

studio/src/components/member-groups/delete-group-dialog.tsx (1)

151-151: LGTM! Prop naming standardized.

The change from onGroupChange to onValueChange standardizes the event handler naming convention across components while maintaining the same callback functionality.

studio/src/components/layout/dashboard-layout.tsx (1)

154-154: LGTM! Role-based navigation separator enhancement.

The conditional separator addition improves navigation visual grouping by showing a separator after Feature Flags for non-admin/developer users, following the established pattern of role-based UI customization.

controlplane/test/delete-user.test.ts (1)

238-241: LGTM - Test correctly updated for multi-group API.

The test properly adapts to the new groups array parameter while maintaining the same test logic. The single group ID is now wrapped in an array, which aligns with the multi-group support changes.

studio/src/components/group-select.tsx (1)

8-8: LGTM - Consistent prop naming improvement.

The renaming from onGroupChange to onValueChange improves consistency with React component conventions. All usages are updated correctly and the functionality remains unchanged.

Also applies to: 13-13, 51-51

controlplane/test/organization/leave-organization.test.ts (2)

1-1: LGTM - Good import cleanup.

Removing unused imports improves code maintainability and reduces clutter.

Also applies to: 3-4


43-43: LGTM - Test correctly updated for multi-group API.

The test properly adapts to the new groups array parameter while preserving the original test logic of assigning a single group to the member.

studio/src/components/audit-log-table.tsx (1)

88-92: LGTM - Proper audit log action support added.

The new conditional branches correctly handle the member_group.added and member_group.removed audit actions with appropriate descriptive text. The implementation follows the existing pattern and enhances audit log readability for group membership changes.

controlplane/src/types/index.ts (1)

282-282: ✅ OrganizationInvitationDTO updated for multi-group support

  • The interface now requires groups: { groupId: string; kcGroupId: string | null }[], replacing the old optional single groupId?.
  • Backend handlers (inviteUser), repository methods, and tests have been updated to build and persist multiple invitation groups.
  • Breaking change: all consumers of this endpoint (e.g. frontend calls to inviteUser) must be updated to send an array of group IDs (req.groups: string[]) instead of a single groupId.
studio/src/hooks/use-check-user-access.ts (1)

28-30: Good performance improvement using Set for role checking.

The change from Array to Set improves the lookup performance from O(n) to O(1), which is particularly beneficial when checking against multiple roles. The logic correctly maintains the same functionality while being more efficient.

studio/src/pages/[organizationSlug]/apikeys.tsx (1)

253-257: Consistent prop naming across GroupSelect usages.

The callback prop rename from onGroupChange to onValueChange maintains consistency with the GroupSelect component's interface changes and standardizes the API across all usages.

Also applies to: 628-628

controlplane/src/db/models.ts (1)

91-92: Appropriate audit log types for multi-group membership tracking.

The addition of generic 'added' and 'removed' actions, along with specific 'member_group.added' and 'member_group.removed' full actions, provides comprehensive audit logging for the new multi-group membership feature.

Also applies to: 151-152

controlplane/src/core/bufservices/user/inviteUser.ts (1)

85-102: Well-implemented multi-group invitation logic.

The implementation correctly:

  • Validates each group exists within the organization
  • Collects validated group IDs for the invitation
  • Ensures at least one group is provided for new invitations
  • Maintains backward compatibility for re-invitations

The error handling and validation flow are appropriate for the multi-group feature.

Also applies to: 168-175, 204-204

proto/wg/cosmo/platform/v1/platform.proto (1)

999-999: LGTM: Multi-group support correctly implemented.

The change from single groupId to repeated string groups properly enables inviting users to multiple groups simultaneously while maintaining backward compatibility through proper field numbering.

controlplane/src/bin/migrate-groups.ts (1)

426-450: LGTM: Migration correctly implements multi-group data model.

The migration properly transitions from the single groupId column to the many-to-many organizationInvitationGroups join table. The two-step approach (query pending invitations, then insert join records) maintains data integrity while preserving the original business logic of assigning pending invitations to the developer group.

controlplane/test/test-util.ts (1)

425-457: LGTM! Well-structured test utility function.

The function properly handles group creation with optional rules, includes appropriate validation with expect statements, and follows good testing practices.

studio/src/components/members/update-member-group-dialog.tsx (1)

15-15: LGTM! Comprehensive update for multi-group support.

The component has been properly updated to support multiple group selection:

  • State management correctly handles arrays
  • Effect properly initializes from member props
  • Validation logic updated for array length
  • Mutation input correctly sends array of group IDs
  • All user-facing text updated to plural forms

Also applies to: 18-18, 26-31, 36-36, 43-43, 49-49, 57-57, 64-64, 84-86, 91-95, 100-100

connect/src/wg/cosmo/platform/v1/platform_pb.ts (4)

7445-7447: LGTM: Correct implementation of repeated groups field.

The migration from single group to repeated groups is implemented correctly with proper TypeScript array typing and protobuf field definition.


7458-7458: LGTM: Proper protobuf field definition for repeated groups.

The field definition correctly specifies the repeated string type for the groups field while maintaining the same field number (2), which preserves backward compatibility.


10477-10478: No discrepancies in UpdateOrgMemberGroupRequest schema
The generated TS fields (orgMemberUserID = 1, groups = 2) exactly match the .proto definition for UpdateOrgMemberGroupRequest. No renumbering or evolution issues detected.


10460-10462: No backward-incompatible changes in UpdateOrgMemberGroupRequest field numbers

Our search shows that in proto/wg/cosmo/platform/v1/platform.proto the message is defined as:

• orgMemberUserID = 1
• groups = 2

and there is no record of these numbers having been reassigned or a prior field removed. Existing clients should continue to work without change.

File:

  • proto/wg/cosmo/platform/v1/platform.proto (lines 1366–1367)
controlplane/migrations/0125_acoustic_jackpot.sql (3)

1-5: LGTM - Clean join table structure.

The new join table properly implements the many-to-many relationship between invitations and groups with appropriate UUID types and constraints.


7-11: LGTM - Proper data migration with NULL handling.

The migration correctly transfers existing single group associations to the new join table while properly filtering out NULL values to prevent constraint violations.


13-26: LGTM - Proper constraint management with appropriate cascade behavior.

The foreign key constraints are correctly configured with cascade deletes, and the duplicate constraint error handling ensures the migration is idempotent.

controlplane/src/db/schema.ts (1)

1541-1558: LGTM - Well-structured join table with proper relationships.

The new organizationInvitationGroups table correctly implements the many-to-many relationship with appropriate cascade delete behavior and indexes on foreign keys. The schema aligns perfectly with the migration file.

studio/src/pages/[organizationSlug]/members.tsx (5)

70-70: LGTM - Correct import for multi-group selection.

The import of MultiGroupSelect component is properly added to support the new multi-group functionality.


74-74: LGTM - Proper schema validation for multiple groups.

The schema correctly validates an array of UUID strings with a minimum of one group, ensuring users must select at least one group when inviting members.


92-92: LGTM - Correct form state watching for groups array.

The form properly watches the groups array field to track selected groups.


104-104: LGTM - Mutation correctly sends groups array.

The mutation call properly sends the groups array to the backend API, aligning with the multi-group invitation functionality.


136-144: LGTM - Proper MultiGroupSelect integration.

The component is correctly integrated with proper value binding and form state updates. The mapping from group objects to group IDs maintains proper form state management.

controlplane/test/organization-groups.test.ts (1)

411-478: Comprehensive test for multiple group membership

Excellent test coverage for the multiple group membership feature, including verification of both database state and Keycloak synchronization.

controlplane/src/core/repositories/OrganizationInvitationRepository.ts (1)

196-204: Consider batch insert optimization

Good use of batch insert for invitation groups. This is more efficient than individual inserts.

Comment thread controlplane/src/core/bufservices/user/acceptOrDeclineInvitation.ts
Comment thread controlplane/src/core/repositories/OrganizationGroupRepository.ts
Comment thread studio/src/pages/[organizationSlug]/members.tsx
Comment thread controlplane/src/core/bufservices/user/updateOrgMemberGroup.ts
Comment thread controlplane/test/organization-groups.test.ts Outdated
@wilsonrivera wilsonrivera merged commit 1e67757 into main Jul 1, 2025
46 of 47 checks passed
@wilsonrivera wilsonrivera deleted the wilson/eng-7240-allow-users-to-be-part-of-multiple-groups branch July 1, 2025 14:49
@coderabbitai coderabbitai Bot mentioned this pull request Jul 24, 2025
5 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Nov 21, 2025
5 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Dec 19, 2025
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants