Approvals in PowerApps with Power Automate, Teams, and Adaptive Cards

Are you looking for a simple and extensible no-code solution to do approvals in PowerApps? Perhaps you have built automation within PowerAutomate and are looking to provide a mechanism to “gate”, or provide a way to confirm your process prior to it executing. Or, you could be using quoting within Dynamics Customer Engagement and want to have your Sales manager review and approve quotes before they are sent to a customer. In any of these scenarios, you have come to the right place.! In this article, I will show you how to create your own custom approvals using PowerAutomate/Flows, Teams, and AdaptiveCards. 

But first, let's explore what Adaptive Cards are. 

image001

image003

To summarize, Adaptive Cards allow you to embed small custom UI elements within a web application (or any application that can render html such as Outlook or Teams) to provide additional information, or in the case of approvals, interactivity. In the following scenario, we will build a simple model within DataVerse to store our approval activities and then build an Adaptive Card which we will expose within Microsoft teams which will notify users when they have approvals that are pending. 

I will start by creating a custom approval activity in PowerApps. It can be customized with additional attributes as needed. For this blog post, I will keep it simple. 

image005

Next, let's create a Flow. I will use the trigger when a record is created, updated, or deleted. I will get the approval request record with the details of From and To and the regarding record. This information will later be used in the adaptive card and Teams. 

image006

Now, I will initialize some variables that will hold the values to be filled in the adaptive card. 

image007

Now, I need to set the variables I initialized in the previous step with the From and to values.  

image008

 

In the first two steps, I am extracting information from the activity party,  setting the formatted values in Name and setting the schema name and id in the second. 

Since we need the e-mail address, we will first have to call   the Metadata Service and get the correct pluralized EntitySetName. 

Next, I am getting the record's e-mail address based on if it was from a system user or a queue. 

Finally, I am updating the From e-mail. 

I will repeat the same steps for the recipient: 

image010

Now that I have all my information, I will use Post an Adaptive Card to a Teams user and wait for a response action. 

image012

For the message field, build your adaptive card Designer | Adaptive Cards and copy card payload and insert it. I am entering the information from my variables. 

 

I’ve used Expense Report as a starting point https://adaptivecards.io/samples/ExpenseReport.html. Once opened in the desginer, switch the host app to Teams Dark mode. To keep things simple for this scenario, I've customized it as follows: 

