Recently I was asked a question about Unified Service Desk for Microsoft Dynamics 365, the requirement was to be able to save a session. Meaning saving details of all the open tabs, so that someone else could re-open it later.
This could be very useful in several scenarios;
- Say you have opened an account, case, phone call (etc etc.) but you need to park it. It could be lunchtime and you want to save it and pick up your work again after lunch.
- Or maybe you want to transfer the current “job” over to a more experienced operator or manager. (Possibly whilst the customer is still on the phone!)
WARNING: This is a complicated USD change. My aim is to explain a design pattern that someone who has advanced USD knowledge could adapt / alter. Therefore, in this post I’m going to assume the reader fully understands USD concepts. This might not be a change you can simply lift and use! E.g. You will probably need to tweak the window navigation rules for your purposes.
Before looking at how I created this change, you may wish to view this video which shows this feature in action;
Steps Involved
Part One : Save the Session
- Create a hosted control
- Create a custom entity
- Create a Scriptlet (Challenge One)
- Create more some Scriptlets! (Challenge Two)
- Create a “CreateEntity” action to populate the custom entity
- Create a “CloseSession” action
- Create a toolbar, I put mine in the CTI Panel
Part Two : re-open the session
- Create a view of Saved Sessions
- Open “Saved Session” custom entity
- Create a bunch of actions
- Add actions to BrowserDocumentComplete
- Create some Window Navigation Rules
Step One – Create a Hosted Control
All good USD changes should start with creating a hosted control! J
Firstly, I created a hosted control called “SavedSession”, this will be used later when I’m re-opening a saved sessions.
Field |
Details |
Name |
SavedSession |
USD Component Type |
CRM Page |
Hosting Type |
IE Process (I only ever use IE Process!) |
Application is Global |
Must be selected.
Note: I am going to “spawn” a session from this tab opening. It is global but it will trigger sessions to open. |
Display Group |
Hidden
The user does not need to see this! But a tip, whilst testing you might want to use MainPanel and only hide the tab when you know your change is performing correctly. |
![]()
Step Two – Create a Custom Entity
Next I created a custom entity in CRM. This will be used to hold the details of all the tabs you’ll need to re-open. My USD application is actually quite a complicated one! So, I needed multiple fields on this entity. You may find you need far fewer fields!
I called my entity “Saved Session”. Notice that the ownership is “User or Team”. This is important as I will want to know who created each saved session. Later I might also want to consider security role restrictions, such as maybe only the person who owns the saved session or their manager can open it.
Note:
In my demo environment, I didn’t worry about security roles. In a production environment you will need to add this new entity into the roles used by all USD users.
Notice that I didn’t select any of the communication & collaboration options. I don’t need notes, activities etc. So, I didn’t select them!
![]()
In my solution, I have quite complex navigation. I use all activity types and most of the system entities available in CRM Dynamics 365. Plus I have multiple standard web pages that could be displayed.
This entity was essentially a list of text fields. All of the ones used for URLs had a type of url. But Initial Entity and Agent Script were just standard text fields.
![]()
A “key” field here is “Initial URL”. In this field, I will hold the url of the first tab opened in the session. Important as that will drive the context of the session when we re-open it.
Next I created a field for each of the tabs / entities that could be opened in a USD session. For readability, I grouped them under headings of Entity Tabs, Associated Views, Activities etc.
![]()
Finally, I added a section for all of the urls that related to web pages outside of USD. In my application, these included maps, results for Google searches, LinkedIn pages etc. In yours you may also have line of business applications or pages on your local intranet etc.
![]()
Obviously, my entity ended up having loads of fields! This entity might look complicated but it is actually really simple as it’s just a load of text fields.
Step Three – Create a Scriptlet (Challenge One)
Whilst creating this solution I hit a couple of challenges that were solved with scriptlets.
My first challenge was that the context does not always contain the url of the initial entity. And I needed to ensure this was captured to be able to open the session correctly.
![]()
My “SavedSessionURL_INITIAL” scriptlet simply returns the url of the initial entity. Most of the time this will be “[[$Context.url]+]” but as that replacement parameter isn’t available on activities I added in some if conditions to get those details from other parameters. (Such as “[[task.url]+]” etc.
My code looked like this ….
function SavedSessionURL_INITIAL() {
// ** To explain ...
// ** Generally speaking, the context will contain the url of the initially loaded record.
// ** So most of the time we just need "$Context.url"
// ** BUT I found that this field doesn't work with activities.
// ** so when the session starts from an activity I grab the url from another replacement parameter.
var url = "[[$Context.url]+]";
var logicalName = "[[$Context.LogicalName]+]";
if (logicalName == "task") {
url = "[[task.url]+]";
}
if(logicalName == "appointment") {
url = "[[appointment.url]+]";
}
if(logicalName == "email"){
url = "[[email.url]+]";
}
if(logicalName == "phonecall") {
url = "[[phonecall.url]+]";
}
return url;
}
SavedSessionURL_INITIAL();
Step Four – Create more some Scriptlets! (Challenge Two)
My next challenge was a problem with the url field on CRM entities. The screen shot below shows the replacement parameters for a newly created case. You can see that the case’s GUID is in the “Id” field. But when you look closely at the URL you can see that this Id has not been included. If we use this URL as it stands a new case form will be displayed instead of the case we just created.
Note: I will explain how / when these scriptlets are called in just a second!
![]()
So again, I turned to Scriptlets for a solution! This time I wrote a small piece of JavaScript that would insert the Id if it was missing.
![]()
function SavedSessionURL() {
var url = "[[incident.url]+]";
var id = "[[incident.Id]+]";
var urlSplit = url.split("&id=&");
if (urlSplit != "" && urlSplit != null) {
url=urlSplit[0] + "&id=" + id + "&" +urlSplit[1];
}
return url
}
SavedSessionURL();
I ended up creating a scriptlet like this for each of the CRM entities that should be created in my interface. Meaning case, opportunity, phone call etc. So quite a few scriptlets were needed but each one was almost identical. ( Copy and paste time! J )
Step Five – Create a “CreateEntity” action to populate the custom entity
Now we come to the fun bit. Having created my custom entity and the scriptlets I needed to format the URLs, I now needed an action to insert a record into my custom entity. So a CreateEntity action. My action was from my CRM Global Manager and looked like this;
![]()
My data field looked like this, notice that for the initial entity url field I call the scriptlet that I described in step 3. And for each of the CRM entities I call one of the scriptlets we created in step 4.
Also notice that some urls, like the standard web pages towards the end can have their urls simply set without the need of a scriptlet.
Tip:
My publisher has “crm_” as its prefix. In your application “crm_name” (etc) might be “new_name”. This CreateEntity action is simply a list of name/value pairs. The first part being the schema name from my custom entity the second being the value to insert.
LogicalName=crm_savedsession
crm_name= [[$Context.LogicalName]+] ([[$Context.Id]+])
crm_initialentity=[[$Context.LogicalName]+]
crm_agentscript=[[Agent Scripting.msdyusd_name]+]
crm_initialurl=[[$Scriptlet.SavedSessionURL_INITIAL]]
crm_accounturl=[[$Scriptlet.SavedSessionURL_Account]]
crm_caseurl=[[$Scriptlet.SavedSessionURL_Case]]
crm_contacturl=[[$Scriptlet.SavedSessionURL_Contact]]
crm_leadurl=[[$Scriptlet.SavedSessionURL_Lead]]
crm_opportunityurl=[[$Scriptlet.SavedSessionURL_Opportunity]]
crm_workorderurl=[[$Scriptlet.SavedSessionURL_Workorder]]
crm_appointmenturl=[[$Scriptlet.SavedSessionURL_Appointment]]
crm_emailurl=[[$Scriptlet.SavedSessionURL_Email]]
crm_phonecallurl=[[$Scriptlet.SavedSessionURL_Phonecall]]
crm_taskurl=[[$Scriptlet.SavedSessionURL_Task]]
crm_associatedcaseurl=[[Associated Cases.url]+]
crm_associatedcontacturl=[[Associated Contacts.url]+]
crm_associatedopportunityurl=[[Associated Opportunities.url]+]
crm_associatedworkorderurl=[[Associated Work Orders.url]+]
crm_googlemapurl=[[Google Maps.url]+]
crm_googleurl=[[Google.url]+]
crm_bingurl=[[Bing.url]+]
crm_bingmapurl=[[Bing Maps.url]+]
crm_accountwebsiteurl=[[Account Website.url]+]
crm_linkedinurl=[[LinkedIn.url]+]
Step Six – Create a Close Session Action
Now I created a very simple action to close the session. As once I have saved the session details to my custom entity I would like to force it to close.
As you can see below this is a simple CloseSession action which is applied to my Session Tabs hosted control.
Notice the order of 5. My CreateEntity action had an order of 1. (Meaning I want to fire the CreateEntity action and then the CloseSession action.)
![]()
Step Seven – Create a toolbar, I put mine in the CTI Panel
Next you need a toolbar button that will contain a save session button. In my example I decided to show where the CTI Panel typically goes. (As I wasn’t using it!) You could of course add the button to any toolbar.
First I created a hosted control for my toolbar that looked like this. (Notice my display group is “CTiPanel”)
![]()
I then created a toolbar, mine is shown below. (Don’t forget to link the toolbar to the hosted control you created above! Using the hosted control option in the navigation of the toolbar.)
![]()
Next I created a toolbar button.
I gave my toolbar button an image that I found in the resources of the CRM 2016 SDK.
Notice that I have an enable and visible condition. “[[$Context.LogicalName]+]”!=”” This simply means that my button won’t show if I don’t have a session open.
And then finally I added the CreateEntity action and CloseSession actions we previously created to my button.
![]()
Below you can see a screen shot of my USD interface complete with the SAVE SESSION button in the CTiPanel.
![]()
Step Eight – Create a view of Saved Sessions
Next I wanted to have a list of the saves sessions. I’d already got a search toolbar and tab. So I decided to re-use that. You may decide to locate elsewhere in your navigation. Below you can see how my Saved Sessions option was shown in my search toolbar.
![]()
The button I created looked like this, notice that the button calls an action to “find” my Saved Session custom entity.
![]()
My find action was pretty simple. It was just a find action with the schema name of my custom entity in the data field. As shown below.
![]()
My view of saved sessions looked like this. Now at this point I will say I don’t like this view! It is functional but from an end user point of view I do not like the way I have shown the GUID of the initial entity in the name. If / when I create a production ready version of this change I will alter my CreateEntity action to set a more user friendly name to the entity.
![]()
Step Nine – Open “Saved Session” custom entity
Next I needed to open my custom entity of Saved Session from the list of sessions I’d shown in my search tab. If you recall, in step one we created a hosted control called “SavedSession”. We are going to load custom entity into that tab. Except the tab is hidden so the user will not see this happen!
To load the tab I used a window navigation rule. As shown below.
![]()
Field |
Description |
Name |
Saved Session from Search |
Order |
1 (You may need a different order!) |
From |
Search
As I had loaded the list of saved sessions into a tab called “Search”. |
Entity |
crm_savedsession
The schema name of my custom entity. |
Route type |
Popup |
Action |
Route Window
I don’t often use a Route Window action on a global tab! What this going to do is open a global tab from the global search tab. BUT the tab is hidden! |
Target tab and show tab |
SavedSession
The name of the hosted control we created in step one. (As hidden I guess I didn’t really need to set the show tab!) |
Step Ten – Create a bunch of actions
First of all I created a popup action that will load the initial url. It is important that a popup action is used as I want this action to trigger my window navigation rules which will start my session.
Also, notice that the order is 10. A low number as I want this to be the first action that is run.
![]()
Now I created a navigation action for each of the urls that will be loaded from my custom entity. A navigate action as I want these tabs to load within my session.
The actions were simple enough but I had quite a few as I needed one for each url I had held in my custom entity.
Below you can see I have given an example of the action I used for account. Notice that the hosted control is account and the action is “navigate”. Then in the data field I have set the url to the field from my custom entity. So …. url=[[crm.savedsession.crm_accounturl]g]
I also added a condition to each of these navigate actions, as I only want to do them if I have that particular url. “[[crm_savedsession.crm_accounturl]g]”!=”” etc etc.
![]()
![]()
In my custom entity I had also stored the name of the agent script that is currently active. So I also created a agent scripting GotoTask to load this. For this one the hosted control was Agent Scripting and my action was GotoTask.
Tip:
Notice that the order is high! I want this action to run after all of my navigation actions.
![]()
![]()
Finally I created an action to close the SavedSession tab. (Which is hidden!) The main thing to notice is that the order is very high. I need this to be the last action that runs.
![]()
Step Eleven – Add actions to BrowserDocumentComplete
Next I added all of the actions I’d just created to the BrowserDocumentComplete event of my SavedSession hosted control. The logic here is;
- Do a popup, which will start the session.
- Do loads of navigate actions for all the possible tabs.
- Go to the agent script.
- Close the SavedSession hosted control.
![]()
Notice that my agent script and close actions had the highest order and therefore came last.
![]()
Step Twelve – Create some Window Navigation Rules
In your solution this step could take some thought! Depending on how you have structured you window navigation rules then you may need some to start the sessions as required. In my case I created a navigation rule for each entity that could be an initial entity.
So I had a list like this;
![]()
I opted for an order of 300, as that fitted with the structure I have in my rules. As I said at the start my USD interface is pretty complicated so I needed quite a few navigation rules. In yours you may find a much smaller number is required.
Tip: Each navigation rule also has some associated actions to expand the left panel etc. The exact format of yoru rules will differ depending on your applications functionality.
Obviously, this isn’t a simple change! But I hope you can see that it might be a useful addition to your Unified Service Desk interface.
J
Filed under:
USD - Configuration Tagged:
Microsoft Dynamics 365,
Microsoft Dynamics CRM,
Unified Service Desk