Skip to content

Commit 06e5429

Browse files
authored
Fix _get_members_set to get role name (#1993)
1 parent b2ec986 commit 06e5429

File tree

3 files changed

+296
-145
lines changed

3 files changed

+296
-145
lines changed

libs/labelbox/src/labelbox/schema/user_group.py

Lines changed: 62 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ def update(self) -> UserGroup:
230230
Raises:
231231
ValueError: If group ID or name is not set, or if projects don't exist.
232232
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.
234234
"""
235235
if not self.id:
236236
raise ValueError("Group id is required")
@@ -247,8 +247,11 @@ def update(self) -> UserGroup:
247247
)
248248

249249
# 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
252255

253256
query = """
254257
mutation UpdateUserGroupPyApi($id: ID!, $name: String!, $description: String, $color: String!, $projectIds: [ID!]!, $userRoles: [UserRoleInput!], $notifyMembers: Boolean) {
@@ -312,7 +315,7 @@ def create(self) -> UserGroup:
312315
313316
Raises:
314317
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.
316319
ResourceConflict: If a group with the same name already exists.
317320
"""
318321
if self.id:
@@ -330,8 +333,11 @@ def create(self) -> UserGroup:
330333
)
331334

332335
# 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
335341

336342
query = """
337343
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(
496502
def _filter_project_based_users(self) -> Set[User]:
497503
"""Filter users to only include users eligible for UserGroups.
498504
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.
501507
502508
Returns:
503509
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.
504513
"""
505514
all_users = set()
506515
for member in self.members:
@@ -509,63 +518,52 @@ def _filter_project_based_users(self) -> Set[User]:
509518
if not all_users:
510519
return set()
511520

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()
521524

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)
540532
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
542534
invalid_users.append(
543535
{
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,
547539
}
548540
)
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+
)
549550

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']}'"
563557
)
564558

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+
)
566565

567-
except Exception:
568-
return all_users # Fallback: let server handle validation
566+
return eligible_users
569567

570568
def _build_user_roles(
571569
self, eligible_users: Set[User]
@@ -679,6 +677,12 @@ def _get_members_set(
679677
member_nodes = members_data.get("nodes", [])
680678
user_group_roles = members_data.get("userGroupRoles", [])
681679

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+
682686
# Create a mapping from userId to roleId
683687
user_role_mapping = {
684688
role_data["userId"]: role_data["roleId"]
@@ -694,15 +698,9 @@ def _get_members_set(
694698

695699
# Get the role for this user from the mapping
696700
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]
706704
members.add(UserGroupMember(user=user, role=role))
707705

708706
return members

0 commit comments

Comments
 (0)