Thursday, May 18, 2017

Showing and hiding sections on a form + quirks

Showing and hiding sections on a form is not possible to do in a fully supported way without hiding all components of the section.
This can be quite arduous, and in addition someone might have created a business rule that messes that up for you and all of a sudden, it's visible anyway.

To help with that there's a great little function hidden in the client side library that you could use
Xrm.Page.ui.tabs. get(delegate: MatchingDelegate<T>): T[];
This little bit lets you query for a tab using a filter in form of a function. This will let you specify criteria like section content and other properties/attributes.
This means you loop through all tabs on the form and filter by the criteria specified. I’m doing the following (please notice that I’m writing in typescript):
var parentTab = parent.Xrm.Page.ui.tabs.get(t => {
    return t.sections.get("MyUniqueSectionName") !== null
});
var mySection = parentTab[0].sections.get("MyUniqueSectionName");
mySection.setVisible(false);
For the first part, I’m creating a delegate function of t, where t is the current tab being iterated over.
Inside the delegate there’s a short one-liner which returns true if there is a section in the tab with the unique name. This will generate an array of tabs as a result set. I made sure that I used a unique name for my section so I will only get one tab in return.
Next I collect the section from the result set, and finally set the visibility to false.
Putting this into a reusable function with two parameters, I can now utilize it as follows:
setSectionVisibility("MyFirstUniqueSectionName", false);
setSectionVisibility("MySecondUniqueSectionName", true);
Now I can reuse the function and trigger it on field changes or form load to make sure I only show the sections applicable for a given scenario.

Quirks!

I found one quirk with this which was kind of annoying, but luckily the fix isn’t particularly hard. If you have a business rule which shows and hides fields in a section, and that section is hidden at the same time (for example if you trigger on change of a field, and both the business rule and this javascript snippet triggers on the same change), then the following quirk happens.
The section stays visible, but if you write out console.log(mySection.getVisible()) it will say “false”. I assumed there was some race condition going on, because if I hid a field with a business rule at the same time as I hid the section with this javascript, then the section stayed visible but the field got hidden. I did some testing and found out that if you put it inside a setTimeout() then it works like a charm. What I ended up doing was this:
setTimeout(f => {
    setSectionVisibility("MyFirstUniqueSectionName", false);
    setSectionVisibility("MySecondUniqueSectionName", true);
}, 10);

Even though the timeout is only 10 milliseconds that’s still enough to prevent the race condition, and everything works brilliantly.


Another thing I found is that the tab has another, undocumented function: Xrm.Page.ui.tabs. getByFilter(delegate: MatchingDelegate<T>): T[];

This does the same thing as get(…), but has a longer, more explicit function name. Seeing that this is undocumented I would stay away from it as long as both do the same thing (using Daryl Labar’s DefinitelyTyped definitions for Xrm in Typescript will only show the documented function).


Edit:
I have been notified that the xrm definitions of DefinitelyTyped is an effort made by the following:
David Berry
Matt Ngan
Markus Mauch
Daryl LaBar
Tully H

Refreshing web resources and iframes, the supported way

I wrote this great (IMO) little snippet to refresh web resources and iframes (which is basically the same thing) on an MSDYN365 form.
A great thing about the client side library is that it allows you to control form components even when you're operating from inside another frame (be aware that this is also a risk, and something you should be aware of).

I made a web resource which lists out data from several different child-entity records, lets call it RecordLister, and then I have another web resource which allows for creation of different child-entity records, lets call it RecordCreator.

When I create new child records from the RecordCreator I want to refresh the RecordListener resource, but as you might know the "Xrm.Page.data.refresh" function does not reload the web resources, it only reloads the data in the native fields. However, since the web resources are basically iframes it should be possible to reload the content, and luckily for us there is.

First off, I'm doing this in typescript, and I'm using the excellent DefinitelyTyped definitions made by Daryl Labar. You should too!

So here's the code I used to get this to work, and I'll explain it step by step.


var recordLister = parent.Xrm.Page.ui.controls.get("WebResource_recordlister") as Xrm.Page.FramedControl;

What this bit does is just getting the web resource using the Xrm client library, and since I’m using DefinitelyTyped I’m casting it to the specific type I know it is (in this case a FramedControl).

var oldSource = recordLister.getSrc();

Next I’m getting the source url from the control and store it in a local variable, followed by setting the source to about:blank. This way I’m keeping the relative URL that was used before, and I’m changing the source value to make sure the client recognizes the change (setting the value to the same thing doesn’t trigger a reload). I use about:blank just to make sure that should anything crash, hang or otherwise work improperly then at least the frame is blank.

recordLister.SetSrc(oldSource);

Finally I’m setting the source back to its original value, triggering a reload of the contents.

So that’s an easy way to reload your web resource or iframe using supported javascript (typescript) client side functions.

Edit:
I have been notified that the xrm definitions of DefinitelyTyped is an effort made by the following:
David Berry
Matt Ngan
Markus Mauch
Daryl LaBar
Tully H

Tuesday, May 9, 2017

Dynamics 365 REST API implementation changed for _workerid_value

I just noticed one little finicky change to the MSDYN365 REST API.
We have two custom buttons on the case form in one of our customer solutions

  1. Pick case: Lets you pick the case if it is in a queue and you are not the owner
  2. Release case: Lets you release the case back into the queue if you are the owner


