Dynamic Approval Workflow in Teams using Cards

Published by Storm A on

One of the main use-cases that has always been a great candidate for the power platform is approval workflows. Using the ‘Approvals’ connector in Power Automate is usually the first method to use for this which has a handy Outlook integration that provides the user an interactive email that handles the response back to Flow. This is fine and dandy for simple processes, but if someone doesn’t respond immediately the approval can quickly get buried in your inbox. Thankfully Teams has a feature that allows users to send these types of dynamic messages and is much more flexible when it comes to layout and design: Adaptive Cards.

Cards were introduced to Teams at the beginning of this year and has continued to be improved upon. Building cards involves manipulating JSON which can be a little tricky to get used to but thankfully Microsoft has an adaptive card designer tool which makes it a lot easier to design and build cards. To better understand how we can use this we’ll be using the designer to create a department approval message that get’s sent to a department head to review.

<<Insert photo of the message example>>

Department Approval Flow in Teams

To setup the Proof-Of-Concept (POC) that we’ll be developing, I’ve created a simple process diagram to outline the different steps of our process.

General Approval Flow using SharePoint list as the request form.

I. Department List

This SharePoint list acts as a reference list to track the department names and the head of the department (single select person field). This association could be queried from a wide range of sources (Azure AD job titles, ERP / HCM systems, etc) but for simplicity-sake we will manage these in a secured SharePoint site that is only accessible to the admin(s).

Make sure Department Head is a single-select person field

II. Dept. Request List

This list is what captures the submission from the end user and acts as the trigger for our Power Automate:

Title

  1. Dept (lookup column to “Department List” > Title)
    you will need to create this from the list settings > Create column tool.
  2. Request Details (multi-line of text)
  3. Due Date (date only)
  4. Status (choice):
    1. Pending Review – default
    2. Approved
    3. Rejected
    4. Complete
  5. Reviewed Date (date only)
  6. Reviewer (single select person field)
  7. Reviewer Comment (multi-line of text)
Dept Request List

III. New Automated Cloud Flow

(I’m sure I’ll have to update the name of this soon given Microsoft’s name changing addiction) This will be the Flow / Power Automate / Cloud Flow that maintains our logic described above and send the teams message(s). The steps below will describe how to build this flow in detail.

Step 1 – Design the Card

Before we create the flow, let’s design the card using the designer to have that ready first. Navigate to https://adaptivecards.io/designer/ to open the designer and change the “host app” to Microsoft Teams – Light / Dark. With the updated card example, we can start developing.

Populate your Sample Data

Creating a sample record that reflects your data makes it easier to see what the card would actually look like. It also makes it easier to map once we incorporate it into the flow. If you are following the list schema examples I provided, then you can use the following sample JSON:

{
    "Title":"Fix Daily Reports Workflow for VP",
    "Department":"IT Services",
    "RequestDetails":"The reports workflow that runs every day is reporting an error and not sending the reports as expected.",
    "DueDate":"12/26/2020",
    "CreatedBy": {
        "DisplayName": "Storm Anderson",
        "Email": "Storm@kumopartners.com",
        "Image": ""
    }
}

Reset the Card Payload Editor

I recommend starting from scratch (though you can certainly reverse engineer the other templates they provide) by resetting the Card Payload Editor to the following JSON.

// CARD PAYLOAD EDITOR JSON
{
    "type": "AdaptiveCard",
    "body": [
        
    ],
    "actions": [
        
    ],
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.2"
}

Add the Necessary fields

Now that we have the editor setup, let’s start to add elements to the card. You can drag and drop from the left hand toolbar onto the card to populate it automatically. Once populated, you can manipulate the properties through the right-hand toolbar. If you setup the sample data, you will then be able to add the mappings as well.

ElementProperties
Text: “New Department Request Initiated”
Size: Large
Once added – an icon appears in the bottom right which allows you to create more columns – create two for now.
Style: Default
Column 1 – Text: “Department”
Weight: Bolder
Column 2 – Text: ${Department}
Color: Accent
two columns again
Style: Default
Column 1 – Text: “Due Date”
Weight: Bolder
Column 2 – Text: ${DueDate}
Color: Accent
Text: ${Title}
Separator: Checked
Weight: Bolder
Text: ${RequestDetails}
ID: Comment – this is important to set as we need to reference it in the Flow.
Placeholder: “Provide comments regarding your review.”
Multi-line: Checked

With all the card elements added, we just need to add the two final things, the approve and reject buttons. These can be added by selecting the adaptiveCard at the top of the structure, which will then display the “Add an action” button. Add two “Action.Submit” actions.

Change the first one to:

Then the second as well:

Click preview mode and you should now have something that looks like the following:

You can now Copy the card payload into the Flow when we come to that step next.

Step 2 – Building The Flow

Now that we have the lists in SharePoint built, we can start to build the flow.

1. Trigger

This will point to the Dept request list we created in sharepoint.

2. Lookup Department Record

The first action we need to do is lookup the related department that was selected from the request form. We can do this by using a simple ODATA filter query to lookup the record by ID. We can then set the Top Count to be 1 given we should only expect one record.

3. Parse Department Head Record

Parse JSON is great for standardizing and referencing record data that you use in your flow. What we do here is grab the first record of the ‘Get Items’ action and then point to the ‘DepartmentHead’ field which is the person field in SharePoint. The schema used only points to the fields we need / want (DisplayName, Email, JobTitle) to reduce clutter.

  • Content: first(outputs('Get_items')?['body/value'])?['DepartmentHead']
  • Schema:
{
    "type": "object",
    "properties": {
        "DisplayName": {
            "type": "string"
        },
        "Email": {
            "type": "string"
        },
        "JobTitle": {
            "type": "string"
        }
    }
}

4. Send the Adaptive Card

Let’s copy the card JSON (payload) we generated earlier and paste it into the message property of the action. When we copy it over, the bindings we used in the editor will need to be replaced with the flow bound mappings. This is pretty strait-forward to do as you just look for any old mapping, and update it using a dynamic value (or expression).

Old Value:

New Value:

If you have been following along, you can either replace the values with your own mappings or use the JSON below which matches the list schema used above. Thankfully Power Automate allows us to copy / paste dynamic references by using the @{…} notation around our expressions.

{
    "type": "AdaptiveCard",
    "body": [
        {
            "type": "TextBlock",
            "text": "New Department Request Initiated",
            "wrap": true,
            "size": "Large"
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "Department",
                            "wrap": true,
                            "weight": "Bolder"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "@{triggerOutputs()?['body/Dept/Value']}",
                            "wrap": true,
                            "color": "Accent"
                        }
                    ]
                }
            ],
            "style": "default"
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "Due Date",
                            "wrap": true,
                            "weight": "Bolder"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "@{triggerOutputs()?['body/DueDate']}",
                            "wrap": true,
                            "color": "Accent"
                        }
                    ]
                }
            ],
            "style": "default"
        },
        {
            "type": "TextBlock",
            "text": "@{triggerOutputs()?['body/Title']}",
            "wrap": true,
            "separator": true,
            "weight": "Bolder"
        },
        {
            "type": "TextBlock",
            "text": "@{triggerOutputs()?['body/RequestDetails']}",
            "wrap": true,
            "spacing": "None"
        },
        {
            "type": "Input.Text",
            "placeholder": "Provide comments regarding your review.",
            "isMultiline": true,
            "id": "comment"
        }
    ],
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.2",
    "actions": [
        {
            "type": "Action.Submit",
            "title": "Approve",
            "style": "positive",
            "id": "Approve"
        },
        {
            "type": "Action.Submit",
            "title": "Reject",
            "style": "destructive",
            "id": "Reject"
        }
    ]
} 

Unfortunately the Teams card does not automatically determine the fields we created in our card. Therefore we’ll have to use expressions to determine the user’s response. Let’s re-name the action to something simpler such as “Approval Request” to make it easier to reference.

Update Existing Request

Now that the user has responded, before we send the response back to the requester let’s update the existing request item:

  • Status: if(equals(body('Approval_Request')?['submitActionId'],'Approve','Approved','Rejected'))
  • Reviewer Comment: body('Approval_Request')?['data']?['comment']

Handle Approval Outcome

With the updated name for the Teams action, we can use the following expression to determine what the approver selected: body('Approval_Request')?['submitActionId']. Now we can send a message back to the user if rejected, or expand upon the approval flow to incorporate more logic / process as needed.

Now if we create a new item on the list, we should see a message come through to Teams as expected.

Hope you liked this guide and stay tuned for more. Let us know if you have any feedback or anything that should be clarified.


0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *