Wednesday, 7 October 2020

How to create a dynamic Record URL for a model-driven app

In classic workflows we can create a hyperlink in the email message content of an email activity. One neat feature is the ability to insert a Record URL to enable the recipient to click and browse to the record immediately. Now this is only relevant if that recipient is a licensed user of Dynamics 365 or CDS. If the user does not have the appropriate license, does not have the appropriate entity permissions they won't be able to view the record.

The question is: How do we replicate this classic workflows feature in a flow with Power Automate? I learnt how to do this earlier this year and Scott Durow shared a tip that he learnt from #LowCode queen Sara Lagerquist - might have been from their collab presentation at Scottish Summit 2020. In this WTF episode I'll share my method with you.

The Use Case

As the newly assigned Owner of a Case,

I want to receive an email with a hyperlink to the Case record,

so that I can easily view the Case and action accordingly.

Whenever a new Owner is assigned to a Case, an email is to be sent to the user with a hyperlink that directs them to the Case record within the Customer Service Hub app.

Understanding the URL of a model-driven app

If you look closely at the URL in your browser when viewing a record in a model-driven app it will be the following.

https://benitezheredev.crm6.dynamics.com/main.aspx?appid=fc11177c-0f6b-e811-a95c-000d3ae13628&pagetype=entityrecord&etn=incident&id=02193772-695f-4185-93f2-867abd56ac6c

This is what we want to replicate. The answers are all there in the URL staring at us. I'll break it down.

1. This is the Organisation Base URL of the instance.

2. As per the docs.microsoft.com article,  "All entity forms and views are displayed in the main.aspx page. Query string parameters passed to this page control what will be displayed." This is required to load the record within a model-driven app.

3. This is the ID of the model-driven app.

4. This defines what type of page you want to load. In this scenario the user is directed to a record, therefore the value is entity record. It also defines defines what entity you want to load where you need to reference the logical name of the entity. In this scenario it is the Case record. For more information about this please refer to this docs.microsoft.com article.

5. This is the GUID of the record you want to load.

What I'll cover next is how to retrieve 1, 3, 4 and 5 

How to identify the Organisation URL (#1)

This is straight forward. Behold, I give you Natraj's blog post.

Whenever you use a CDS action in flow with Power Automate there will be a property value of @odata.id which will contain the Organisation Base URL. Natraj's technique is to retrieve that value and apply some functions so that only the Organisation Base URL is extracted.

My expression is the following

first(split(outputs('Get_a_record')?['body/@odata.id'], '/api/'))

The functions used here is split and first.

The split allows us to separate the Organisation URL from the rest of the value. A split will result in an array and we only want the first row in the array which is where the first function comes in handy.

In my vlog I initially showed you the split function,

split(outputs('Get_a_record')?['body/@odata.id'], '/api/')

which will result in an array with two rows as the output. The first row is the Base Organisation URL.


The next action I forgot to show the run history of in my vlog. When you wrap the split function around with a first function, it will retrieve only the first row which is the Organisation Base URL.

first(split(outputs('Get_a_record')?['body/@odata.id'], '/api/'))


For your learning, I have this expression in a Compose action to demonstrate what this expression achieves. Later in the flow I use this same expression in my expression that forms the Record URL rather than the output of the
Compose since it's for educational purposes.

How to retrieve the model-driven app ID (#3)

As mentioned earlier, I learnt from the beloved Scott Durow who learnt it from the #LowCode Queen Sara Lagerquist. I now have the privilege of sharing it with you 😊

When you create a custom model-driven app, the following screen will be presented to you where you have to fill in the name and other details.


What happens behind the scenes is that a new record will be created in an entity called appmodule and each record will have an appmoduleid.

To test this out, take a look at your model-driven app URL and copy the ID you see.


In a new browser tab you're going to call the Dynamics 365/CDS API using the copied ID value.

https://YOURORG.crm6.dynamics.com/api/data/v9.1/appmodules?$filter=appmoduleid eq COPIED ID VALUE

You should now see the details of the app displayed.


Since there are multiple model-driven apps in every instance, we can narrow it down using a CDS List Records action with a filter query applied. The Name value of the model-driven app will be used to only return the appmodule so that we can retrieve the appmoduleid.

How to identify the logical name for the page type definition (#5)

Since we are loading a record, the page type value is 'entity record.'

The next part is to provide the logical name of the record. The clue is already in the Dynamics 365 record URL. It's after the "etn=" reference in the URL, this is the logical name of the entity of the Dynamics 365 record.

For more details about page type please refer to this docs.microsoft.com article.

How to retrieve the record GUID (#5)

There should be an action in your flow that has the GUID of the record you want to generate the Dynamics Record URL for. Reference the field that represents the GUID, the unique identifier.

Putting it all together

The last bit is to combine it altogether. The finishing touch is using the concat function to tie it all up.

The expression is the following.

concat(first(split(outputs('Get_User_record')?['body/@odata.id'], '/api/')),'/main.aspx?appid=',outputs('Retrieve_appmodule')?['body']?['value'][0]?['appmoduleid'],'&pagetype=entityrecord&etn=incident&id=',triggerOutputs()?['body/incidentid'])
It may look like a pile of words and characters but I'll break it down again so that it's easier for you understand and learn 😊

1. This is the expression that will retrieve the first row after we split the @odata.id value as explained earlier in this blog post.

2. This is required to load an entity record within a model-driven app.

3. This is the expression to retrieve the ID of the model-driven app where we used a CDS List Records action to retrieve the appmodule based on the Name of the app, Customer Service Hub. In this expression I am using one of the method's from another #WTF episode - How to Avoid the Apply to Each from appearing where we reference the first row in the output of the CDS List Records action.

4. This defines that the page type to load is entity record and the entity to load is the Case entity. The Case entity logical name is incident.

5. This is the GUID of the Case record from the trigger of the flow. When a Case record Owner is updated, it will trigger this flow.

That is the beautiful expression!

Using the expression

I'm using a Compose action to not only use the expression to embed the Record URL as a hyperlink but also format the email with HTML.

The output of this compose action is then referenced in the Description of the email activity that is created through a CDS Create a new record action.


What my flow in Power Automate looks like

As seen in my vlog the following is my flow.



The HTML content is a Compose action that has the expression that forms the Record URL as seen earlier in this blog post.
The last three actions are ones I have mentioned in previously #WTF episodes to create and send a CDS email activity via flow so check them out if you want to learn more about them.

Power Automate in action

Time to test the flow by assigning a new Owner to the Case record.


Awesome sauce, as you can see the Record URL created by the expression in flow successfully directs the user to the instance of a specified model-driven app and loads the Case record.

Keeping up with Application Lifecycle Management [ALM]

This method is what I call "ALM" friendly and compatible. When you are customizing, configuring or extending Dynamics 365 or CDS it is best practice to do so in a sandbox environment such as a DEV instance. When you're ready to move your components across to a target instance such as UAT, you move it through solutions. 

When you view records across different instances like DEV and UAT, the appmoduleid will be different. If your method of forming the Record URL does not accommodate loading the app based on the instance, there's a high chance the end user will encounter the error like the following because the appmoduleid does not exist in the target environment.

By using the CDS List Records action to retrieve the appmoduleid value this method is compatible with any instance as long as

  1. the entity is in the target instance. If it isn't then the hyperlink will not work
  2. the name of the model-driven app is the same across all environments

The name of the model-driven app is usually the same in each instance such as DEV, UAT and Production. If the name of the model-driven app is different in each instance, then this method isn't going to work because of how the filter in the CDS List Records action uses the name value.

Other methods blogged about in our community

What I've shared with you in this #WTF episode is one way of forming the Record URL in flow with Power Automate. Linn Zaw Win shared his method in his blog post. 

Summary

By using a functions in a single expression you can form the Record URL and embed it as a hyperlink for a CDS email activity or even a Microsoft Teams message.

Catch you in the next #WTF episode

Don't forget to do a shout on Twitter or leave a comment in my vlog if this has helped you out
🤗

0 comments:

Post a comment