Skip to main content

Dynamics crm apps: Enforce right app on login


     The new dynamics crm apps (Dynamics 365) is a great feature. Once it show up, I promised to my customer to create a few “masks” for crm to wear based on login user roles. So I did.
      All looks good but one unpleasant surprise. After login, the Default app is always loaded. Users then have to find desired app in the list and load it. Few additional clicks, time – not a good user experience.
      I have disable Default App from all users in settings but issue persisted.

Well, coding.

    1. Create an Enable Rule for some of home page ribbons.
I added it to “Refresh All” button. Thanks to Scott Durow’s Workbench.

    2. Code flow:

- Create a JSON object to map apps to roles. I named Apps after roles and now it makes the mapping very convenient.
* This hard code mapping works for me but there definitely a lot of option to generalize the selection *
- Get logged in user roles names and use JSON mapping to get a right app name.
- Validate current app name vs one we found to prevent overlapping.
- Get app id by name and create url with.
- Reload home page

I use OOB Progress Indicator just in case of delays. Not necessary.


Used articles:
Create and manage custom business apps in Customer Engagement using code

Code:

 /* Register as Enable Rule of "Refresh All" command in application ribbon  
 ************************************************************************/  
 var EnforceAppsScript = (function (window, document)  
 {  
   /*Privet properties and methods  
   *******************************/  
   var appPerRole = {  
     "Name of Role One": "Name of App One",  
     "Name of Role Two": "Name of App Two",  
     "Name of Role Three": "Name of App Three"  
   };  
   var appName = window.top.requiredAppName;  
   /*****************************************************************************************  
   Helper methods  
   *****************************************************************************************/  
   function webApiOutputParse(collection, propertiesArray)  
   {  
     var prop = [];  
     collection.forEach(function (row, i)  
     {  
       propertiesArray.forEach(function (p)  
       {  
         var f = p + "@OData.Community.Display.V1.FormattedValue";  
         prop.push((row[f] ? row[f] : row[p])); // Get formatted value if one exists for this property.   
       })  
     });  
     return prop;  
   }  
   function SdkRequest(action, clientUrl, uri, data, formattedValue, maxPageSize)  
   {  
     if (!RegExp(action, "g").test("POST PATCH PUT GET DELETE"))  
     { // Expected action verbs.   
       throw new Error("Sdk.request: action parameter must be one of the following: " +  
         "POST, PATCH, PUT, GET, or DELETE.");  
     }  
     if (!typeof uri === "string")  
     {  
       throw new Error("Sdk.request: uri parameter must be a string.");  
     }  
     if ((RegExp(action, "g").test("POST PATCH PUT")) && (data === null || data === undefined))  
     {  
       throw new Error("Sdk.request: data parameter must not be null for operations that create or modify data.");  
     }  
     if (maxPageSize === null || maxPageSize === undefined)  
     {  
       maxPageSize = 10; // Default limit is 10 entities per page.   
     }  
     // Construct a fully qualified URI if a relative URI is passed in.   
     if (uri.charAt(0) === "/")  
     {  
       uri = clientUrl + "/api/data/v9.0" + uri;  
     }  
     return new Promise(function (resolve, reject)  
     {  
       var request = new XMLHttpRequest();  
       request.open(action, encodeURI(uri), true);  
       request.setRequestHeader("OData-MaxVersion", "4.0");  
       request.setRequestHeader("OData-Version", "4.0");  
       request.setRequestHeader("Accept", "application/json");  
       request.setRequestHeader("Content-Type", "application/json; charset=utf-8");  
       request.setRequestHeader("Prefer", "odata.maxpagesize=" + maxPageSize);  
       if (formattedValue)  
       {  
         request.setRequestHeader("Prefer",  
           "odata.include-annotations=OData.Community.Display.V1.FormattedValue");  
       }  
       request.onreadystatechange = function ()  
       {  
         if (this.readyState === 4)  
         {  
           request.onreadystatechange = null;  
           switch (this.status)  
           {  
             case 200: // Success with content returned in response body.   
             case 204: // Success with no content returned in response body.   
               resolve(this);  
               break;  
             default: // All other statuses are unexpected so are treated like errors.   
               var error;  
               try  
               {  
                 error = JSON.parse(request.response).error;  
               } catch (e)  
               {  
                 error = new Error("Unexpected Error");  
               }  
               reject(error);  
               break;  
           }  
         }  
       };  
       request.send(JSON.stringify(data));  
     });  
   };  
   /*****************************************************************************************  
   Enforce Apps  
   *****************************************************************************************/  
   function enforceApps(globalContext)  
   {  
     //check flag to verified if the load already happened  
     if (window.top.isAppLoaded && window.top.isAppLoaded == '1') return;  
     var clientUrl = globalContext.getClientUrl();  
     //get roles names  
     var userRolesIds = globalContext.userSettings.securityRoles;  
     var filterTemplate = "roleid eq ";  
     var filter = "";  
     for (var i = 0; i < userRolesIds.length; i++)  
       filter = filter + ((userRolesIds.length > 1 && i < (userRolesIds.length - 1))  
         ? (filterTemplate + userRolesIds[i] + " or ") : (filterTemplate + userRolesIds[i]));  
     var url = "$select=name";  
     if (filter != null)  
     {  
       url += "&$filter=" + filter;  
     }  
     url += "&$orderby=name";  
     //overlay with spinner 1  
     Xrm.Utility.showProgressIndicator("Loading app ...");  
     EnforceAppsScript.SdkRequest("GET", clientUrl, "/roles?" + url)  
     .then(function (request)  
     {  
       console.log("roles done");  
       var collection = JSON.parse(request.response).value;  
       console.log("response value " + collection);  
       var rolesNames = EnforceAppsScript.webApiOutputParse(collection, ["name"]);  
       console.log("roles Names done");  
       //find app by name  
       for (var i = 0; i < rolesNames.length; i++)  
       {  
         appName = appPerRole[rolesNames[i]];  
         if (appName && appName != undefined) break;  
       }  
       console.log("app Name: " + appName);  
       //find app by name  
       if (appName && appName != undefined && appName != '')  
         return EnforceAppsScript.SdkRequest("GET", clientUrl, "/appmodules?$select=name,clienttype,appmoduleid&$filter=name eq '" + appName + "'");  
         //default app persists  
       else throw new Error('No app found by current user roles');  
     }).then(function (request)  
     {  
       console.log("checking current app");  
       //appmoduleid  
       globalContext.getCurrentAppName()  
       .then(function (currentAppName)  
       {  
         //check if app already opened  
         //no => get id and load it  
         if (currentAppName != appName)  
         {  
           var app = JSON.parse(request.response).value;  
           //close progress spinner   
           Xrm.Utility.closeProgressIndicator();  
           //update flag:  
           window.top.isAppLoaded = "1";  
           window.top.requiredAppName = appName;  
           window.top.location.href = clientUrl + "/main.aspx?appid=" + app[0].appmoduleid;  
         }  
         //app is already loaded => nothing else we need          
         //close progress spinner  
         Xrm.Utility.closeProgressIndicator();  
       },  
       function (error)  
       {  
         //close progress spinner   
         Xrm.Utility.closeProgressIndicator();  
         console.log(error.message);  
       });  
     })  
     .catch(function (error)  
     {  
       //close progress spinner   
       Xrm.Utility.closeProgressIndicator();  
       console.log(error.message);  
     });  
   }  
   /*Public properties and methods  
   ********************************/  
   return {  
     //loads on Home (main) page  
     OnLoad: function ()  
     {  
       console.log("begin apps");  
       var globalContext = Xrm.Utility.getGlobalContext();  
       //manage apps and reload  
       enforceApps(globalContext);  
     },  
     SdkRequest: SdkRequest,  
     webApiOutputParse: webApiOutputParse,  
   };  
 })(window, document);  


