Author Archives: Alex Shlega

Working with the grid onLoad event

Sometimes, I get a feeling that, as far as Dynamics/Model-Driven javascript event handlers are concerned, everything has already been said and done. However, I was recently asked a question which, as it later turned out, did not really have the kind of a simple answer I thought it would have (meaning, “just google it” did not work).

How do you refresh a form once a grid on the form has been updated?

For example, imagine there is a subgrid on the form, and, every time a new record is added to the subgrid, there is a real-time process that updates the “counter” field. By default, unless there are further customizations, I will have to hit “refresh” button to see updated value of my counter field. Otherwise, I will keep seeing 0:

image

Which is not correct, since, if I clicked “Refresh” there, I would see “2”:

image

Apparently, some customization is in order, and, it seems, what we need is an event that will trigger on update of the subgrid. If there were such an event, I could just refresh the form to update the data.

This seems to be a no-brainer. For the form refresh, there is formContext.data.refresh method:

https://docs.microsoft.com/en-us/powerapps/developer/model-driven-apps/clientapi/reference/formcontext-data/refresh

For the sugrid, there is addOnLoad method for adding event handlers:

https://docs.microsoft.com/en-us/powerapps/developer/model-driven-apps/clientapi/reference/grids/gridcontrol/addonload

So, it seems, I just need to use addOnLoad to add a listener function, and, from that function, I need to refresh the form.

Except that, as it turned out, there are a few caveats:

  • When you open a record in your app, form onLoad will fire first. This is a good place to call addOnLoad for the grid
  • Gird’s onLoad event will follow shortly. But only if there is some data in the grid. Otherwise, it won’t happen for an empty grid
  • Every time a linked record is added to the grid or removed from it, grid’s onLoad event will fire. Even one the last record has been removed from the grid and the grid is empty after that
  • Once formContext.data.refresh is called, form data, including all grids on the form, will be refreshed. Form onLoad won’t fire, but onLoad event for the grid will fire at that time (Although, see note above about empty grids). This may lead to an infinite recursion if another formContext.data.refresh is called at that time

 

Strangely, I could not find a simple solution for that recursion problem above. At some point, I figured I could just add a variable and use it as a switch. So, once in the grid’s “onload” event, I would check if it’s set to true, and, if yes, would reset it and do nothing. Otherwise, I would set it to true, and, then, would call formContext.data.refresh

This was supposed to take care of the recursion, since I would be calling “refresh” every second time, and, therefore, the recursion wouldn’t be happening. And this was all working great until I realized that, when the form opens up initially, there is no way of telling if grid’s onload will happen or not (since that depends on whether there are any linked records – see the list above). Which means I can’t be sure which is the “first” time and which is the “second” when it comes to the grid events.

Eventually, I got a solution, but this now involves an API call to check modifiedon date. Along the way, it turned out that “modifiedon” date that we can get from the attributes on the form does not include seconds. You can try it yourself – I was quite surprised.

On the other hand, if we use Xrm.WebApi.retrieveRecord, we can get modifiedon date with the seconds included there.

What I got in the end is javascript code below.

  • gridName should be updated with the name of your grid control
  • onFormLoad should be added as an onLoad event handler for the form
  • onFormSave should be added as an onSave event handler for the form

 

Basically, this script will call refresh whenever modifiedon date changes after a grid control has been reloaded. Keeping in mind that I’d need to compare seconds as well, I am using Xrm.WebApi.retrieveRecord to initialize lastModifiedOn variable in the form onLoad.

And, then, I’m just using the same API call to verify if modifiedon has changed (and, then, to call “refresh”) in the grid onLoad event.

Finally, I need onFormSave to reset lastModifiedOn whenever some other data on the form is saved. Otherwise, once the form comes back after “save”, all grids will be reloaded, and, since modifiedon will be updated by then, an additional refresh will follow right away. Which is not ideal, of course.

 

var formContext = null;
var lastModifiedOn = null;
var gridName = "Details";

function onFormLoad(executionContext)
{
  formContext = executionContext.getFormContext();
  //Can't use
  //lastModifiedOn = formContext.getAttribute("modifiedon").getValue();
  //Since that value does not include "Seconds"
  //Also, this needs to be done for "updates" only
  if(formContext.ui.getFormType() == 2){
    Xrm.WebApi.retrieveRecord(formContext.data.entity.getEntityName(), formContext.data.entity.getId(), "?$select=modifiedon").then(onRetrieveModifiedOn);
  }
}

function onFormSave()
{
	//Not to refresh on save
	lastModifiedOn = null;
}

function onSubgridLoad(executionContext)
{
   Xrm.WebApi.retrieveRecord(formContext.data.entity.getEntityName(), formContext.data.entity.getId(), "?$select=modifiedon").then(onRetrieveModifiedOn);
}

function onRetrieveModifiedOn(result)
{
	if(lastModifiedOn != result.modifiedon)
	{
		debugger;
		var doRefresh = false;
		if(lastModifiedOn == null){
			formContext.getControl(gridName).addOnLoad(onSubgridLoad);
		}
		else{
			doRefresh = true;
		}
		lastModifiedOn = result.modifiedon;
		if(doRefresh) formContext.data.refresh();
	}
}

 

Have fun with the Power!

PCF Controls solution dependencies

I have definitely managed to mess up my PCF controls solution a few weeks ago, since I put some test entities into that solution, and, then, I missed to include a few dependencies. Definitely my apologies for that to everyone who tried to deploy that solution while I was happily spending time on vacation, but, hopefully, this post will help.

First of all, there are two different solutions now. In the main solution, I have those PCF controls and a plugin to support N:N.

Then, in a separate solution, I have all the test entities and forms to set up a quick demo.

Those two solutions should be imported in exactly this order, since, of course, you won’t be able to install “demo” solution having not installed the PCF solution first:

This is a good lesson learned, though. I guess it does make sense to always create a separate solution for the PCF controls?

If you are an ISV, and you are using those controls in your own solutions, you would probably want to be able to update the PCF controls without having to update anything else.

If you are developing PCF controls internally, it’s, essentially, the same idea, since you may want to reuse those controls in various environments internally.

Although… here is what looks a little unusual here. I can’t recall any other solution component that would be so independent from anything else. In the past, we used to put workflows into separate solutions to ensure we can bring in required reference data first. We might use separate solutions for a set of base entities since we’d be building other solutions on top of that core entity model. We might use dedicated solutions for the plugins, since plugins might make our solution files really big.

Still, those were all specific reasons – sometimes, they would be applicable, and, sometimes, they would not be. As for the PCF, when all the entity names, relationship names, and other configuration settings are passed through the component parameters, it seems we have a solution component that will be completely independent from anything else most of the times.  Instead, other components in the system will become dependent on the PCF components as time goes, so it probably make sense to always put PCF into a separate solution just because of that.

A CDS security model which supports data sharing, but which is not using business units or access teams

We all know about the owner teams, security roles, access teams, field security profiles, etc. But there is yet another not-so-obvious security feature which controls access to CDS records through the “reparent” behavior on the “parental”/”configurable cascading” 1:N relationship. Why does it matter?

See, on the diagram below, even though the security role is configured to give access to the user-owned records only, my User A can still access records on the far right side of the parental entity hierarchy:

How come? Well, carry on reading…

I’ve been trying to figure out how to set up CDS security for the very specific requirements, and it has proven to be a little more complicated than I originally envisioned (even though I had never thought it would be simple).

To start with, there are, essentially, only two security mechanisms in CDS:

  • Security roles
  • Record sharing

Yes, there are, also, teams. However, teams are still relying on the security roles/record sharing – they are just adding an extra layer of ownership/sharing.

There is hierarchy security, too. But it’s only applicable when there is a hierarchy relationship between at least some users in the system, and it’s not the case in my scenario.

And there is Field Security, of course, but I am not at that level of granularity yet.

There is one additional aspect of CDS security which might be helpful, and that’s the ability of CDS to propagate some of the security-related operations through the relationships:

image

However, if you try using that, you’ll quickly find out that there can be only one cascading/parental relationships per entity:

image

“The related entity has already been configured with a cascading or parental relationship”

Which makes sense – if an entity had two or more “parent” entities (which would have  cascading/parental relationships with this entity), the system would not, really, know, which parent record to use when cascading “share”/”assign” operations through such relationships. The first parent record could be assign to one user, and the second one could be assigned to the another user. There would be no way for the system to decide which user to assign the child record to.

Hence, there is that limitation above. Besides, “cascading” only happens when an operation occurs on the parent record. So, for instance, if a new child record is added, it won’t be automatically shared/assigned through such a relationship.

On the other hand, there is “Reparent” behavior which happens when a child record’s parent is set. In which case the owner of the parent record gets access to the child record.

With all that in mind, the scenario I have been trying to model (from the security standpoint) is this:

  • There is a single CDS environment
  • There are functional business teams – each team corresponds to a model-driven app in the environment
  • Within each application, there is a hierarchy of records (at the top, there is a case. Then there are direct and indirect child entities)
  • There are some shared entities
  • Functional team members are supposed to have full access to all corresponding application features/entities
  • The same user can be a member of more than one functional team

This “business security” model does not map well to the business units, since a user in CDS can be a member of only one business unit, and, as mentioned above, in my case I need the ability to have the same user added to multiple “functional teams”.

One way to do it would be to micromanage record access by sharing every record with the teams/users as required. That can become messy, though. I would need a plugin to automate sharing, I would need to define, somehow, which teams/users to share each record with, etc. Besides, “sharing” is supposed to be an exception rather than a norm because of the potential performance issues.

Either way, since I can’t user multiple business units, let’s assume there is a single BU. This means there are only two access levels I can work with in the security roles:

  • User
  • Business Unit

 

image

“Parent-Child” and “Organization” would not make any difference when there is only one business unit.

I can’t set up the security role with the “Business Unit” access, since every user in that BU will have access to all data. Which is not how it should be.

But, if I configure that security role to allow access to the “user-owned” records, then there is a problem: once a record is assigned to a user, other users won’t be able to see that record.

It’s almost like there is no solution to this model, but, with one additional assumption, it may still work

  • Let’s create an owner team per “functional team”
  • Let’s create a security role which gives access to the “user-owned” records and grant that role to each team
  • Let’s keep cases (which are at the top of the entity hierarchy) assigned to the teams
  • And let’s add users to the teams (the same user might be added to more than one team)

 

Would it work? Yes, but only if there is a relationship path from the case entity to every other entity through the relationships with cascaded “Reparent” behavior.

That’s how “regarding” relationship is set up for notes and activities out of the box (those are all parental relationships), so I just need to ensure the same kind of relationship exists for everything else:

image

All users which are members of those teams above will get access to the cases which are assigned to the teams. And, as such, they will also have access to the “child” records of those cases.

If a new child record is created under the case (or under an existing case’s child record), that record will still be accessible to the team members because of “reparent” behavior.

So, as long as cases are correctly routed to the right teams… this model should work, it seems?

What’s unusual about it is that not a single security role will have “business-unit” (or deeper) access level, there will be no access teams, and, yet, CDS data will still be secured.

And, finally, what if I wanted to assign cases to the individual users? That would break this whole “reparent” part  (since team member won’t have access to the case anymore). However, what if there were a special entity which would be a case’s parent, and what if, for each case, the system would create a parent record and assign it to the team? Then, out of a sudden, “reparent” would kick in, and all team members would get access to the cases AND to the child records of those cases. Even if those cases were assigned to the the individual users. Of course this would mean I’d have to reconfigure existing parental relationship (which is between cases and “Customers”). But, in this scenario, it seems to be fine.

PS. As for the “reparent”, you will find some additional details below. However, even though  that page is talking about “read access” rights, it’s more than that (write access is also inherited, for example):

https://docs.microsoft.com/en-us/dynamics365/customerengagement/on-premises/developer/entity-relationship-behavior#BKMK_ReparentAction

Application Insights for Canvas Apps?

In the new blog post, Power Platform product team is featuring Application Insights integration for Canvas Apps:

https://powerapps.microsoft.com/en-us/blog/log-telemetry-for-your-apps-using-azure-application-insights/

It does look great, and it’s one of those eye-catching/cool features which won’t leave you indifferent for sure:

image

Although, I can’t get rid of the feeling that we are now observing how “Citizen Developers” world is getting on the collision course with the “Professional Developers” world.

See, every time I’m talking about Canvas Apps, I can’t help but mention that I don’t truly believe that real “citizen developers” are just lesser versions of the “professional developers”.

If that were the case, a professional developer would be able to just start coding with Canvas Apps right away. Which they can, to an extent, but there is an always a learning curve for the professional developers there. On the other hand, “Citizen Developers”, unless they have development background, may have to face an even steeper learning curve, and not just because they have to learn to write functions, understand events, etc. It’s also because a lot of traditional development concepts are starting to trickle into the Canvas Applications world.

ALM is, likely, one area where both worlds are not that different. Since it’s all about application development, whether it’s lo-code or not, the question of ALM comes up naturally, and, out of a sudden, Citizen Developers and Professional Developers have to start speaking the same language.

As is in the case of Application Insights integration. I don’t have to go far for the example:

image

“Microsoft Azure Resource”, “SDK”,  “telemetry”, “instrumentation key” – this is all written in a very pro developer-friendly language, and, apparently, this is something “Citizen Developers” may need to learn as well.

Besides, using Application Insights to document user journeys seems to make sense only when we are talking about relatively complex canvas applications which will live through a number of versions/iterations, and that all but guarantees a “citizen developer” must have some advanced knowledge of the development concepts to maintain such applications.

Well… this was mostly off-topic so far to be fair.

Getting back to the Application Insights, we just had a project went live with a couple of “supporting” canvas applications, and I am already thinking of adding Application Insights instrumentation to those apps, so I could show application usage patters to the project stakeholders. That would certainly be a screen they might want to spend some time talking about.

So, yes, I’m pretty sure we just got a very useful feature. If anything is missing there, it’s probably having a similar feature for the model-driven appsSmile

TCS Tools v 1.0.23.0

It’s been a while since I’ve updated TCS Tools the last time – there are a few reasons for that, of course. First of all, the most popular component in that solution has always been “attribute setter” which essentially allowed to do advanced operations in the workflows using FetchXml:

  • Using FetchXml to query and update related child records (or to run a workflow on them)
  • Using FetchXml to query and update related parent record (or to run a workflow on that record)

 

with the Power Automate Flows taking over process automation from the classic workflows, most of that can now be done right in the Flows, though there are a couple of areas where TCS Tools might still be useful:

  • Real-time classic workflows (since there are no real-time Flows)
  • Dynamics On-premise

 

With the on-premise version, it’s getting really complicated these days. I know it does exist in different flavors (8.2 is, likely, the most popular). Unfortunately, I have no way of supporting on-premise anymore.

This only leaves real-time classic workflows in the online version as a “target” for TCS Tools at the moment.

With all that said, I just released a minor update which fixes an issue with the special characters not being encoded properly (for the details, have a look at the “invalid XML” comments here)

To download and deploy the update, just follow the same steps described in the original post:

https://www.itaintboring.com/tcs-tools/solution-summary/

N:N Lookup on the new record form? Let’s do it!

It was great to see how N:N lookup PCF control has sparked some interest, but there are still a few things that could(and probably should) be added.

For example, what if I wanted to make it work when creating a new record? Normally, a subgrid won’t event show up on the new record form. But, in the updated version of the N:N lookup, it’s actually possible now:

ntonmultiselectoncreate

So, where is the catch?

The problem there is that there is no way for the PCF control to associate anything to the record being created, since, of course, that record does not exist yet. But, I thought, a “post operation” plugin would certainly be able to do it:

 

image

If you wanted to try it, here is what you need:

NToNMultiSelect control has been updated, too

You can use the same approach with any entity, just keep in mind a few things.

NToNMultiSelect is supposed to be bound to a single line text control. I should probably change this to “multiline”, but, for now, that’s what it is. Since this control is passing JSON data through that field, the field should be long enough (2000 characters). Yes, there is still room for improvement.

Also, you will need to register a plugin step on each entity which is using this control:

image

It should be registered in the PostOperation, and it should be synchronous.

The plugin will go over all the attributes, and, if any of them includes data in the required format, it will parse the data, and it will create required record associations.

That’s it for today – have fun with the Power! (just testing a new slogan hereSmile )

Is it a multiselect optionset? Nope… it’s an N:N lookup

If you ever wanted to have your own multiselect optionset which would be utilizing an N:N relationship behind the scene, here you go:

ntonmultiselect

It works and behaves similarly to the out-of-the-box multiselect optionset, but it’s not an option set. It’s a custom PCF control that’s relying on the N:N relationship to display those dropdown values and to store the selections.

Turned out it was not even that difficult to build this – all that was needed is to combine Select2 with PCF

The sources (and the solution file) are on github: https://github.com/ashlega/ITAintBoring.PCFControls

This is the first version, so it might not be “final”, but, so far, here is how this control is supposed to be configured:

  • You can use it for any single line text control
  • There are a few properties to set:
  • image

Linked Entity Name: “another” side of the N:N

Linked Entity Name Attribute: usually, it would be “name” attribute of the linked entity

Linked Entity ID Attribute: and this is the “Id” attribute

Relationship Name: this is the name of the N:N relationship (from the N:N properties page)

Relationship Entity Name: this is the name of the N:N relationship entity name (from the N:N properties page)

Some of those properties could probably be retrieved through the metadata requests, but, for now, you’ll just need to set them manually when configuring the control.

PS. There is more to it now (Jan 31): https://www.itaintboring.com/dynamics-crm/nn-lookup-on-the-new-record-form-lets-do-it/

2020 Release Wave 1 – random picks

Looking at the 2020 Release Wave 1 features, it’s kind of hard to figure out which ones will be more useful. Somehow, all of those I’ve read through so far seem to have the potential to strike a chord with those working with Power Platform / Dynamics 365, so it’s going to be a very interesting wave.

Here are just a few examples:

Enabling printable pages in canvas apps

“Makers are able to configure a printable page in their canvas apps, taking the content on the screen and turning it into a printable format (PDF)”

I was talking about it to the client just the other week – they wanted to know if there is a way to print a Canvas App form. It’s still not exactly around the corner, since public preview of this feature is coming in July 2020, but for a lot of enterprise projects this is, actually, not too far away.

General availability for large files and images is coming in April 2020

Are you still not comfortable with Sharepoint integration for some reason and need a way to link large files directly to the records in CDS? There you go:

image

Forms displayed in modal dialogs

Do you want that command bar button to open a dialog before you deactivate a record? Or, possibly, before you close a case?

You will be able to open regular forms in the modal popup dialogs now. This kind of functionality is something we’ve been asking about for years:

“Users do not have to navigate away from a form to create or edit a related record. This greatly improves productivity by reducing clicks and eliminating the need to do unnecessary navigation back and forth across forms.”

image

Actually…

There is going to be a configurable case resolution page in Wave 1

“Choose between the non-customizable modal dialog experience (default setting) and the customizable form experience”

Will be it based on the modal dialog forms mentioned above? We’ll see soon, I guess.

“Save” button is back

It’s not hiding down there anymore – it’s back at the top (although, I think it’s down there as well.

Btw, technically, the description given in the release plan is not 100% correct: “Before this release, if the auto save option was turned on, both options were hidden and not available in the command bar”

See, in the releases that might now be long forgotten, “save” button was always visible at the top. I guess the good things are coming back sometimesSmile

License enforcement for Team Member licenses

Team member licenses have always been a problem because somewhat vague language around them could not stop people from trying to utilize those licenses. After all, the price could be really attractive.

Now that Power Apps have there own $10 license, and, so, Team Member license only makes sense for Dynamics 365, license enforcement will be coming in.

image

Why do I think it’s a good thing? Well, that’s because it brings certainty and leaves no room to the interpretation. The clients won’t be at risk of violating the license terms once those terms are, actually, enforced.

There is more….

Flow steps in business process flows, secrets management in the flows, etc etc

Have a look for yourself:

2020 Release Wave 1 for Power Platform

2020 Release Wave 1 for Dynamics 365

Lookup filtering with connection roles

Here is what I wanted to set up today:

There is a custom SkillSet entity that has an “Advisor” field. That field is a lookup to the out-of-the-box contact entity. However, unlike with a regular lookup, I want that “Advisor” field to only display contacts which are connected to the current skillset through the “Expert” connection role.

In other words, imagine I have the skillset record below, and it has a couple of connected contacts (both in the “Expert” role):

image

I want only those two to show up in the lookup selector when I am choosing a contact for the “Advisor” field:

image

Even though there are, of course, other contacts in the system.


Actually, before I continue, let’s talk about connections and connection roles quickly. There is not a lot I can say in addition to what has already been written in the docs:

https://docs.microsoft.com/en-us/powerapps/maker/common-data-service/configure-connection-roles

Although, if you have not worked with the connections before, there is something to keep in mind

Connection roles can connect records of different types, but there is neither “source” nor “target” in the role definition

It’s not as if there were a source entity, a target entity, and a role. It’s just that there is a set of entities, and you can connect any entity from that set to any other entity in that set using your connection role:

image

Which may lead to some interesting effects – for example, I can have a SkillSet connected to a Contact as if that SkillSet were an expert, which does not really make sense:

image

But, of course, I can get a contact connected to a skillset in that role, and that makes much more sense:

image

 


That’s all great, but how do I filter the lookup now that I have an “Expert” role, and there are two contacts connected to the Power Platform skillset through that role?

That’s where we need to use addCustomView method

Why not to use addCustomFilter?

The first method (addCustomView) accepts complete fetchXml as one of the parameters, which means we can do pretty much anything there. For example, we can link other entities to define more advanced conditions.

The second method (addCustomFilter) accepts a filter to be applied to the existing view. We cannot use this method to define a filter on the linked entities.

In case with the connections, what we need is a view that starts with the contacts and that only displays those which are connected to the selected SkillSet record in the “Expert” role like this:

image

So… You will find a link to the github repo below, but here is the script:

function formOnLoad(executionContext)
{

	var context = executionContext.getFormContext();
	
	var viewId = "bc80640e-45b7-4c51-b745-7f3b648e62a1";
	var fetchXml = "<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='true'>"+
	  "<entity name='contact'>"+
		"<attribute name='fullname' />"+
		"<attribute name='telephone1' />"+
		"<attribute name='contactid' />"+
		"<order attribute='fullname' descending='false' />"+
		"<link-entity name='connection' from='record2id' to='contactid' link-type='inner' alias='ce'>"+
		  "<link-entity name='connectionrole' from='connectionroleid' to='record2roleid' link-type='inner' alias='cf'>"+
			"<filter type='and'>"+
			  "<condition attribute='name' operator='eq' value='Expert' />"+
			"</filter>"+
		  "</link-entity>"+
		  "<link-entity name='ita__skillset' from='ita__skillsetid' to='record1id' link-type='inner' alias='cg'>"+
			"<filter type='and'>"+
			  "<condition attribute='ita__name' operator='eq' value='" + context.getAttribute("ita__name").getValue() + "' />"+
			"</filter>"+
		  "</link-entity>"+
		"</link-entity>"+
	  "</entity>"+
	"</fetch>";
	
	var layoutXml = "<grid name='resultset' object='2' jump='fullname' select='1' preview='0' icon='1'>"+
	  "<row name='result' id='contactid'>"+
		"<cell name='fullname' width='300' />"+
	  "</row>"+
	"</grid>";
	
    context.getControl("ita__advisor").addCustomView(viewId, "contact", "Experts", fetchXml, layoutXml, true);
}

 

What’s happening in the script is:

  • It defines fetchXml (which I downloaded from the Advanced Find)
  • It dynamically populates skillset name in the fetchXml condition
  • Then it defines layout xml for the view. I used View Layout Replicator plugin in XrmToolBox to get the layout quickly:
  • image
  • Finally, the script calls “addCustomView” on the lookup control

 

And, of course, that script has been added to the “onLoad” of the form:

image

Now, I used connections above since that’s something that came up on the current project, but, of course, the same technique with custom views can be applied in other scenarios where you need to create a custom lookup view.

Either way, if you wanted to try it quickly, you will find unmanaged solution file in the git repo below:

https://github.com/ashlega/ItAintBoring.ConnectionRoleFilteredLookup

Have fun!

Power Platform Admin vs Dynamics 365 Admin

If you’ve been using Dynamics 365 Admin role to delegate Dynamics/Power Platform admin permissions to certain users, you might want to have a look at the Power Platform Admin role, too, since it may work better in some cases.

The main difference between those two roles is that you may need to add Dynamics 365 Admins users to the environment security group in order to let them access the environment, whereas you don’t need to do it for the Power Platform Admins:

https://docs.microsoft.com/en-us/power-platform/admin/use-service-admin-role-manage-tenant

image

Here is a quick illustration of how it works:

1. New user, no admin roles

No environments are showing up in the admin portal:

image

2. Same user, Power Platform Admin role

Six environments are showing up:

image

3. Same user, Dynamics 365 Admin role

Only five environments are showing up now since the 6th one has a security group assigned to it, and my user account is not included into that group:

image

Still, both roles are available and it may probably make sense to use Dynamics 365 Admin in those situations when you want to limit permissions a bit more. Although, the whole reason for this post is that we have found it a little confusing that such users must still be added to the environment security group, and, for us, it seems switching to Power Platform Admin might make this a little more straightforward.