Performance issue in getting permission matrix

Nov 26, 2013 at 8:39 AM
Hi,

At a customer I don't have access to a development environment, so I need to do everything OOTB. Ofcourse SharePoint isn't made to be OOTB for every customer, hence SPServices Library.

I need to build functionalitity to get all the permissions for a user on all the sites.

So what do I do?

First there is a selection on the user ($().SPServices.SPAutocomplete), this gives no problems.
Then I select a subset of sites we want to query (due to performance issue, explained later)
What I do then is get all the groups the user is part of and put it in an array. Next I get all the webs (and children => recursive) and create an html-table where the TD's have unique ID's. This also runs without any major issues.

But then, I need to get for every cell the permission of the group and web (row-column combination). This is not performant at all and makes my browser crash all the time, even for very little gets. I have even implemented the code with an interval, so that I can manage the timeouts. And it works if I put the timeout high enough, but this is so slow and takes ages to complete for big subset of sites and alot of groups.

So basically I need a matrix with permissions for a specific user (later to be implemented for ALL groups).

Does anyone have any idea where I'm going wrong? I have tested this with 0.7.2, SP2013.01 and SP2013.02. I have read a post from Paul stating something about caching (https://spservices.codeplex.com/workitem/10182), that's why I moved to SP2013.02.

Below my code.
<h1>Rechten per gebruiker</h1>
<table cellpadding="3" cellspacing="3">
    <tr>
        <td><b>Gebruiker:</b></td>
        <td><input type="text" title="gebruiker" id="txtGebruiker" /></td>
    </tr>
    <tr>
        <td><b>Luik:</b></td>
        <td>
            <select id="ddlLuik" style="width: 155px;">
                <option value="URL TO ENVIRONMENT HERE">Platform</option>
                <option value="URL TO ENVIRONMENT HERE/SUB">Projectruimtes</option>
                <option value="URL TO ENVIRONMENT HERE/SUB2">Opvolgsystemen</option>
                <option value="URL TO ENVIRONMENT HERE/SUB3">Vergaderruimtes</option>
                <option value="URL TO ENVIRONMENT HERE/SUB4">Kennisbibliotheken</option>
                <option value="URL TO ENVIRONMENT HERE/SUB5">Teamsites</option>
            </select>
        </td>
    </tr>
    <tr>
        <td colspan="2" align="right"><input type="button" id="btnLookup" value="Zoek rechten op" /></td>
    </tr>
</table>
<input type="hidden" id="hdnUserName" />
<br/>
<div id="loader" style="display: none; width: 233px;"><div class="progress-label">Laden van permissies...</div></div>
<div id="myRACI" style="display: none;"></div>
<br/>
<div id="myRaciLegende"></div>

