@@ -230,7 +230,7 @@ def update(self) -> UserGroup:
230
230
Raises:
231
231
ValueError: If group ID or name is not set, or if projects don't exist.
232
232
ResourceNotFoundError: If the group or projects are not found.
233
- UnprocessableEntityError: If user validation fails.
233
+ UnprocessableEntityError: If user validation fails or users have workspace-level org roles .
234
234
"""
235
235
if not self .id :
236
236
raise ValueError ("Group id is required" )
@@ -247,8 +247,11 @@ def update(self) -> UserGroup:
247
247
)
248
248
249
249
# Filter eligible users and build user roles
250
- eligible_users = self ._filter_project_based_users ()
251
- user_roles = self ._build_user_roles (eligible_users )
250
+ try :
251
+ eligible_users = self ._filter_project_based_users ()
252
+ user_roles = self ._build_user_roles (eligible_users )
253
+ except ValueError as e :
254
+ raise UnprocessableEntityError (str (e )) from e
252
255
253
256
query = """
254
257
mutation UpdateUserGroupPyApi($id: ID!, $name: String!, $description: String, $color: String!, $projectIds: [ID!]!, $userRoles: [UserRoleInput!], $notifyMembers: Boolean) {
@@ -312,7 +315,7 @@ def create(self) -> UserGroup:
312
315
313
316
Raises:
314
317
ValueError: If group already has ID, name is invalid, or projects don't exist.
315
- ResourceCreationError: If creation fails or user validation fails.
318
+ ResourceCreationError: If creation fails, user validation fails, or users have workspace-level org roles .
316
319
ResourceConflict: If a group with the same name already exists.
317
320
"""
318
321
if self .id :
@@ -330,8 +333,11 @@ def create(self) -> UserGroup:
330
333
)
331
334
332
335
# Filter eligible users and build user roles
333
- eligible_users = self ._filter_project_based_users ()
334
- user_roles = self ._build_user_roles (eligible_users )
336
+ try :
337
+ eligible_users = self ._filter_project_based_users ()
338
+ user_roles = self ._build_user_roles (eligible_users )
339
+ except ValueError as e :
340
+ raise ResourceCreationError (str (e )) from e
335
341
336
342
query = """
337
343
mutation CreateUserGroupPyApi($name: String!, $description: String, $color: String!, $projectIds: [ID!]!, $userRoles: [UserRoleInput!]!, $notifyMembers: Boolean, $roleId: String, $searchQuery: AlignerrSearchServiceQuery) {
@@ -496,11 +502,14 @@ def get_user_groups(
496
502
def _filter_project_based_users (self ) -> Set [User ]:
497
503
"""Filter users to only include users eligible for UserGroups.
498
504
499
- Filters out users with specific admin organization roles that cannot be
500
- added to UserGroups. Most users should be eligible .
505
+ Only project-based users (org role "NONE") can be added to UserGroups.
506
+ Users with any workspace-level organization role cannot be added .
501
507
502
508
Returns:
503
509
Set of users that are eligible to be added to the group.
510
+
511
+ Raises:
512
+ ValueError: If any user has a workspace-level organization role.
504
513
"""
505
514
all_users = set ()
506
515
for member in self .members :
@@ -509,63 +518,52 @@ def _filter_project_based_users(self) -> Set[User]:
509
518
if not all_users :
510
519
return set ()
511
520
512
- user_ids = [user .uid for user in all_users ]
513
- query = """
514
- query CheckUserOrgRolesPyApi($userIds: [ID!]!) {
515
- users(where: {id_in: $userIds}) {
516
- id
517
- orgRole { id name }
518
- }
519
- }
520
- """
521
+ # Check each user's org role directly
522
+ invalid_users = []
523
+ eligible_users = set ()
521
524
522
- try :
523
- result = self .client .execute (query , {"userIds" : user_ids })
524
- if not result or "users" not in result :
525
- return all_users # Fallback: let server handle validation
526
-
527
- # Check for users with org roles that cannot be used in UserGroups
528
- # Only users with no org role (project-based users) can be assigned to UserGroups
529
- eligible_user_ids = set ()
530
- invalid_users = []
531
-
532
- for user_data in result ["users" ]:
533
- org_role = user_data .get ("orgRole" )
534
- user_id = user_data ["id" ]
535
- user_email = user_data .get ("email" , "unknown" )
536
-
537
- if org_role is None :
538
- # Users with no org role (project-based users) are eligible
539
- eligible_user_ids .add (user_id )
525
+ for user in all_users :
526
+ try :
527
+ # Get the user's organization role directly
528
+ org_role = user .org_role ()
529
+ if org_role is None or org_role .name .upper () == "NONE" :
530
+ # Users with no org role or "NONE" role are project-based and eligible
531
+ eligible_users .add (user )
540
532
else :
541
- # Users with ANY workspace org role cannot be assigned to UserGroups
533
+ # Users with any workspace org role cannot be assigned to UserGroups
542
534
invalid_users .append (
543
535
{
544
- "id" : user_id ,
545
- "email" : user_email ,
546
- "org_role" : org_role .get ( " name" ) ,
536
+ "id" : user . uid ,
537
+ "email" : getattr ( user , "email" , "unknown" ) ,
538
+ "org_role" : org_role .name ,
547
539
}
548
540
)
541
+ except Exception as e :
542
+ # If we can't determine the user's role, treat as invalid for safety
543
+ invalid_users .append (
544
+ {
545
+ "id" : user .uid ,
546
+ "email" : getattr (user , "email" , "unknown" ),
547
+ "org_role" : f"unknown (error: { str (e )} )" ,
548
+ }
549
+ )
549
550
550
- # Raise error if any invalid users found
551
- if invalid_users :
552
- error_details = []
553
- for user in invalid_users :
554
- error_details .append (
555
- f"User { user ['id' ]} ({ user ['email' ]} ) has org role '{ user ['org_role' ]} '"
556
- )
557
-
558
- raise ValueError (
559
- f"Cannot create UserGroup with users who have organization roles. "
560
- f"Only project-based users (no org role) can be assigned to UserGroups.\n "
561
- f"Invalid users:\n "
562
- + "\n " .join (f" • { detail } " for detail in error_details )
551
+ # Raise error if any invalid users found
552
+ if invalid_users :
553
+ error_details = []
554
+ for user_info in invalid_users :
555
+ error_details .append (
556
+ f"User { user_info ['id' ]} ({ user_info ['email' ]} ) has org role '{ user_info ['org_role' ]} '"
563
557
)
564
558
565
- return {user for user in all_users if user .uid in eligible_user_ids }
559
+ raise ValueError (
560
+ f"Cannot create UserGroup with users who have organization roles. "
561
+ f"Only project-based users (no org role or role 'NONE') can be assigned to UserGroups.\n "
562
+ f"Invalid users:\n "
563
+ + "\n " .join (f" • { detail } " for detail in error_details )
564
+ )
566
565
567
- except Exception :
568
- return all_users # Fallback: let server handle validation
566
+ return eligible_users
569
567
570
568
def _build_user_roles (
571
569
self , eligible_users : Set [User ]
@@ -679,6 +677,12 @@ def _get_members_set(
679
677
member_nodes = members_data .get ("nodes" , [])
680
678
user_group_roles = members_data .get ("userGroupRoles" , [])
681
679
680
+ # Get all roles to map IDs to names
681
+ from labelbox .schema .role import get_roles
682
+
683
+ all_roles = get_roles (self .client )
684
+ role_id_to_role = {role .uid : role for role in all_roles .values ()}
685
+
682
686
# Create a mapping from userId to roleId
683
687
user_role_mapping = {
684
688
role_data ["userId" ]: role_data ["roleId" ]
@@ -694,15 +698,9 @@ def _get_members_set(
694
698
695
699
# Get the role for this user from the mapping
696
700
role_id = user_role_mapping .get (node ["id" ])
697
- if role_id :
698
- # We need to fetch the role details since we only have the roleId
699
- # For now, create a minimal Role object with just the ID
700
- role_values : defaultdict [str , Any ] = defaultdict (lambda : None )
701
- role_values ["id" ] = role_id
702
- # We don't have the role name from this response, so we'll leave it as None
703
- # The Role object will fetch the name when needed
704
- role = Role (self .client , role_values )
705
-
701
+ if role_id and role_id in role_id_to_role :
702
+ # Use the actual Role object with proper name resolution
703
+ role = role_id_to_role [role_id ]
706
704
members .add (UserGroupMember (user = user , role = role ))
707
705
708
706
return members
0 commit comments