{ 

    "type": "AdaptiveCard", 

    "body": [ 

        { 

            "type": "Container", 

            "style": "emphasis", 

            "items": [ 

                { 

                    "type": "ColumnSet", 

                    "columns": [ 

                        { 

                            "type": "Column", 

                            "items": [ 

                                { 

                                    "type": "TextBlock", 

                                    "size": "Large", 

                                    "weight": "Bolder", 

                                    "text": "**EXPENSE APPROVAL**", 

                                    "wrap": true, 

                                    "style": "heading" 

                                } 

                            ], 

                            "width": "stretch" 

                        }, 

                        { 

                            "type": "Column", 

                            "items": [ 

                                { 

                                    "type": "Image", 

                                    "url": "${status_url}", 

                                    "altText": "${status}", 

                                    "height": "30px" 

                                } 

                            ], 

                            "width": "auto" 

                        } 

                    ] 

                } 

            ], 

            "bleed": true 

        }, 

        { 

            "type": "Container", 

            "items": [ 

                { 

                    "type": "ColumnSet", 

                    "columns": [ 

                        { 

                            "type": "Column", 

                            "items": [ 

                                { 

                                    "type": "TextBlock", 

                                    "size": "ExtraLarge", 

                                    "text": "${purpose}", 

                                    "wrap": true, 

                                    "style": "heading" 

                                } 

                            ], 

                            "width": "stretch" 

                        }, 

                        { 

                            "type": "Column", 

                            "width": "auto" 

                        } 

                    ] 

                }, 

                { 

                    "type": "TextBlock", 

                    "spacing": "Small", 

                    "size": "Small", 

                    "weight": "Bolder", 

                    "text": "[${code}](https://adaptivecards.io)", 

                    "wrap": true 

                }, 

                { 

                    "type": "FactSet", 

                    "spacing": "Large", 

                    "facts": [ 

                        { 

                            "title": "Submitted By", 

                            "value": "**${created_by_name}**  ${creater_email}" 

                        }, 

                        { 

                            "title": "Duration", 

                            "value": "${formatTicks(min(select(expenses, x, int(x.created_by))), 'yyyy-MM-dd')} - ${formatTicks(max(select(expenses, x, int(x.created_by))), 'yyyy-MM-dd')}" 

                        }, 

                        { 

                            "title": "Submitted On", 

                            "value": "${formatDateTime(submitted_date, 'yyyy-MM-dd')}" 

                        }, 

                        { 

                            "title": "Reimbursable Amount", 

                            "value": "$${formatNumber(sum(select(expenses, x, if(x.is_reimbursable, x.total, 0))), 2)}" 

                        }, 

                        { 

                            "title": "Awaiting approval from", 

                            "value": "**${approver}**  ${approver_email}" 

                        }, 

                        { 

                            "title": "Submitted to", 

                            "value": "**${other_submitter}**   ${other_submitter_email}" 

                        } 

                    ] 

                } 

            ] 

        }, 

        { 

            "type": "ColumnSet", 

            "spacing": "Large", 

            "separator": true, 

            "columns": [ 

                { 

                    "type": "Column", 

                    "items": [ 

                        { 

                            "type": "TextBlock", 

                            "horizontalAlignment": "Right", 

                            "text": "Total Expense Amount \t", 

                            "wrap": true 

                        }, 

                        { 

                            "type": "TextBlock", 

                            "horizontalAlignment": "Right", 

                            "text": "Non-reimbursable Amount", 

                            "wrap": true 

                        }, 

                        { 

                            "type": "TextBlock", 

                            "horizontalAlignment": "Right", 

                            "text": "Advance Amount", 

                            "wrap": true 

                        } 

                    ], 

                    "width": "stretch" 

                }, 

                { 

                    "type": "Column", 

                    "items": [ 

                        { 

                            "type": "TextBlock", 

                            "text": "$${formatNumber(sum(select(expenses, x, x.total)), 2)}", 

                            "wrap": true 

                        }, 

                        { 

                            "type": "TextBlock", 

                            "text": "(-) $${formatNumber(sum(select(expenses, x, if(x.is_reimbursable, 0, x.total))), 2)} \t", 

                            "wrap": true 

                        }, 

                        { 

                            "type": "TextBlock", 

                            "text": "(-) 0.00 \t", 

                            "wrap": true 

                        } 

                    ], 

                    "width": "auto" 

                }, 

                { 

                    "type": "Column", 

                    "width": "auto" 

                } 

            ] 

        }, 

        { 

            "type": "Container", 

            "style": "emphasis", 

            "items": [ 

                { 

                    "type": "ColumnSet", 

                    "columns": [ 

                        { 

                            "type": "Column", 

                            "items": [ 

                                { 

                                    "type": "TextBlock", 

                                    "horizontalAlignment": "Right", 

                                    "text": "Amount to be Reimbursed", 

                                    "wrap": true 

                                } 

                            ], 

                            "width": "stretch" 

                        }, 

                        { 

                            "type": "Column", 

                            "items": [ 

                                { 

                                    "type": "TextBlock", 

                                    "weight": "Bolder", 

                                    "text": "$${formatNumber(sum(select(expenses, x, if(x.is_reimbursable, x.total, 0))), 2)}", 

                                    "wrap": true 

                                } 

                            ], 

                            "width": "auto" 

                        }, 

                        { 

                            "type": "Column", 

                            "width": "auto" 

                        } 

                    ] 

                } 

            ], 

            "bleed": true 

        }, 

        { 

            "type": "ColumnSet", 

            "columns": [ 

                { 

                    "type": "Column", 

                    "verticalContentAlignment": "Center", 

                    "items": [ 

                        { 

                            "type": "TextBlock", 

                            "id": "showHistory", 

                            "horizontalAlignment": "Right", 

                            "color": "Accent", 

                            "text": "Show history", 

                            "wrap": true 

                        }, 

                        { 

                            "type": "TextBlock", 

                            "id": "hideHistory", 

                            "horizontalAlignment": "Right", 

                            "color": "Accent", 

                            "text": "Hide history", 

                            "wrap": true, 

                            "isVisible": false 

                        } 

                    ], 

                    "width": 1 

                } 

            ] 

        }, 

        { 

            "type": "Container", 

            "id": "cardContent4", 

            "isVisible": false, 

            "items": [ 

                { 

                    "type": "Container", 

                    "items": [ 

                        { 

                            "type": "TextBlock", 

                            "text": "* Expense submitted by **${created_by_name}** on {{DATE(${formatDateTime(created_date, 'yyyy-MM-ddTHH:mm:ssZ')}, SHORT)}}", 

                            "isSubtle": true, 

                            "wrap": true 

                        }, 

                        { 

                            "type": "TextBlock", 

                            "text": "* Expense ${expenses[0].status} by **${expenses[0].approver}** on {{DATE(${formatDateTime(approval_date, 'yyyy-MM-ddTHH:mm:ssZ')}, SHORT)}}", 

                            "isSubtle": true, 

                            "wrap": true 

                        } 

                    ] 

                } 

            ] 

        }, 

        { 

            "type": "Container" 

        } 

    ], 

    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 

    "version": "1.5", 

    "fallbackText": "This card requires Adaptive Cards v1.2 support to be rendered properly." 

} 

Make sure to replace sample values with expressions from PowerAutomate variables of data from Approval Request.

image013

Lastly, we must update the approval request in PowerApps. Once we get the response back from Teams action, I am using the Update a record action and updating the approval activity with the appropriate Activity Status and Status Reason. 

image015

Here is how the run looks. 

image016

image018

And there you have it. I hope this blog post got your creative juices flowing and showed you the power of PowerApps, PowerAutomate, Teams, and AdaptiveCards. 

 

Leave a Reply

Your email address will not be published.