DEV Community

Cover image for Conversational Approval process using Adaptive Cards and JSON objects
Matthew Collinge
Matthew Collinge

Posted on

Conversational Approval process using Adaptive Cards and JSON objects

Introduction

This is Part 3 of a series of posts where I try to explain how to send Form answers from a Power App into Power Automate and then send Adaptive Cards in Microsoft Teams to allow the Requestor and Approver make incremental changes to the Form answers.

Part 1

Part 2

We're going to start Part 3 by building a second Flow. In this Form Approval Process this Flow will actually be the one that sends an Adaptive Card to our Approver.

Here is an overview of what we're going to do here.

Solution Process Flow

Why don't I use the standard Approvals Actions?

What I'm trying to achieve is a more conversational process for the Approval, where the Requestor and Approver can both edit the data inside Message Cards in Teams until the Approver decides what to do with it.. We'll do this by passing our Object again, plus a few other useful Expressions.

We're going to trigger this from our "[proxy] - Object Flow" we made in an earlier Post: Passing Objects from Power Apps to Power Automate.

The Approver's Flow

As this will be a Child Flow. We'll need a Manual trigger and I'll call this Flow "[child] - Approver Card". We just need one text Input. varObject as this can contain everything we need.

Then, we'll Initialize a Variable that will convert the Text Input to JSON.

json(triggerBody()['text'])
Enter fullscreen mode Exit fullscreen mode

Power Automate - Manual Trigger

Adaptive Card

Let's add another action - Post adaptive card and wait for a response. We'll need to construct an Adaptive card for this. Again I would recommend you build your full Adaptive Card by previewing it with the Help of The Adaptive Card Designer or the VSCode Microsoft Adaptive Card Previewer

Power Automate - Post adaptive card and wait for a response

Text Inputs
I will start with the Text value we're getting for the "Name" field in the Form.

  • We need a type, this will be Input.Text
  • We can specify the Label above the field as "Name"
  • For the Value, we can reference the property inside of varObject by inserting "@{variables('varObject')?['Name']}"
  • Finally, each field in the Adaptive Card needs an id, which we can keep exactly the same as the Object Property name to make it easier to reference in the future.

In my scenario, we already have an input value, so 'Placeholder Text' isn't really needed.

    {
      "type": "Input.Text",
      "label": "Name",
      "value": "@{variables('varObject')?['Name']}",
      "id": "Name"
    },
Enter fullscreen mode Exit fullscreen mode

You can use the example above to build any other Text Inputs.

Toggle
The 'Mortgaged' field is a Boolean field in our Power Apps Form, so we can replicate this with Input.Toggle, similar to the Text Input we need to give an id, label and value.

    {
      "type": "Input.Toggle",
      "title": "Mortgaged",
      "id": "Mortgaged",
      "label": "Mortgaged",
      "value": "@{variables('varObject')?['Mortgaged']}"
    },
Enter fullscreen mode Exit fullscreen mode

Choices
I have previously described how we can have a Choices Dropdown in this Blog Post:
Passing a Choices List from Power Apps to an Adaptive Card - in this example, we can add a Label: "Country"

    {
    "type": "Input.ChoiceSet",
    "label": "Country",
    "choices": @{variables('varObject')?['Countries']},
    "id": "Country",
    "value": "@{variables('varObject')?['Country']}"
    }
Enter fullscreen mode Exit fullscreen mode

For the Rating Field, we know there's only five options, so we might as well enter these manually, giving us this:

{
      "type": "Input.ChoiceSet",
      "choices": [
        {
          "title": "1",
          "value": "1"
        },
        {
          "title": "2",
          "value": "2"
        },
        {
          "title": "3",
          "value": "3"
        },
        {
          "title": "4",
          "value": "4"
        },
                {
          "title": "5",
          "value": "5"
        }
      ],
      "label": "Rating",
      "id": "Rating",
      "value": "@{variables('varObject')?['Rating']}"
    }
Enter fullscreen mode Exit fullscreen mode

Actions
That's the fields done, now we need some actions to respond with. These can be placed at the foot of the Adaptive Card like this and will be where the Approver makes their decision.

  "actions": [
        {
            "type": "Action.Submit",
            "title": "Approve",
            "style": "positive",
            "id": "approve"
        },
        {
            "type": "Action.Submit",
            "title": "Respond",
            "id": "respond"
        },
        {
            "type": "Action.Submit",
            "title": "Reject",
            "style": "destructive",
            "id": "reject"
        },
        {
        "type": "Action.ShowCard",
        "title": "ReAssign",
        "card": {
            "type": "AdaptiveCard",
            "body": [
                {
                "type": "Input.Text",
                "label": "ReAssign to",
                "style": "Email",
                "id": "ReAssign_To",
                "isRequired": true,
                "errorMessage": "Please enter an email address"
                }
            ],
            "actions": [
                {
                "type": "Action.Submit",
                "title": "ReAssign"
                }
            ]
        },
        "id": "acAssignCard"
        }
    ]
Enter fullscreen mode Exit fullscreen mode

Feel free to elaborate on the options you provide, such as comments, or even an option to reassign. We'll use these soon in a Switch Action to decide on the next step. IF you want to use this you'll see that there's an expandable section (https://adaptivecards.io/explorer/Action.ShowCard.html)[Action.ShowCard] when ReAssign is selected, this is to ensure an email address is entered.
Adaptive Card - Approver Actions

