1

I am trying to come up with a way to get ALL queue Users (not just the users who have a groupMember record with UserOrGroupId matching theirs) via apex.

Queue membership can also be granted via User, Group, Role, Role and subordinates, Portal User, Portal Role, Portal Role and subordinates, and also if you are above a user in the role hierarchy, you will be granted access.

I cannot be the first person who has solved this problem, and there is a lot to keep track of here.

Thanks!

3 Answers 3

4

I think you can combine a couple answers to questions and be able to get something working well.


You listed all the requirements in all the ways someone can be added to a queue. If we simplify it, you have two paths you need to handle based on UserOrGroupId:

  1. Users
  2. Groups (encompasses role, groups, etc)

This is the start of any logic/code you'd have to gather it

SELECT UserOrGroupId from GroupMember where Group.Name = 'insert name here'

You would need to check if the UserOrGroupId is indeed a user or group (getSObjectType())

  • If it's a User, you can add it to a list to save for your final query against User. This question covers this and is the simple part.
  • If it's a group, you have to perform another query against Group
SELECT RelatedId,Type,DeveloperName FROM Group WHERE Id in: groupIds

The Type will be what you need to figure out your next set of logic (I excluded some types, see documentation of all values):

  1. Regular (a regular public group)
  2. Role
  3. RoleAndSubordinates
  4. Organization (all users in org)

For these, you can have different logic to end up with what you want - UserIds.

if it's a regular group

Use the same query as above on Group. Be aware, it's possible to have a group that then has other roles/groups associated with it. You have to follow through the web.

if it's a role

query the active users in that role

SELECT Id FROM User WHERE UserRoleId =: roleId AND isActive = true

if it's a role and subordinate

Query the roles and roles that look up to this role. This question goes over figuring out this problem.

if it's an organization

It's simply all users who are in the queue.


Maps will be your friend here. You'll most likely want to query all Groups, possibly all GroupMembers, and all roles in the organization in 3 queries to start to build maps you can reference throughout.

3

Kris, thank you for the direction.

After much thought and time, I have found a few things :

  1. Group DeveloperName is NOT unique - this was a bit of a surprise for me.
  2. You must query for all Groups and their members anyway to construct this for Queues, so I extended the functionality to include all active users for any group in the system.
  3. Group has an attribute called DoesIncludeBosses which if true will include any users in the role hierarchy above the group members.
  4. The response that is returned holds a lot of data, so we will lazy load it to save queries and run time calculations when used multiple times.

Here is the solution which I came up with. It genereates a Map<String,Set<Id>> where the key is group.DeveloperName + '-' + group.Type and the value is a set of all active users for that group. It uses the Role Hierarchy which was described in the link posted by Kris.

Here are some of the helper methods for roles

public static Map<Id, List<UserRole>> hierarchy {
        get {
            hierarchy = new Map<Id, List<UserRole>>();
            for (UserRole role : userRoleMap.values()) {
                if (!hierarchy.containsKey(role.ParentRoleId)) {
                    hierarchy.put(role.ParentRoleId, new List<UserRole>());
                }
                hierarchy.get(role.ParentRoleId).add(role);
            }
            return hierarchy;
        }
        private set;
    }

    public static List<UserRole> getChildren(Id userRoleId) {
        return hierarchy.containsKey(userRoleId) ? hierarchy.get(userRoleId) : new List<UserRole>();
    }

    public static Set<Id> getSubHierarchy(Id userRoleId) {
        Set<Id> roleIds = new Set<Id> { userRoleId };
        for (UserRole childRole : getChildren(userRoleId)) {
            roleIds.addAll(getSubHierarchy(childRole.Id));
        }
        return roleIds;
    }

    public static Set<Id> getParentRoles(Id userRoleId) {
        Set<Id> roleIds = new Set<Id>();
        UserRole currentRole = userRoleMap.get(userRoleId);
        while (currentRole.ParentRoleId != null) {
            roleIds.add(currentRole.ParentRoleId);
            currentRole = userRoleMap.get(currentRole.ParentRoleId);
        }
        return roleIds;
    }

    private static Map<Id,Set<Id>> getRoleIdToUsers(Map<Id,User> userMap) {
        Map<Id, Set<Id>> roleIdToUsers = new Map<Id,Set<Id>>();
        for (User u : userMap.values()) {
            if (u.UserRoleId != null ){
                if (!roleIdToUsers.containsKey(u.UserRoleId)) {
                    roleIdToUsers.put(u.UserRoleId, new Set<Id>());
                }
                roleIdToUsers.get(u.UserRoleId).add(u.Id);
            }
        }
        return roleIdToUsers;
    }

And here is the main method which does the calculation for you. The basic strategy here is to calculate the entire group based on type. If the Type is Queue or Regular then the groupMembers can be other groups, so we must calculate their membership recursively.