So I do the following query against the MSDYN365 REST api through JS (I know, I know, there's other ways to do this, but this has the lowest latency cost):
>xhr.open("GET", apiUrl + "incidents(" + caseId + ")?$select=title&$expand=Incident_QueueItem($select=queueitemid,_workerid_value)", true);

Back in the old days of version 8.0CRM2016 if there was no worker assigned to the queue item, then this would be the case:
>JSON.parse(xhr.response).Incident_QueueItem[0]._wokerid_value
undefined

But in the modern days of version 8.2MSDYN365 if there is no worker assigned to the queue item, then it would return this:
>JSON.parse(xhr.response).Incident_QueueItem[0]._wokerid_value
null

Having hacked this together in a hurry, someone (fingers crossed it wasn't me) decided to do the following check:
>if (queueItem._workerid_value !== null) { ... }

Which worked perfectly, until recently when we upgraded to MSDYN365. Now it returned true all the time, even when there was no worker assigned. The only thing that tipped us off was that it was still working in an old environment still running 8.0, so that's how we found out about it.
The workaround is, as you might already see, very simple. Just do an empty check instead of value specific check, which works for both null and undefined:
>if (queueItem._workerid_value) { ... }


Lesson learned: Implementation will change, so make (doubly) sure to plan accordingly.
EDIT: This apparently only happens for _workerid_value, so there seemed to be a mistake in the 8.0 implementation in CRM2016.
Also, to be more specific, I'm using api/data/8.0 in both examples.

Sunday, May 7, 2017

Deleting Azure Resources when portal is stuck and unable to acquire token for subscription

So that's a long headline!
I recently had some issues trying to delete a resource in one of my resource groups in Azure, and when I decided to try and use powershell to do it I ran into more problems. So here is a short recap of what I did, in case somebody else runs into the same issues.

Background:
I have both an MPN account for my own company and an MSDN account from a company I work for, both are connected to the same Microsoft ID but are different subscriptions on different tenants.

So here's my issue. I wanted to delete some old resoures in the Azure portal so save some of my dev credits, but whenever I opened the resources in the portal they were just stuck loading forever.
After two days of this I got fed up and decided to fix it with powershell (which I probably should have been doing in the first place.
So what I did was fire up powershell, and run the following lines:

Import-Module Azure
Login-AzureRmAccount
Get-Subscription

And that produced the following error:
WARNING: Unable to acquire token for tenant 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'

So first I was met with one issue, then a new one when I tried the other, recommended path of fixing things.
Luckily for me this annoyed me, so I did some very quick google searching to find a workaround to get my workaround fixed, so I simply did this to log in instead:

Login-AzureRmAccount -TenantId 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
Get-Subscription


And voilá! The subscription was available and everything was working fine.
And just to clearify how to delete the resources, just run the following commands

Get-AzureRmResource
Remove-AzureRmResource

Nothing big, but here's the entire solution in one blog post. Also, I'm a much happier man now with plenty of credits left on his Azure subscription.

Tuesday, March 14, 2017

Query Builder Error in MSDYN365

Query Build Error

The specified field does not exist in Microsoft Dynamics 365

I recently encountered this error in an on-premises environment one of my customers have. The issue occurs when I try to export the unmanaged solution, and it happened after I deleted a currency field from an entity that was included in the solution.
I managed to reproduce the issue in my online-environment, and I've sent a ticket to Microsoft so they might get it fixed soon, but I'll outline my findings in this blog post.

To reproduce this error, simply do the following (please do this in a dev-environment which you can break as you please, and back up all your solutions first):
  1. Create a new solution
  2. Add an existing entity to it
  3. Add an existing (or create a new) currency field
  4. Delete the currency field
  5. Try to export the solution (managed/unmanaged, happens with both)
You will probably be presented with this error message (unless it is fixed by the time you're reading this):


I tried to create each of the field types, it only fails on the currency type. I think this might have something to do with the secondary attribute which contains the currency base. In the back-end both option set and currency has two columns in the database, but only the currency field has a secondary field available in the UI.
If you mark both the currency and the currencybase field for deletion it will give you an error saying one attribute was not deleted, so that is not a viable workaround.
If you have created the entity in the solution you're working on then everything is OK, but if it's created in another solution (for example default solutions, or default entities) that are added as existing entities then this happens.

Workarounds (I prefer the first one, the other two are best for fresh installations and new solutions that haven't been through the normal ALM cycle yet):

  1. Restore a new dev environment (sandbox if you're online) from a backup, remove the field from the solution and export it as unmanaged. In the target dev-environment, make sure the field is deleted, then delete the solution (it's unmanaged, so the customizations will still be in place). Then import the newly exported solution which doesn't have the currency field included, it can now be exported as usual.
  2. Happens for unmanaged solutions, so you can just delete the solution and re-create it. Can be quite arduous, or near impossible, if you have included a lot of customization in the solution.
  3. Delete the entity and recreate it with the same id (only applicable for custom entities). Might be possible for simple entities without too much customization (will cause data loss). Can be re-created with the same id and schema and logical name so it doesn't break inheritance. I find this one most risky, so I would avoid it. Would work for solutions and entities that haven't been exported before.

Monday, February 13, 2017

Change form translations on secondary languages (exploit)

Changing section translations in MSDYN365

So first of all, thanks to my fellow (and much more experienced) expert @rappen who pointed out that this could potentially mess up your solutions.
ALWAYS BACK UP YOUR SOLUTIONS FIRST!
Also, only use this for the display name on the sections, if you change the section name it will change for the base language as well.

So we all know that translations in #MSDYN365 can be a pain unless you're using tools (a big shout-out to http://www.xrmtoolbox.com/ ). But did you know that there's a simple way to translate section and category labels? 
First off I'm not giving you any guarantees on this one, it seems like an exploit and they might change the implementation without prior warning, but if you're willing to take the (relatively small) risk you can do the following:
1) Change the language to the base language
2) Go to "settings -> solutions" and open up the solution you're working on (you are working on solutions, right?)
3) Change the language in the main tab to the language you want to translate to
4) Navigate to the form you want, and you'll see all the secondary language names. You can then edit the label, save and publish.
Now the sections on your forms will have translations for all the languages you repeat this for.

This isn't anything revolutionary, but if you're doing this as a one-off you might consider it instead of installing third party solutions or taking the chance and editing the XML.

Sunday, February 12, 2017

Using related entities in PowerApps with MSDYN365 as a data source

Getting related records to work in PowerApps and MSDYN365

PowerApps is a relatively new feature to the Office365 portfolio which allows advanced users to create their own apps which can be used on mobile (and desktop) platforms to perform daily tasks. It is a great tool for creating those small functions you usually have to book a developer for a few hours/days, and is ready for authentication, authorization, and distribution right out of the box.
PowerApps comes with the ability to connect to a wide array of sources to collect and manipulate data, and today I'm going to demonstrate how to connect with MSDYN365:CRM and how to use connect with related records.

Connecting to MSDYN365:CRM

Now this is the easiest part. To connect with MSDYN365 in your powerapp simply go to the 'Content' tab and select Data Sources, and you'll be presented with the option to add new connections and data sources. Here it will probably show you something like this (I have a completely new trial organization with only O365 E3 and MSDYN365 Plan1:

What I'm doing is connecting to the MSDYN365 suggestion, and I'm going to add 3 tables (entities) to my data source list; Accounts, Contacts and Users (systemusers).
Once that's done I'm going to add a new screen with the browse template. This will give me the option to search and display records from the chosen data source.
Now, from the right hand side, choose the advanced tab and select your Gallery component from the dropdown list. Next through the Home tab on the top toolbar select "Items" from the property dropdown box. We're going to do some drastic changes to the formula used to collect data, but first a little insight.

When a function is run/validated in powerapps it returns the result set to the parent function (or context, whatever you'd like to call it). This means that we can build an advanced data source or query by nesting functions inside each other, and end up with a pretty good set of data to play with.

So to get the data I want in my gallery browser I'm going to use the following formula:
SortByColumns(Search(AddColumns(Accounts, "User", LookUp(Users, systemuserid = _ownerid_value), "Contact", LookUp(Contacts, contactid = _primarycontactid_value)), TextSearchBox1.Text, "accountnumber", "name"), "name", If(SortDescending1, Descending, Ascending))

Now this might seem a little frightening at first, but I'll go through it step by step and you'll see how I thought through it. Remember the part about nesting functions that I mentioned above? Well that's how I start building a function, by nesting backwards.

  1. I want a collection of accounts, but I also want to add the primary contact and the account owner (I'm not checking for teams, I'm just assuming that all accounts are owned by users. This is just a quick demo). That means I'll have to use a function to join those sources. A quick search online says that I can use AddColumns to do joins in a PowerApp formula, and thanks to the built in intellisense it's easy to create the formula required.
    AddColumns(<data source>, <table alias>, LookUp(<data source>, <condition>))
    This will nest the related table underneath the table alias (or namespace if you will), and you can add additional tables by just specifying new column aliases and lookup expressions. To get the ones I needed I filled in the following:
    AddColumns(Accounts, "User", LookUp(Users, systemuserid = _ownerid_value), "Contact", LookUp(Contacts, contactid = _primarycontactid_value))
    This means that I've nested the Users entity underneath the "User" column alias, and the Contacts entity underneath the "Contact" column alias.
  2. I need to be able to search for these records, I don't simply want all the accounts listed at all times. Search is a function designed for this, but I could also use Filter if it was a set query I wanted to use. Now I know that the AddColumns(...) functon I used will return the result set of that function, so I could potentially search through the related records as well. I'm going to do it a lot simpler so I'll want to search through the account number and name field on account. The syntax is :
    Search(<data source>, <Search text>, <Column to search in>, <Column2 to search in>, ...)
    The data source in our case will be our previous AddColumns formula. The search text we'll get from the default search box, TextSearchBox1.Text, and the columns we want are "accountnumber" and "name", which makes the formula look like this:
    Search(AddColumns(.....), TextSearchBox1.Text, "accountnumber", "name")
  3. Lastly I'd like to sort the result, so I'm going to add a SortByColumns function just to get a predictive list instead of a random one. This starts just the same as our previous function, the result of everything we've done so far has been returned, and we can order by the columns we want. The syntax looks like this:
    SortByColumns(<data source>, <column name>, If(<logical test>, <true value>, <false value>))
    We want to use the data source we've built so far, and we want to use the "name" column of the account. The logical test will be the default sorting button, with true as Descending and false as Ascending (false is the default value). We end up with the following formula:
    SortByColumns(Search(...), "name", If(SortDescending1, Descending, Ascending))
Now we've got a search gallery were we can search for accounts and get the related primary contact and the owning user data values. We can use that to further style our result list so it will look something like this:



Customizing the details form

So now that we have a decent-ish looking gallery we want to do some modifications to our details and edit forms. There isn't really any magic to this, you simply add and remove the data fields that you want. There are however two things I would like to point out so that you don't walk into the sweet honey traps.
First of all, we want to set a data source and item that PowerApps has a relation to and can work with. If we choose the same data source as we used in the gallery then we can certainly we the data and place it on the form, but updating the record or creating a new one could prove a bigger challenge. So what we do here is that for the data source, we set the "Accounts" entity table. The Items property is a bit more special though, and here we have two options (I'm going for option 2):
  1. We use the item from the gallery, ie. BrowseGallery1.Selected. This will work perfectly fine as long as you don't try to update any other fields than the main account fields. However, you will get an error in the PowerApp user interface because the data source chosen for the details form is not the same data source as you have in your gallery. The upside is that you don't have to do a new query to get the account data, but on the downside you will have to live with a warning sign that comes with not following the best practices.
  2. We use the accountid from the gallery to look up the specific account from the account data source, ie we use the following formula:
    LookUp(Accounts, accountid = BrowseGallery1.Selected.accountid)
    The positive thing about this is that we're now 100% compliant with the best practices, but we now don't have direct access to the sub-components, and we're doing an additional query to CRM for a lookup. Luckily, this lookup is very cheap because we're getting 1 entity based on the Guid, and I'll be picking this one just so we can demonstrate how to do a lookup inside a field.
So now that we've got the data available we can start adding the fields to the forms. If we want to have some fields from related records we can simply use the LookUp function to get them. For example, if I want the full name of the primary contact I can use this formula in a data form:
LookUp(Contacts, contactid = _primarycontactid_value).fullname
So now that I've done a very small amount of customization my detail form looks like this.


Customizing the details form and changing the primary contact

So now we're ready to do some customization to the edit form. I've gone ahead and added the same data source and item as we did on the detail form, and I've added the address fields just so we have something to play with. The results so far looks like this:


Now, as a power user I would love to be able to edit the primary contact of the customer without having to specify a Guid, I want a search window so that I can find the right one and add it to the account I'm looking at. The first thing I'll do is to add a new browse gallery screen, and then I add a new button on the edit form which navigates to the new screen:

Now, on the contactselector screen I want to show all active contacts. I'll start off by creating the same kind of formula as we did in the beginning of this blog post, but this time I'm just searching through the Contacts data source. My formula now looks like this:
SortByColumns(Search(Filter(Contacts, statecode = 0), TextSearchBox2.Text, "fullname"), "fullname", If(SortDescending1, SortOrder.Descending, SortOrder.Ascending))

Now the right arrow on each result will usually try to navigate to a details window, but we don't have that window here and we don't want to see the details either, we just want to add this contact as the primary contact to the account. So what we're going to do is to use the "patch" function in PowerApps. The Patch option allows you to edit items on an existing record, while the update function replaces an old record with a new one. Since we just want to update the contact I'm going ahead with the patch method. The syntax for the patch function is like this:
Patch(<data source>, LookUp(...), { field1: value1, field2: value2})
So we want to update the account we have selected in previous windows. In additon, we want to update the primary contact Id for that account. That means we have to do a formula like so:
Patch(Accounts, LookUp(Accounts, accountid = BrowseGallery1.Selected.accountid), { _primarycontactid_value: ThisItem.contactid})
This is starting to look pretty nice, but we also want to navigate away from this form after updating. To do that we simply add a semicolon after the Patch function, then call the Back() function to go to the previous window. What we end up with is a window that looks something like this:

So now we have a complete app that can search for Accounts, display related record information, and finally edit the fields and set a new primary contact. This should work just as well for all related records, but now you have a pretty good example to start out with.

Wrap-up

So as we've seen not everything in PowerApps is just point-and-click, but you don't have to be an advanced developer to find out this stuff on your own. The documentation for powerapps is decent, so you'll find plenty of examples you can experiment with on your own.
What I find very exciting is that not only can I create these simple apps very fast, I can also distribute them to other users in my organization so that it will be available from the https://home.dynamics.com menu.

I've added a copy of the powerapp to to my public google drive so you can download it and use it as you want (TestAccounts.msapp). 
All you have to do is to go to the "Content" tab and add the data sources, you'll need "Accounts", "Contacts" and "Users", and then you're ready to go.