The JSON for the Approval Adaptive Card is here.

Union

Now we have an Adaptive card that we can send to an Approver and Wait for a response.

We can now capture any changes that the Approver may have made back into our original varObject.

Start by adding a Compose Action, and in the Compose Action we can enter this Expression

union(variables('varObject'),outputs('Post_adaptive_card_and_wait_for_a_response').body.data)
Enter fullscreen mode Exit fullscreen mode

Compose - Union Body Data with varObject
What the Union expression does is combine the properties from the varObject with the data obtained from the response of the action Post_adaptive_card_and_wait_for_a_response. This is done to update or synchronize the values in the varObject with the field values changed in the Adaptive Card.

Then we just need to feed the output of our Compose expression into Set the varObject.
Set variable - varObject

Switch

We'll add a switch Condition to allow us to decide the path of the Flow from this point using some Switch Cases, which match the the ID of our Actions.

outputs('Post_adaptive_card_and_wait_for_a_response')?['body/submitActionId']
Enter fullscreen mode Exit fullscreen mode

We have the options: approve, respond, reject, ReAssign_To and we can decide what we we want to happen with each of these paths. I'll come to respond in a moment, but I will first give some examples of how you can use the other Switch Cases.

Power Automate - Switch

Approve
This would be where you would define what you want to happen if the Adaptive Card is approved; Send an Email, update a SharePoint List Item etc...

Reject
Here you could send Post a Standard Adaptive card or send an email to the Requestor.

ReAssign
A good example for using the setProperty() expression would be if you had a defined Approver_Email in your varObject. You could use a Compose Action with:

setProperty(variables('varObject'), 'Approver_Email', outputs('Approver_adaptive_card')?['body/data/ReAssign_To'])
Enter fullscreen mode Exit fullscreen mode

Then, Set Variable - varObject again with the output of the Compose Action.

Power Automate - ReAssign

Respond
So for the Respond Case, we're going to create another Child Flow, it will be a copy of this Flow where the Requestor can make changes to re-submit to the Approver.

Respond to a PowerApp or flow

To make sure this Child Card let's the Parent Flow it has run, place in a Respond to a PowerApp or flow as the last Action.

[proxy] - Object Flow

Before we move on to the Requestor Card, we need to trigger this Child Flow from the [proxy] - Object Flow by making this the 3rd action in the Flow.
Object Flow - Run a Child Flow - [child] - Approver Card.png

Requestor Card

Now - back to the [child] - Approver Card] Flow, so we can copy it....
Copy of - [child] - Approver Card

We'll call this: [child] - Requestor Card and add it to the same solution as our App and other Flows.

Let's first make a couple of small adjustments to the Post adaptive card and wait for a response Message in this one by changing the first TextBlock with text Property from:
Image description

To:

    {
      "type": "TextBlock",
      "size": "Medium",
      "weight": "Bolder",
      "text": "Requestor Request"
    },
Enter fullscreen mode Exit fullscreen mode

Then we just need to swap out the "actions": [] section at the bottom of the Card. The Requestor won't have the same options as the Approver, so we can swap out the whole section for this:

  "actions": [
        {   
        "type": "Action.Submit",
        "title": "Re-Submit 🔁",
        "id": "acSubmit",
        "style": "positive"
      },
      {
        "type": "Action.Submit",
        "title": "Cancel Request ❌",
        "id": "acCancelRequest",
        "style": "destructive"
      }
    ]
Enter fullscreen mode Exit fullscreen mode

And of course, change the Receipient, so that the message goes to our Receipient. The Union action is still here to update any changes which then update our varObject.

Switch

As we've changed the Actions, we'll now need to change the cases to "acSubmit" and "acCancelRequest".

Power Automate - Requestor Switch

acCancelRequest
Again, I'll leave it up to you what to do with this path; you may want to imform the Approver with another standard Adaptive Card message.

acSubmit
So here's the key feature that allows us to perform this conversation loop between the Approver and Requestor. - We're now going to re-trigger [child] - Approver Card and with that, pass back the varObject as a String again.

Create a 'Run a Child Flow' action and select our [child] - Approver Card Flow we created earlier. For the varObject Text Input enter: string(variables('varObject')) - this converts the varObject into a String again to pass to Approver Flow.

Child - Approver Card

Respond to a PowerApp or flow

Again, to make sure this Child Card let's the Parent Flow it has run, place in a Respond to a PowerApp or flow as the last Action.

JSON for the Requestor Adaptive Card is here

Back to [child] - Approver Card to finish off

All we need to do now is the mirror opposite of this in the [child] - Approver Card Flow. Go back to that flow and underneath the Case - respond create a 'Run a Child Flow' action and select our [child] - Requestor Card

Run a Child Flow - [child] - Requestor Card

Testing and fine tuning

To prevent any confusing repeat runs of these Flows, I would recommend changing the Retry Policy
on each Run a Child Flow Action to None

Retry Policy - None

So, here are some examples that we should see in Microsoft Teams.

Power App Form

Power App Form

Approver's 1st Message

Approver's 1st Message

Requestor's 1st Message

Requestor's 1st Message

Approver's 2nd Message

Approver's 2nd Message

Conclusion

Hopefully, that was a helpful guide. I have one final Post to add a little bit extra to this type of Approval process. Please leave any questions or comments below.

Top comments (1)

Collapse
 
wyattdave profile image
david wyatt

Very cool 😎