Comments

Kevin said…
Hi there,

I tried implementing this Javascript, but when I tie the function enforceApps to the refresh all button on the application ribbon, the Javascript functions are not loaded.

Can you please share an example implementation from the ribbon workbench?

Thank you!
Hi Kevin. Please use EnforceAppsScript.OnLoad method on Enable Rule
Kevin said…
Still not working..

https://ibb.co/ddA817
Hi Kevin,

All look correct on the screenshot.
Except command.
I use command of Refresh All button.
Command name: Mscrm.DashboardTools.RefreshCommand from Home page.
The on u use looks like the refresh button from grid.

Can u try to add the rule to different command?


Please check what's your default home page.
The command has to be related to one of ribbon buttons from default home page.
Kevin said…
I added the JS to the button command. The JS is loaded, but it's only showing "begin apps" in the console. The other steps seem to be ignored..
Hi Kevin,

How it's going?

What crm version do u have?
If it's 8.2 please change the row:

uri = clientUrl + "/api/data/v9.0" + uri;

to use a proper web api version. In your case it's /api/data/v8.2

Kevin said…
It is v9.0, so I am using the correct API version according to the scripts.

Would you mind having a quick web sessions so we can discuss? I'll help you enhance the blog post after, since I think it will be of great help for the community.
Sure, ping me via linkedin and we'll figure out a time.

https://www.linkedin.com/in/michael-kalinovich-b6743a23/

Most popular

Dynamics 365 online and Adx portal on-premise. Are they compatible?

Adxstudio Portal how to: Incremental (Sequential) Deployment and backup. Part I

Custom plugin exception output for crm form