Introduction
Are you looking for a simple and extensible no-code solution to do approvals in Power Apps? Perhaps you have built automation within PowerAutomate and want to provide a mechanism to “gate” or confirm your process before it executes. 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 custom approvals using PowerAutomate/Flows, Teams, and AdaptiveCards.
But first, let’s explore what Adaptive Cards are.
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 interactivity in the case of approvals. 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 to notify users when they have pending approvals.
I will start by creating a custom approval activity in Power Apps. It can be customized with additional attributes as needed. I will keep this blog post simple.
Further Reading: Dataflows In Power BI
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.
Now, I will initialize some variables that will hold the values to be filled in the adaptive card.
Streamline Approvals with Expert Power Apps and Power Automate Solutions
Our services are tailored to improve your approval workflows and increase productivity. Are you ready to take your approvals to the next level?
Request a DemoNow, I need to set the variables I initialized in the previous step with the From and to values.
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:
Now that I have all my information, I will use Post an Adaptive Card to a Teams user and wait for a response action.
For the message field, build your adaptive card Designer | Adaptive Cards copy card payload and insert it. I am entering the information from my variables.
I’ve used Expense Report as a starting point. Once opened in the designer, 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.
Further Reading: How To Use Power Automate To Enhance Power BI
Lastly, we must update the approval request in Power Apps. Once we get the response back from Teams action, I will use the Update a record action and update the approval activity with the appropriate Activity Status and Status Reason.
Transform your Workflow with Power Apps and Power Automate
Discover how to seamlessly integrate approvals in your Power Apps using Power Automate, Teams, and Adaptive Cards with AlphaBOLD. Our specialized services are designed to enhance your business processes and workflow automation.
Request a DemoHere is how the run looks.
And there you have it. I hope this blog post got your creative juices flowing and showed you the power of Power Apps, Power Automate, Teams, and Adaptive Cards.