This might be the dumbest blog in a while, but I am adding it in case others have problems with the same issue.
Recently I needed to add new products to the product table of Dynamics, and the buttons for adding the products/families were gone.
I of course had to look at the documentation in case there was something obvious I was missing. The documentation only wanted to ensure that I had a license + security role.
All of this was checking out on my side as I had a license and I was an administrator. Still, I was desperate trying to understand such a stupid issue that I made sure I had enough roles:
Eventually, I got some help from friends, and a huge thank you to Vivian Voss for pointing me in the right direction. First I was told to check the “obvious”.
Are you able to configure products via the old UI?
Vivian Voss
Well, turns out I was.
Solution?
Go to the Default Solution of configuration and make sure that “read only” was not on for the product table in the new UI😂😂🤷♂️🤷♀️🤷
The only thing that I can think of is an update from Microsoft changing this value for some odd reason. The fact that it is read-only in the Unified Client makes no sense, but hey. Not the first time something like that has happened.. hehe.
Just make sure this is OFF, Save changes, and then publish the table. You should be able to configure products again.
By now I am sure most of you have heard about ChatGPT. The AI product is pouring out surprisingly intelligent responses to all sorts of questions that people all around the world have given it lately.
There have been examples of the tool writing papers for school, creating blog posts for people, and helping formulate fairly complex text for webpages and marketers. One thing that someone tipped me off about was the ability to write code, and I was a bit curious as to what that would mean.
So one of the many gaps in my knowledge is specifically regarding Plugins. A plugin is a piece of backend code that runs in real time when triggers occur in Dataverse. I tried doing a little plugin with some help a few years ago and wrote about it on the Company Blog – Plugin in 1 hour.
I was curious how ChatGPT would do with a similar issue, so I took it for a spin😊
Question
First off I started out with a question that you write the same way you would anything Google/Yahoo/AltaVista/Bing/AskJeeves/DuckDuckGo/etc etc etc
The response I get is fairly spot on. Certainly, some will comment about something not being 100% accurate here, but this is more than good enough and quite well-written!
This wasn’t exactly what I was hoping for, so I asked a follow-up question to my original question.
It thought for a few moments before it started spitting out CODE!! 🤯🤯🤯🤯 (above is just a snip of the total code. I put it all in a code box below).
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Linq;
namespace MyPlugins
{
public class SumProductValues : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// Obtain the execution context from the service provider.
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
// Obtain the organization service reference which you will need for CRUD operations.
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
// Retrieve the Opportunity record that the plugin is running against.
Entity opportunity = (Entity)context.InputParameters["Target"];
// Create a query to retrieve the related products.
QueryExpression query = new QueryExpression
{
EntityName = "product",
ColumnSet = new ColumnSet("productid", "price"),
Criteria = new FilterExpression
{
Conditions =
{
new ConditionExpression
{
AttributeName = "parent_opportunity_id",
Operator = ConditionOperator.Equal,
Values = { opportunity.Id }
}
}
}
};
// Retrieve the related products.
EntityCollection products = service.RetrieveMultiple(query);
// Sum up the value of the products.
decimal totalPrice = products.Entities.Sum(product => product.GetAttributeValue<Money>("price").Value);
// Update the Total Price field of the Opportunity record.
opportunity["totalprice"] = new Money(totalPrice);
service.Update(opportunity);
}
}
}
🤯🤯🤯🤯
At this point, I was extremely excited to see that the result is pretty darn good. Sure the trigger here is based on Opportunity, and not the opportunity product, but that is beside the point. It seems that I could have formulated my question in a way that I would have understood it correctly. This is actual usable code for a plugin that could run on Opportunity level to update the sum of all Opportunity products.
Not really sure what more to write about this ATM.. Still trying to wrap my head around it all for now.
Initially, I was just going to explore some possibilities with Custom Pages, but decided to make a solution out of it instead. This way you can just download the solution and do the required modifications for your project (GIPHY API key), and you are good to go😁
I am really excited to hear your feedback after seeing/trying this out, and hopefully, I will be able to make it a lot better in next revisions of the Custom Page!
Go to my GitHub to get a hold of the solution. It’s a pretty “thin” solution that should be easy to configure and easy to delete if you no longer need it.
I have also included a video displaying how you install and use the solution
Installation and setup
Because of ribbon changes, I was forced to create a managed solution for this. The only way it wouldn’t overwrite the current opportunity ribbon.
If you want to play around with the solution, download the unmanaged. Just make sure you don’t have any other ribbon customizations on Opportunity. If you do back it up first!!
For everyone else, you have to download the managed solution to be on the safe side.
On my Gihub page I have written what components get installed, so you can easily remove it all at a later point in time.
Did you think my last post was the final stage of the “Custom Page – Win Notification“? Of course not!! We have one more important step to complete the whole solution.
I am all about salespeople being able to boast about their sales. If you meet a salesperson who doesn’t love to brag about closing, are they even in sales? 😉
Communication is key when you work in organizations of any size and location Onprem/Online/Hybrid. Doesn’t matter where you work, we can all agree that spreading the word around, and making sure everyone gets the latest news is hard. Microsoft is making it pretty obvious that Teams is a preferred channel, so that’s where I also wanted to focus my energy.
Notify the team
I decided to extend the solution a bit to reuse the GIF that we got from the last closing screen, and include it in the Teams integration that I wrote about earlier in the Adaptive Cards.
The first thing I did was to add a new group of fields for the Teams notification. I wanted to let the user choose if they wanted to post the sales to Teams or not. The obvious reason is that some opportunities might have to be reopened, and you don’t want the credit 2 times etc. It’s just a simple logo, text and boolean field.
The next step is adding the variable BoolPostTeams to the run statement of the Power Automate when I click “Confirm Win”.
On the Flow side of things, I will now receive 1 more variable that I can handle. Because I wanted to use the bool type, I have to convert the string “true/false” to a boolean
bool(triggerBody()['Initializevariable2_Value'])
What team are we updating?
Glad you asked 😉 A part of the solution is to include a Team ID and Channel ID. I added these fields to the Business Unit Table. When we store the Teams/Channel ID’s at this level, we can reuse the same solution for several teams in an organization without having to hard code anything.
These are simple text fields that will hold the unique values to the Team and the Channel you want to notify.
NB! A tip for getting the Teams ID and the Channel ID: Add the Team and Channel via the drop-down selector (flow step below). Then open the “peek code” to see what the ID’s of the teams are. If you don’t understand this step, just have a look at my video in the final post of the series 🎥
Back to the flow!
We update the get Opportunity with extra linked tables. This way we don’t have to do multiple retrieves, and only get the fields we need from the linked tables.
After we update the opportunity as closed with our Custom Action “close opportunity”, we check if the close dialog wanted to notify the team.
‘
Then we need to get the @mention tag for the given user that made the sales. Here I am using the email address from the SystemUser table we are getting from the Opportunity extended table.
Finally, we post the Adaptive Card to Teams and behold the wonder of the adaptive card!! 💘
I am using Dynamics 365 as an example, but the process will be the same for Power Apps – Model Driven. Only difference is that Dynamics 365 has the Opportunity table that we are wanting to use.
I have also added the simple button setup to Github, if you just want to give it a go. 👉DOWNLOAD SIMPLE BUTTON HERE👈
Creating an app and adding the button
Start off by adding an app to a solution and giving it a proper name. In my case, I am choosing Sales Win, because I am creating a simple app for everyone to see the functionality. You could of course just add this to your existing app if that is what you want to do.
For the tricky part, you need to open the Command Bar. This is essentially Ribbon Workbench jr😉 In time I would only imagine that most of the Ribbon Workbench would be available here, but we are probably still a few years away from a complete transition here.
Choose the main form for this exercise, and then create a new Command. Command is actually a button. Why it’s called Command I’m not sure as we all are used to add buttons to a ribbon🤷♂️
On the right side, you can add an image, and I usually find mine through SVG’s online. SVG’s for download <- a blog post I wrote about the topic. Add this as a webresource.
Now, we are going to have to create some JavaScript. It’s actually the only way to open a Custom Page. The JavaScript is pretty “simple”, and I will provide you with the copy-paste version.
JavaScript
🛑BEFORE YOU STOP READING BECAUSE OF JAVASCRIPT🛑
I will provide the JavaScript you need. Unfortunately, you will still need to understand how to copy-paste some script for stuff like custom pages to work.
The only parameter you have to change here if you are creating everything from scratch is: name: “saleswin_saleswin_c0947” <- replace with the name of your custom page.
function CloseWON(formContext) {
//Get Opportunity GUID and remove {}
var recordGUID = formContext.data.entity.getId().replace(/[{}]/g, "");
// Centered Dialog
var pageInput = {
pageType: "custom",
name: "saleswin_saleswin_c0947", //Unique name of Custom page
entityName: "opportunity",
recordId: recordGUID,
};
var navigationOptions = {
target: 2,
position: 1,
width: {value: 450, unit: "px"},
height:{value: 550, unit: "px"}
};
Xrm.Navigation.navigateTo(pageInput, navigationOptions)
.then(
function () {
// Called when the dialog closes
formContext.data.refresh();
}
).catch(
function (error) {
// Handle error
alert("CANCEL");
}
);
}
This JavaScript opens up a custom page and sends in a GUID parameter (look at PageInput) to use within the page. This is just how Custom Pages work, so don’t try to do any magic here!
It’s important to hide or show the button at the correct times, and that is why you have to add logic. With the following function, the button is only visible when the Opportunity is in edit mode. When creating a new Opportunity you will not see this button.
Self.Selected.State = FormMode.Edit
Add a new page to your solution
A custom page is a lot like a Canvas App, but it’s not exactly the same. Not all functionality is the same as a Canvas App, so you need to get familiar with it first.
The first thing I noticed was the lack of multiple screens.
It seems not impossible to add more screens, but Microsoft has hidden this feature. The reason seems to be related to isolating a Screen to be a specific application. Might make sense, but not for my use case. It does make somewhat sense because a Custom Page can be opened from everywhere within Model Driven Apps. It’s actually not tied to anything (entity) at all. If you need to turn it on: 👇
Please don’t judge this Canvas App ATM. It will only get better over time 😂
I added labels and text boxes without any logic to them yet and added a data source for Opportunities. This is all for the next step of our configuration of the Sales Win dialog.
Last step (maybe the most important one)
We now need to add the custom page to the APP.
Make sure you UNCHECK the “Show in navigation”. Otherwise, you will see this page as a navigation option on the left side like account, contact, oppty etc.
Finally, publish in the ribbon editor
What have we achieved so far?
Once the button is pushed, the Custom Page loads. 🙏👍🎉
Tiny blog for a tiny button 😂 This is only relevant if you have users that work in Outlook Web. Every now and then I do encounter a few Apple users that prefer the Outlook Web, even though it works well with Outlook for Mac.
Outlook client
If you use the Outlook client you know the button from the ribbon. Click the button do load the client.
Outlook Web.
OOTB the Dynamics client is hidden once it is deployed for the user. Only way to find it is to open the actual email and choose the ellipsis
Great thing is that we can change the order of the buttons:)
Solution
Open the Outlook Web settings and choose “View All Outlook Settings”.
Find the Dynamics 365 button in the Customize Actions and click save.
Even though we all wish it wasn’t so, Document Locations still rule the integrations between Dynamics 365 and SharePoint. I’m not saying that I have a better idea what would be a smarter way of solving it, but it all seems a bit “2011” ish.
Last week I encountered a problem with the Document Locations for Teams, and I was surprised when I couldn’t find them in the Document Locations at first. The list only contained the SharePoint sites that the standard SharePoint connector uses.
In this list I was missing all of the Teams locations. Turns out that the view is only showing Active SHAREPOINT locations.. hehe
All you have to do is add the “MS TEAMS” to the search, and you should see all of the document locations for that also
Launching a new app or launching a new CRM system always leaves the users with the same question. Where do I find the application? At first I didn’t really understand the question, because I thought it was natural to bookmark the URL to your application ie https://www.company.crm4.dynamics.com/***** etc.
Eventually I realized that most users are actually using the waffle menu in office 365 when navigating to applications that they don’t use continuously.
They were expecting to see the application in the list when you clicked the waffle menu, because this would save them time.
Luckily this is not a problem 😊 Open the all apps, and locate the app you are looking for
And just like that you now have a quick navigation to your CRM or Power App application in the Microsoft 365 app launcher👊
This post is almost not relevant any more due to the fact that we all will be pushed into the make.powerapps experience in “the fullness of time”, but I will still be using the classic viewer for the foreseeable future 🙂
From time to the images can get distorted due to unknown reasons. This is not a big deal as the buttons still work, but can be annoying for the users. You might have seen this in the 2 places:
There seem to be several ways to solve this issue, but not all seem to work the same. I have seen some options describing a cache clear in the console of the browser, but for some reason none of those worked for me. Lately I have had to open the developer tools and delete a bit more manual.
Click F12 in the browser where you see the error
Open the Application tab on the dev tools
Clear or delete anything within the storage related to dynamics.
Try to restart the browser, and hopefully that should do the trick:)
This is not the average Dynamics CRM post, but I was challenged by Malin Martnes to see if we could integrate the competency part of of Dynamics HR with Customer Engagement. The reason we wanted to look into the matter was because we thought it would be really easy!!! Turns out we were wrong.. hehe 😂
The first thing we did was figuring out what tables were accessible in the CDS integration. At the time of reading the blog, the number of entities was not a lot. The ones that I needed were not there. The complete list of tables that write directly to CDS can be found here: https://docs.microsoft.com/en-us/dynamics365/human-resources/hr-developer-entities
A lot of fields available, but just not the ones regarding competency.
What to do?!?!? Docs to the rescue !!
Thanks to a link from DOC’s I learned that Finance and Operations has a link to export data. You can choose whatever dataset you want, and export it to file. This has actually nothing to do with HR, but is a feature from Finance and Operations.
So I followed the blog but I used Flow instead of Logic Apps.
First start off by creating an export function in HR. Whenever this is called, it will answer your call with a file.
The flow looks like the logic app in structure, but I do a little extra in the end to extract the .zip file to excel.
The first hard part was actually finding the URL for HR. If you ask me it’s quite hidden. I had to have some help from Malin to figure out what my URL to the environment was.
The “Export Workers” has to be identical to the name of the export package. Then you have to decide what the name of the file I wanted to export was.
The delay is there because the service can sometimes use a little time with the response. Just let it work:)
This part of the flow was just to see if .zip file had been completed. When it was complete, we could get the actual package.
At this point I had absolutely no idea what I was doing, but it was working. The body of the HTTP GET function returned a .zip file. This I could create directly in a OneDrive connector.
The last step was then to extract the file from the .zip, and voila. I now had an automated Excel export from Dynamics HR.
For the last part I could simply connect a DataFlow for importing to Dynamics:)
Was there even a point to this?
Well.. Yes, and no.. The positive thing about this was learning that Finance and Operations has an export function that I could use for extremely simple integrations. This I might be able to use at some later timer.