<script type="text/javascript" src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<script type="text/javascript">
    var strURL = "URL TO ENVIRONMENT HERE", objGroups = new Array(), countPerms = 0;    
    var progressbar = $( "#loader" ), progressLabel = $( ".progress-label" );
    var intervalObj;
    var timeout = 500;
 
    progressbar.progressbar({value: false, change: function(){progressLabel.text( progressbar.progressbar( "value" ) + "%" );}});
    
    $(document).ready(function() {
        $("head").append("<link rel='stylesheet' type='text/css' href='http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css' />");
        $("head").append("<style>.ui-progressbar {position: relative;} .progress-label {position: absolute;left: 50%;top: 4px;font-weight: bold;text-shadow: 1px 1px 0 #fff;color: #0070BA;}</style>");
    
        $().SPServices.SPAutocomplete({
            WebURL: strURL,
            sourceList: "Lijst met gebruikersgegevens",
            sourceColumn: "ImnName",
            columnName: "gebruiker",
            CAMLQuery: "<IsNotNull><FieldRef Name='EMail' /></IsNotNull>",
            numChars: 1,
            ignoreCase: true,
            highlightClass: "ms-bold",
            slideDownSpeed: 100,
            debug: true
        });
        
        // Build the RACI legend
        $().SPServices({
            operation: "GetRolesAndPermissionsForSite",
            webURL: strURL,
            async: false,
            cacheXML: false,
            completefunc:   function(xData, Status) {
                                $("#myRaciLegende").html(processRACILegende(xData.responseXML));
                            }
        });
    });
    

    $("#btnLookup").bind("click", function(){
        if($("#txtGebruiker").val() == ""){
            $("#txtGebruiker").attr("style", "border: 1px solid red;");
            $("#loader").hide();

            return;
        }
        else{$("#txtGebruiker").attr("style", "border: 1px solid #A4A4A4;");}
        
        objGroups.clear();$("#loader").show();$("#myRACI").hide();$("#hdnUserName").val("");progressbar.progressbar( "value", 0);
            
        $().SPServices({
        operation: "GetListItems",
        webURL: strURL,
        listName: "Lijst met gebruikersgegevens",
        async: false,
        cacheXML: false,
        CAMLViewFields: "<ViewFields><FieldRef Name='UserName' /></ViewFields>",
        CAMLQuery: "<Query><Where><Eq><FieldRef Name='ImnName' /><Value Type='Text'>" + $("#txtGebruiker").val() + "</Value></Eq></Where></Query>",
        completefunc: function(xData, Status) {
                            if(Status == "success"){
                                $(xData.responseXML).filterNode("z:row").each(function() {
                                    $("#hdnUserName").val("alfa\\" + $(this).attr("ows_UserName"));
                                });
                            }
                        }
        });     
        
        if($("#hdnUserName").val() != "alfa\\undefined" && $("#hdnUserName").val() != ""){
            // Build matrix
            $().SPServices({
                operation: "GetWebCollection",
                webURL: $("#ddlLuik").val(),
                async: false,
                cacheXML: false,
                completefunc:   function(xData, Status) {
                                    $("#myRACI").html(processWebsXmlSitesRACI(xData.responseXML));
                                }
            });
            
            countPerms = $(".webPerm").length;
            
            if(countPerms <= 5) timeout = 100;
            if(countPerms > 5 && countPerms <= 10) timeout = 150;
            if(countPerms > 10 && countPerms <= 20) timeout = 350;
            if(countPerms > 20 && countPerms <= 30) timeout = 500;
    
            intervalObj = setInterval(getWebPerms, timeout);
        }
        else{$("#myRACI").html("<span style='color: red;'>Deze gebruiker kon niet worden gevonden.</span>");$("#myRaciLegende").hide();}
    });
    
    $("#txtGebruiker").bind("keydown", function(){$("#txtGebruiker").attr("style", "border: 1px solid #A4A4A4;");});    
    $.fn.filterNode = function(name){return this.find('*').filter(function(){return this.nodeName === name;});};    
    Array.prototype.clear = function(){while (this.length > 0) {this.pop();}};
    
    function getWebPerms(){
        var k = 0;
        
        progressbar.progressbar( "value", Math.round(((countPerms - $(".webPerm").length) / countPerms) * 100));
        
        obj = $(".webPerm").first();
        var url = $(obj).attr("id").substring(0, $(obj).attr("id").lastIndexOf('_')), gId = $(obj).attr("id").substring($(obj).attr("id").lastIndexOf('_') + 1);
                    
        $(obj).removeClass("webPerm").html("").SPServices({
            operation: "GetRoleCollectionFromGroup",
            webURL: url,
            groupName: objGroups[gId],
            async: false,
            cacheXML: false,
            completefunc: function (xData, Status) {
                            if(Status== "success"){
                                strPerms = "",strTypes = "";                    
                                $(xData.responseXML).filterNode("Role").each(function(){
                                    strName = "";strTempName = $(this).attr("Name").split(' ');
                                    for(i = 0; i < strTempName.length; i++){strName += strTempName[i].substring(0, 1).toUpperCase();}               
                                    strPerms += strName + ", ";strTypes += $(this).attr("Name") + ", ";
                                });                                                                             
                                $(obj).append("<span title='" + strTypes.substring(0, strTypes.length - 2) + "'>" + strPerms.substring(0, strPerms.length - 2) + "</span>");
                            }
                        }
        });
        
        if($(".webPerm").length < 1){$("#loader").hide();$("#myRACI").show();clearInterval(intervalObj);}
    }
    
    function processWebsXmlSitesRACI(objDoc){
        function processWebs(m_objNode, m_intIndex){
            strOutput += "<tr>";
            strOutput += "<td class='nodot' style='padding-left:" + (m_intIndex * 20) + "px; padding-right:" + (m_intIndex * 20) + "px;'><a href='" + $(m_objNode).attr("Url") + "'>" + $(m_objNode).attr("Title") + "</a></td>";
            
            for(j = 0; j < objGroups.length; j++){
                strOutput += "<td id='" + $(m_objNode).attr("Url") + "_" + j + "' class='webPerm'></td>";   
            }   
            
            strOutput += "</tr>";       
            $().SPServices({
                 operation: "GetWebCollection",
                 webURL: $(m_objNode).attr("Url"),
                 async: false,
                 cacheXML: false,
                 completefunc: function(xData, Status) {            
                                    $(xData.responseXML).filterNode("Web").each(function(){processWebs(this, (m_intIndex + 1));});  
                                  }
            });
        }
        
        strOutput = "";     
        if($(objDoc).filterNode("Web").length > 0){
            strOutput += "<table class='raci'><tr><th>Site</th>";       
            $().SPServices({
                 operation: "GetGroupCollectionFromUser",
                 userLoginName: $("#hdnUserName").val(),
                 async: false,
                 cacheXML: false,
                 completefunc: function(xData, Status) {
                                    $(xData.responseXML).filterNode("Group").each(function(){
                                        objGroups.push($(this).attr("Name"));
                                        strOutput += "      <th><a href='/sites/dewi/_layouts/people.aspx?MembershipGroupId=" + $(this).attr("ID") + "' target='_blank'>" + $(this).attr("Name") + "</a></th>";
                                    }); 
                                  }
            });
            //strOutput += "<th>Site</th></tr>";
            strOutput += "</tr>";
            $(objDoc).filterNode("Web").each(function(){processWebs(this, 1);});
            strOutput += "</table>";
        }       
        return (strOutput);
    }
    
    function processRACILegende(objDoc){
        strOutput = "<table class='raciLegende'><tr><th colspan='3' style='text-align: left;'>Legende</th></tr>";                   
        var sorted = $(objDoc).filterNode("Role").sort( function(a,b){var at = $(a).attr('Name'), bt = $(b).attr('Name');return (at < bt) ? -1 : 1;});                                                          
        $(sorted).each(function(){
            strName = "";strTempName = $(this).attr("Name").split(' ');
            for(i = 0; i < strTempName.length; i++){
                strName += strTempName[i].substring(0, 1).toUpperCase();
            }
            
            strOutput += "<tr><td><b>" + strName + "</b></td><td>" + $(this).attr("Name") + "</td><td>" + $(this).attr("Description") + "</td></tr>";
        });
        
        strOutput += "</table>";        
        return (strOutput);
    }
</script>
Kind regards,
Laurent
Nov 26, 2013 at 8:40 AM
This is mostly written in Dutch, but if you copy the file and change the URL and the values of the dropdowns to URL's on your environment, you should get a working example.

I just need it to be faster, alot faster! I'm pulling my hair here.
Coordinator
Nov 26, 2013 at 12:43 PM
Without trying to run this, I can see that you're trying to make all the calls synchronously. That'll hang up the browser for a long time.

Take a look at jQuery promises. As of version 2013.01, SPServices returns a promise from every call, and by processing using those promises, you should be able to make this work. There's a link to one of my blog posts on the home page showing the differences in using promises vs. not.
◾2013-03-09 - Comparing SPServices 2013.01 Calls with Async vs. Promises Methods

M.