@TestVisible
    public static Map<String,Set<Id>> groupDeveloperNameAndTypeToActiveUserIds {
        get {
            if (groupDeveloperNameAndTypeToActiveUserIds == null) {
                groupDeveloperNameAndTypeToActiveUserIds = new  Map<String,Set<Id>>();
                //query for all relevant data
                Map<Id,Group> groupMap = new Map<Id,Group>([SELECT Id, DeveloperName, DoesIncludeBosses, Type, RelatedId, (SELECT UserOrGroupId FROM GroupMembers) FROM Group]);
                Map<Id,User> userMap = new Map<Id,User>([SELECT Id, IsActive, IsPortalEnabled, UserRoleId FROM User WHERE IsActive = true]);
                Map<Id,Set<Id>> roleIdToUsers = getRoleIdToUsers(userMap);
                //calculate group users for each individual group
                for (Group g : groupMap.values()) {
                    groupDeveloperNameAndTypeToActiveUserIds.put(g.DeveloperName + '-' + g.Type, getGroupUsers(g, groupMap, userMap, roleIdToUsers));
                }
                //add users above in the hierarchy
                for (Group g : groupMap.values()) {
                    if (g.DoesIncludeBosses == true) {
                        Set<Id> rolesForUsersInThisGroup = new Set<Id>();
                        for (id userId : groupDeveloperNameAndTypeToActiveUserIds.get(g.DeveloperName + '-' + g.Type)) {
                            User u = userMap.get(userId);
                            if (u.UserRoleId != null) {
                                rolesForUsersInThisGroup.add(u.UserRoleId);
                            }
                        }
                        Set<Id> allParentRoles = new Set<Id>();
                        for (Id roleId : rolesForUsersInThisGroup) {
                            allParentRoles.addAll(getParentRoles(roleId));
                        }
                        for (Id parentRoleId : allParentRoles) {
                            groupDeveloperNameAndTypeToActiveUserIds.get(g.DeveloperName + '-' + g.Type).addAll(roleIdToUsers.get(parentRoleId));
                        }
                    }
                }
            }
            return groupDeveloperNameAndTypeToActiveUserIds;
        }
        private set;
    }


    static Set<Id> getGroupUsers(Group g, Map<Id,Group> groupMap, Map<Id,User> userMap, Map<Id,Set<Id>> roleIdToUsers) {
        Set<Id> usersForThisGroup = new Set<Id>();
        switch on g.Type {
            when 'Organization' {
                for (User u : userMap.values()) {
                    if (!u.IsPortalEnabled) {
                        usersForThisGroup.add(u.id);
                    }
                }
            }
            when 'Regular' {
                for (GroupMember gm : g.GroupMembers) {
                    String sobjectType = gm.UserOrGroupId.getSObjectType().getDescribe().getName();
                    if (sobjectType == 'User') {
                        if (userMap.containsKey(gm.UserOrGroupId)) {
                            usersForThisGroup.add(gm.UserOrGroupId);
                        }
                    } else {
                        //recurse down the group tree
                        usersForThisGroup.addAll(getGroupUsers(groupmap.get(gm.UserOrGroupId), groupMap, userMap, roleIdToUsers));
                    }
                }
            }
            when 'Queue' {
                for (GroupMember gm : g.GroupMembers) {
                    String sobjectType = gm.UserOrGroupId.getSObjectType().getDescribe().getName();
                    if (sobjectType == 'User') {
                        if (userMap.containsKey(gm.UserOrGroupId)) {
                            usersForThisGroup.add(gm.UserOrGroupId);
                        }
                    } else {
                        //recurse down the group tree
                        usersForThisGroup.addAll(getGroupUsers(groupmap.get(gm.UserOrGroupId), groupMap, userMap, roleIdToUsers));
                    }
                }
            }
            when 'AllCustomerPortal' {
                for (User u : userMap.values()) {
                    if (u.IsPortalEnabled) {
                        usersForThisGroup.add(u.id);
                    }
                }
            }
            when 'Role' {
                if (roleIdToUsers.containsKey(g.RelatedId)) {
                    usersForThisGroup.addAll(roleIdToUsers.get(g.RelatedId));
                }
            }
            when 'RoleAndSubordinates' {
                Set<Id> subordinateRoles = getSubHierarchy(g.RelatedId);
                for (Id roleId : subordinateRoles) {
                    if (roleIdToUsers.keySet().contains(roleId)) {
                        usersForThisGroup.addAll(roleIdToUsers.get(g.RelatedId));
                    }
                }
            }
            when 'RoleAndSubordinatesInternal' {
                Set<Id> subordinateRoles = getSubHierarchy(g.RelatedId);
                for (Id roleId : subordinateRoles) {
                    if (roleIdToUsers.keySet().contains(roleId)) {
                        for (Id userId : (roleIdToUsers.get(g.RelatedId))) {
                            if (!userMap.get(userId).IsPortalEnabled) {
                                usersForThisGroup.add(userId);
                            }
                        }
                    }
                }
            }
        }
        return usersForThisGroup;

    }
0

maybe u can try this

List<Group> queues = new List<Group>([
    SELECT Id, (SELECT Id FROM GroupMembers)
    FROM Group 
    WHERE Type='Queue' AND Name IN :groupNames
]);
for(Group queue : queues){
    for(GroupMember member : queue.GroupMembers){
        System.debug(member);
    }
}

to retain the data structure u can store it in a map where the key should be the group id and the value is a list of group members.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .