Custom Page – Opportunity Close Solution Download

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.

💾 DOWNLOAD 💾

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.

Custom Page – Teams Integration

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.

parentaccountid($select=name), owninguser($select=fullname, internalemailaddress), owningbusinessunit($select=saleswin_teamschannelid,saleswin_teamsid)

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!! 💘

{
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "type": "AdaptiveCard",
    "version": "1.2",
    "body": [
        {
            "speak": "Sales Morale Boost",
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": 8,
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "🚨WIN ALERT🚨",
                            "weight": "Bolder",
                            "size": "ExtraLarge",
                            "spacing": "None",
                            "wrap": true,
                            "horizontalAlignment": "Center",
                            "color": "Attention",
                            "fontType": "Default"
                        },
                        {
                            "type": "TextBlock",
                            "text": "@{outputs('GetOpptyInfomation')?['body/name']}",
                            "wrap": true,
                            "size": "Large",
                            "weight": "Bolder",
                            "horizontalAlignment": "Center"
                        },
                        {
                            "type": "ColumnSet",
                            "columns": [
                                {
                                    "type": "Column",
                                    "width": 25,
                                    "items": [
                                        {
                                            "type": "TextBlock",
                                            "text": "🏠 Kunde",
                                            "wrap": true,
                                            "size": "Large",
                                            "weight": "Bolder",
                                            "horizontalAlignment": "Right"
                                        },
                                        {
                                            "type": "TextBlock",
                                            "text": "💲 Verdi",
                                            "wrap": true,
                                            "size": "Large",
                                            "weight": "Bolder",
                                            "horizontalAlignment": "Right"
                                        },
                                        {
                                            "type": "TextBlock",
                                            "text": "👩‍🦲 Selger",
                                            "wrap": true,
                                            "size": "Large",
                                            "weight": "Bolder",
                                            "horizontalAlignment": "Right"
                                        }
                                    ]
                                },
                                {
                                    "type": "Column",
                                    "width": 50,
                                    "items": [
                                        {
                                            "type": "TextBlock",
                                            "text": "@{outputs('GetOpptyInfomation')?['body/parentaccountid/name']}",
                                            "size": "Large",
                                            "weight": "Bolder"
                                        },
                                        {
                                            "type": "TextBlock",
                                            "text": "@{outputs('GetOpptyInfomation')?['body/actualvalue']}",
                                            "weight": "Bolder",
                                            "size": "Large"
                                        },
                                        {
                                            "type": "TextBlock",
                                            "text": "@{outputs('Get_an_@mention_token_for_a_user')?['body/atMention']}",
                                            "weight": "Bolder",
                                            "size": "Large"
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]
        },
        {
            "type": "Image",
            "url": "@{body('Parse_JSON')?['data']?['images']?['original']?['url']}",
            "horizontalAlignment": "Center"
        }
    ]
}

Now, this is what an opportunity close dialog should look like!! 🙏🏆🌟🎉

Custom Page – Open Page with Ribbon Button

I am using Dynamics 365 as an example, but the process will be the same for Power Apps. Only difference is that Dynamics 365 has the Opportunity table that we are wanting to use.

If you want to learn more about Custom pages I suggest you look at the following posts:
Scott Durrow
Lisa Crosby
MCJ
Microsoft Custom Pages

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!

Other examples of how to load a custom page:
Microsoft Docs Custom Page

Hide-Show button

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. 🙏👍🎉

Ribbon Button – Custom Page / Dialog

The deprecation of Dialogs has been discussed WAAAY too many times, and I still feel there is a gap for simple dialogs that would do lots of magic. A while back Microsoft introduced Custom Pages as one option to solve this missing piece, but simplicity is just not there yet.

I have been reviewing lots of great material from fellow MVP’s to study up a bit on the topic
MCJ – Custom Page updating Model Driven app
Scott Durrow – Adding logic to the new button configurator
Lisa Crosbie – Creating custom page

But I can’t quite shake the feeling that it’s still pretty tech-intensive to be able to create a Custom Page and call in from Dynamics. You need to have a decent understanding of the following:

  1. Creating a ribbon button in the new app configuration (easy, but adding commands not so easy)
  2. Javascript to actually start the Custom Page (not for everyone)
  3. Canvas App configuration (I am not a front-end type of guy, and hate having to start with a blank slate every time).
  4. Power FX for the showing of a button + updating whatever you are doing in the canvas app back to the model-driven app.

All in all, I would say that this is probably what you can expect a Dynamics/Power Apps consultant to understand, but it is not given that everyone feels comfortable while configuring. To be honest, Ribbon Workbench isn’t easy without a few tutorials either, but one gets better over time. (did anyone ever get the invert of a true/false statement right the first time?😂).

What to do?

Let’s just get to the bottom of the real issue. How to solve the problem of the Custom Page with a button and at the same time created a great way for sales to have a lot of fun!😁

Over the next weeks, I will start to publish a series of Custom Page and Sales, to showcase what you can do to improve Sales morale and adoption

Every post will be shown on the new Sales Page that I have “Win Notification” so stay tuned!!

The full solution will be available for download in the end.

AI Builder + Lobe

During the recent Arctic Cloud Developer Challenge Hackathon I was playing around with AI Builder for the first time. The scenario we were going to build on there was the detection of Good guy / Bad guy.

The idea was that the citizens would be able to take pictures of suspicious behavior. Once the picture was taken, the classification of the picture would let them know if it was safe or not.

For this exercise I would use my trusted BadBoy/GoodBoy nephew toys:)

Lobe

To start it off I downloaded a free tool called Lobe. www.lobe.ai . Microsoft acquired this tool recently, and it’s a great tool to learn more about object recognition in pictures. The really cool thing about the software is that calculations for the AI model are done on your local computer, so you don’t need to setup any Azure services to try out a model for recognition.

Another great feature is that it integrates seamlessly with Power Platform. Once you train you model with the correct data, you just export it to Power Platform!👏

The first thing you need to do is have enough pictures of the object. Just do at least 15 pictures from different angles to make it understand the object you want to detect.

Tagg all of the images with the correct tags.

Next step is to train the model. This will be done using your local PC resources. When the training is complete you can export to Power Platform.

It’s actually that simple!!! This was really surprising to me:)

Power App

Next up was the Power App the citizens were going to use for the pictures. The idea of course that everyone had this app on their phones and licensing wasn’t an issue 😂

I just added a camera control, and used a button to call a Power Automate Cloud Flow, but this is also where the tricky parts began.

An image is not just an image!!!!! 😤🤦‍♂️🤦‍♀️

How on earth anyone is supposed to understand that you need to convert a picture you take, so that you can send it to Flow, only there to convert it to something else that then would make sense???!??!

Image64 and Power Automate – What a shit show

After asking a few friends, and googling tons of different tips/trics I was able to make this line here work. I am sure there are other ways of doing this, but it’s not blatantly obvious to me.

Set(WebcamPhoto, Camera1.Photo);

Set(PictureFormat,Substitute(Substitute(JSON(WebcamPhoto,JSONFormat.IncludeBinaryData),"data:image/png;base64,",""),"""",""));

'PowerAppV2->Compose'.Run(PictureFormat);

The receiving Power Automate Cloud Flow looked like this:

I tried receiving the image as a type image, but I couldn’t figure it out. Therefore I converted it to a Base64 I believe when sending to Flow. In the Flow I again converted this to a binary representation of the image before sending it to the prediction.

The prediction on the other hand worked out really nice!! I found my model that I had imported from Lobe, and used the ouputs from the Compose 3 action (base64 to binary). I have no idea what the differences are, but I just acknowledge that this is how it has to be done.. I am sure there are other ways, but that someone will have to teach me in the future.

All in all it actually worked really well. As you can see here I added all types of outputs to learn from the data, but it was exactly as expected when taking a picture of Winnie the Poo 😊 The bear was categorized as good, and my model was working.

Why Lobe?

One can wonder why I chose to use Lobe for this, when the AI Builder has the training functionality included within the Power Platform. For my simple case it wouldn’t really matter what I chose to use, I just wanted to test out the newest tech.

When working with larger scale (numbers) of images, Lobe seems to be a lot easier for the importing/tagging/training of the model. Everything runs locally, so the speed of training and testing is a lot faster also. It’s also simple to retrain the model an upload again. This being a hackathon it was important to try new things out 😊

More about AI builder

I talked to Joe Fernandez from the AI builder team, and he pointed me to some resources that are nice to checkout regarding this topic.

https://myignite.microsoft.com/sessions/a5da5404-6a25-4428-b4d0-9aba67076a08 <- forward to 11:50 for info regarding the AI Builder

https://youtube.com/watch?v=MQQmDUCufS8 <- Lobe

Business Unit Name Change

Something small, but yet useful. Thank you Tanguy for the tip🤗

As you probably figured out the Default Business unit name is something you can’t change OOTB after you have created a CRM/Dataverse environment.

If you are not careful when creating a new environment the org name will be set for you, and that is also when the Business Unit Name is set.

This can result in the following main Business Unit, and it just doesn’t make any sense. Also you can’t change it because Parent Business needs to be entered. Problem is that there is no parent to the parent in this case 🤣

XrmToolBox – Bulk Data Update to the rescue😎

Open the view for active Business Units and find your result.

⛔NB!! Make sure your search only returns one business unit

Next thing you do is set a fixed value, add attribute to the bulk job, and the update the record.

Power Apps Pay As you GO!!💸

When Microsoft introduced Azure for the Microsoft public, it was a new way of thinking. We were suddenly paying for what we needed and when we needed it. Amazon had been there for a long while, but for Microsoft customers this was a new way of thinking. After a skeptical start, this model has really become somewhat of a system standard.

As of today Power Platform will be available on Azure subscription! It is being introduced as a “Pay as you go” model. It is important that you don’t mistake this for the same as Azure. In Azure you actually only pay for the compute time used (in most cases), but here you will pay for a license once you use an application.

WOW THIS IS SOOOO COOL … Well, is it really?

Let’s just think about the following first. Just a few weeks ago Microsoft dropped the prices to half of what they used to cost. They are now only 5$ and 20$ for the different plans. When you think about the value you get from a Dataverse OOTB that is a BARGAIN already.

So why am I not overly excited about the “Pay as you Go” PAYGO model? Well, I don’t really see the big impact yet. Most of my customers are on the CSP agreement, and can flex as much as they feel for. Planning ahead for apps is also hard, and is counter intuitive for innovation. By releasing a plan as PAYGO, you essentially need to plan financially for all users that might use an app, while you silently hope that not all users actually use the app that month. For every user that didn’t use the app, you save some money.

I am sure that the plan makes sense for many scenarios, but I just don’t really see them yet. The good thing is that “limitations/possibilities” for the new plan will be monitored closely in the beginning to find the correct levels for all types of use cases. Remember to voice your opinion if you see some great opportunity. Microsoft will be listening😀

Pricing comparison

Standard Pricing App and User Plan

Standard Pricing Storage

PAYGO Pricing app

https://docs.microsoft.com/en-us/power-platform/admin/powerapps-flow-licensing-faq#add-ons

PAYGO Storage

https://docs.microsoft.com/en-us/power-platform/admin/powerapps-flow-licensing-faq#add-ons

Personal Thoughts

The only thing that we know for sure is that licensing will always be a situation where we as consumers want changes. We want more more more, and want to pay less less less. Microsoft will continuously find new license models to adapt to our wishes while finding ways to keep profits. Don’t get me wrong. I am all about Microsoft being able to charge what they want. After all it’s a great product!!! I’m just saying that you need to look behind the shining stuff before you automatically assume that everything new is automatically better.

What you need to do as a customer is get help to assess assess your licensing situation. Not only is licensing complex from a rules perspective, but the applications can be modified to adapt to licensing changes. I am not saying PAYGO is bad, but I’m not jumping on the PAYGO train quite yet. Most of my customers are CSP customers and have a lot of freedom with licensing (Up and Down). Just going to see what happens first 😁

I might also have misunderstood quite a lot in regards to the benefits received from this model, and if so I would love feedback to learn new ways of thinking!👌

GIF Custom Connector

In my last post I wrote about Adaptive Cards in Teams, but a vital factor for that adaptive card to be interesting is the content on the card. Dynamics 365 Sales Adaptive Card🚀.

One of the technical bits last time was connecting with an HTTP GET request to the API. I will be using the same information to create a custom connector so that anyone can reuse for the future 😁

Creating the Connector

Step one.. open https:///make.powerapps.com .

Step 2.. Break down the URL from the first picture like this

Step three… If you don’t really know how to do this, ask a friend!! 🙂

Step three again.. Enter the security settings. When entering the security settings and providing something more than blank, you will be prompted with the credentials first time you create a connection to the connector.

I broke the URL down further with the “api_key” as a query, so that it would show in the URL like the example on the first picture.

Step four.. Create a search tag like the one I had in the URL from the first picture

Step five.. Get the URL from the first picture with your API key, and add this to the import sample

Choose the GET in this case, and add the full URL

Your request should look something like this:

Step 6.. Add a connection to the connector and test with a tag. It should return some info like this:

Result

When you are done, you have a custom connector you can reuse from Power Apps, Flow or any other tool that can use custom connectors.

Connector Gallery 🌌

What better thing to do than release this to the https://connector.gallery when you are done creating a new connector?? :😁💪🥇

The rise of the Galleries🌌

By now I hope most people know the https://pcf.gallery (run by Guido Preite). A great page for sharing community components (PCF) and exposing awesome contributions to the rest of the world. 🌎

What I like about the PCF Gallery is the simplicity of the site only being about PCF components. This is why I asked Guido if we could create a similar site for other components regarding the Power Platform. He was so kind to share his code for the project, so Matt Beard and I decided to give it a go. 🤗

Connector Gallery

First out in list of future galleries is the CONNECTOR gallery . This site will contain all sorts of custom connectors for Power Platform that you can share with the community. If you want to contribute to this gallery, you only have to share the custom connector file you have on GitHub, and we will post it out!

Dynamics / Power Apps + Office App Launcher

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👊