Building an Org Role Hierarchy component in LWC

You might have observed that the role hierarchy which gets displayed in the org (Setup > Users > Roles) is still displayed using a VF component. Today, we will see how to create the same role hierarchy using LWC and Apex.



LWC provides us with a component called lightning-tree which displays a nested tree. We will make use of this component to display the role hierarchy in a tree structure. In Apex, we will query all the user roles and form a JSON structure which will be fed to the lightning-tree component.

Let's first see the LWC component - roleHierarchy:

roleHierarchy.html
<template> <div class="slds-p-around_medium lgc-bg"> <lightning-tree items={roles}
header="Roles">
</lightning-tree> </div> </template>
Here we have used the lightning-tree component which will display the data in a tree structure. The items attribute is of type Object which has nested items as an array of key-value pairs.

roleHierarchy.js
import { LightningElement, wire, track } from 'lwc'; import getRoles from '@salesforce/apex/RoleHierarchyCtrl.getRoles'; export default class RoleHierarchy extends LightningElement { @track roles; @track error; @wire(getRoles) wiredRoles({ error, data }) { if (data) { this.roles = JSON.parse(data); this.error = undefined; } else if (error) { this.error = error; this.roles = undefined; } } }
Here we have imported the method getRoles from RoleHierarchyCtrl Apex class. Then we have wired the getRoles method to a JS method named wiredRoles. The string result returned from the Apex controller is converted to a JSON object using JSON.parse function provided by JavaScript.

roleHierarchy.css
.lgc-bg { background-color: rgb(242, 242, 242); } .lgc-bg-inverse { background-color: rgb(22, 50, 92); }
roleHierarchy.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?> <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> <apiVersion>47.0</apiVersion> <isExposed>true</isExposed> <targets> <target>lightning__RecordPage</target> <target>lightning__AppPage</target> <target>lightning__HomePage</target> </targets> </LightningComponentBundle>
This exposes the component to app builder and makes it available to all the page types.

RoleHierarchyCtrl.cls
public class RoleHierarchyCtrl {
 
    @AuraEnabled(cacheable=true)
    public static String getRoles(){
        // get the top most role of the org - The top Role will have the field
        // value of ParentRoleId as null
        // This query will always return 1 record
        List<UserRole> topRole = [SELECT Id, Name, ParentRoleId FROM
                                    UserRole WHERE ParentRoleId = ''];
        // Get all the roles which are present under this parent role recursively
        // This is a flat list of all the roles present in the org
        List<UserRole> allRoles = RoleUtils.getChildRoles(topRole);
        Map<Id,List<RoleInfo>> parentToChildRolesMap = new Map<Id,List<RoleInfo>>();
        for(UserRole role: allRoles) {
            parentToChildRolesMap.put(role.Id, RoleUtils.getChildRoles(role.Id, allRoles));
        }
        List<RoleInfo> roleInfoList = new List<RoleInfo>();
        roleInfoList.add(new RoleInfo(topRole[0].Id, topRole[0].Name,
                            topRole[0].Name, parentToChildRolesMap.get(topRole[0].Id)));
        return JSON.serialize(roleInfoList, true);
    }
}
The getRoles method returns a JSON encoded string. In the first step, we query the top most role of the org. The trick here is that the top most role will have the value of ParentRoleId field as null. Rest all the roles in the org will have the value of ParentRoleId as populated since it is a mandatory field. Once we have the top most role, we then query its child roles, then the child roles of all those child roles and this goes on recursively until we have all the roles present in the org.
The next step is to create a map where the key is the Role Id and the corresponding value is the direct reporting roles to that particular Role Id. This is again a recursive call which will make an n-th level deep tree of all the roles present in the org.

RoleInfo.cls
public class RoleInfo implements Comparable{

    public Id roleId {get;set;}

    public String label {get;set;}

    public String name {get;set;}

    public List<RoleInfo> items {get;set;}

    public RoleInfo(Id roleId, String label, String name, List<RoleInfo> items){
        this.roleId = roleId;
        this.label = label;
        this.name = name;
        this.items = items;
    }

    public Integer compareTo(Object objToCompare){
        // sort alphabetically
        return name.compareTo(((RoleInfo)objToCompare).name);
    }
}
RoleInfo is a wrapper class that will hold the data in the format which is expected by the lightning-tree component. Here we have also implemented the Comparable interface so that we can sort the list of RoleInfo items by the value of their name. This gives us an output that is alphabetically arranged.

RoleUtils.cls
public class RoleUtils {
 
    public static boolean isFirstCall = true;
    public static List<UserRole> roles = new List<UserRole>();
 
    public static List<UserRole> getChildRoles(List<UserRole> userRoles){
        // add top most role to the rolesList
        if (isFirstCall){
            isFirstCall = false;
            roles.addAll(userRoles);
        }
        Set<Id> roleIds = new Set<Id>();
        for (UserRole ur: userRoles){
            roleIds.add(ur.Id);
        }
        List<UserRole> childRoles = [SELECT Id, Name, ParentRoleId FROM UserRole WHERE ParentRoleId =: roleIds];
        roles.addAll(childRoles);
        if (childRoles.size() > 0){
            // recursively call the same method to fetch all the child roles until no further
            // child roles are returned back
            getChildRoles(childRoles);
        }
        return roles;
}

    public static List<RoleInfo> getChildRoles(Id parentId, List<UserRole> roles){
        List<RoleInfo> childRoles = new List<RoleInfo>();
        for(UserRole role: roles){
            if(parentId == role.ParentRoleId) {
                // recursively call the same method to form a n-th level deep hierarchy
                childRoles.add(new RoleInfo(role.Id, role.Name, role.Name, getChildRoles(role.Id, roles)));
                // sort alphabetically
                childRoles.sort();
            }
        }
        return childRoles;
    }
}
This is the utility class which has 2 intelligent methods which are recursive in nature. The first method returns all the roles present in an org. The second method creates an n-th level deep tree of the role information retrieved by the first method.
I hope you have found this blog useful. If you have any questions, please post them in the comments section. Thank you for reading this.

Comments

  1. Hi! How can I make this component to see only the roles hierarchy for the logged in user not to all roles? Regards

    ReplyDelete

Post a Comment

Popular posts from this blog

Salesforce Hacks: System.LimitException: Too many queueable jobs added to the queue: 2

Salesforce Lightning: Countdown timer