Trigger Power Automate flows from Power Apps Portals

Currently the only way to update records from a Power Apps Portals is to create or update data via an Entity Form, Web Form or a companion app. The companion app incurs a certain level of technical debt in terms of development of the app and management of where its hosted, such as an Azure web app or Azure function.

Power Automate to the Rescue!

The following post outlines the steps I took to trigger a Power Automate flow from a portal web page that will update a record in CDS (common data service) without needing to submit a form or navigating away from the page. Power Automate effectively acts as the Power Apps Portals “companion app”.

Note: Unless you have been living under a rock, Power Automate is the new official name for Microsoft Flow.

Note 2: These steps would work with Power Apps Portals running on Dynamics 365 instances (which, as you know, run on CDS). If I say “CDS”, Dynamics 365 could also apply.

Power Apps Portals Editable Grid

I wanted to build a simple editable grid on a Power Apps Portals page where a portal user could update a series of records without needing to open an entity form or a web form step for each record.

I started to build a custom web template. (Sample code can be found on my Github Site.)

My example will list a series of courses and allow the portal user to update the description for each course without submitting a form. It might not be the most relevant use case, but hopefully will illustrate the process.

The first step was to retrieve the course data which I would want to edit. For this I use the Liquid FetchXML tag to retrieve the data, and of course I composed the FetchXML statement using the FetchXML Builder.

{% fetchxml courses %}

<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct='true' returntotalrecordcount='true'>
    <entity name="avngr_course" >
        <attribute name="avngr_coursedescription" />
        <attribute name="avngr_coursename" />
        <attribute name="avngr_courselevel" />
        <attribute name="avngr_cost" />
        <attribute name="avngr_courseid" />
    </entity>
</fetch>

{% endfetchxml %}

In the web template, if data is retrieved, I want to display the data in a fairly simple list. As a bonus, since the text could be quite lengthy, I decided to hide the description in an “accordion style” so the user could open and collapse each record. This part is completely optional.

<!-- assuming data is retrieved -->
{% if courses.results.total_record_count > 0 %}

    <!-- for each course retrieve, show the name as the accordion section and description in text area to be edited -->
    <!-- use the GUID as the id for the textarea control to retrieve later -->
    <!-- pass the GUID to the JavaScript function to update record -->
    {% for course in courses.results.entities %}

        <button class="accordion">{{course.avngr_coursename}}</button>
        <div class="panel">
            <textarea id={{course.Id}} rows="4" cols="50">{{course.avngr_coursedescription}}</textarea>
            <button onclick="updaterecord('{{course.Id}}')">Update</button>
        </div>

    {% endfor %}

{% else %}

    <h2>No records found.</h2>

{% endif %}

If you look at the code, I set the description value in a HTML textarea element, so it can be edited on a portal page.

I set the textarea HTML tag id to the GUID of the record from CDS, this gives each textarea control a unique id (which will need later).

The course name, I just set as the accordion button name.

Within the panel, I also added a button item, that will pass the GUID to a JavaScript function that I will create as part of the web template.

The result looks like the following on a Power Apps Portal page:

The problem now is how to pass the user edits to update the corresponding CDS/Dynamics 365 record. Traditionally, we could write a “companion” web application that we could call from JavaScript and pass along the updates.

I decided to try to use Power Automate.

Power Automate provides a HTTPS Request trigger that we can call from a Power Apps Portal (or other places).

Power Automate HTTP Request Trigger

This trigger requires a JSON (JavaScript Object Notation) string in order to be configured and it will accept data in a JSON string. We can begin to build out our web template further to get a sample JSON string.

We will start to build our JSON string in the JavaScript function that we will call from our “Update” button that we place beside each record.

I add JavaScript code in my web template records between <script> tags to keep everything together. See the sample code for the full example.

Since we used the actual record GUID as the textarea id and passed the id to the function, we have what we need to start building our JSON string.

NOTE: If there are more than one field to edit, we could concatenate addition id information to each HTML element to remain unique, e.g. “id=textarea1{{course.Id}}”

var courseupdate = '{ "course" : [' +
        '{ "courseid": "' + id +'", "coursedetail": "' + document.getElementById(id).value.trim() + '"} ]}';

The will produce a JSON string similar to the following (you can add an “alert” function to get your sample):

{ "course" : [{ "courseid": "42e86bd1-e704-ea11-a811-000d3af962ef", "coursedetail": "This is an introductory course for AI Builder"} ]}

Once you have your sample, you can use that as a pattern for the Power Automate Http Request trigger by clicking the “Generate from sample” button;

Generate JSON Schema from sample
Adding JSON schema

The next step is to take the data collected from the HTTP request trigger and update CDS. In Power Automate, I used the Common Data Service (current environment) Update a record connector to update the Course record in the CDS environment.

Update a record (CDS current environment)

We have the “course id” and “course description” from our HTTP request trigger in our Power Automate flow. We can use these values to directly update a record in CDS/Dynamics 365.

Update the record with values from the HTTP Request

Once we save the flow, it will create the HTTP POST URL that we will use to call from our Power Apps Portal page.

Copying the HTTP POST URL

You could use a tool like Postman to test the Power Automate flow to ensure that the JSON string will update a CDS/Dynamics 365 record.

The final step to get the process functional is the add the code to the JavaScript function in the portal web template to submit the JSON string to Power Automate to update the record in CDS/Dynamics 365. This will use the XMLHttpRequest function.

//Update record by calling HTTP request Power Automate flow
function updaterecord(id) {
            
        //build JSON string using values from display
        var courseupdate = '{ "course" : [' +
        '{ "courseid": "' + id +'", "coursedetail": "' + document.getElementById(id).value.trim() + '"} ]}';

        //use the alert to get JSON example for flow
        //alert(courseupdate);

        //build http request string using URL from Power Automate flow
        var req = new XMLHttpRequest();
        var url = "https://<<copy HTTP POST URL from Power Automate here>>";

        //send https request to Power Automate
        req.open("POST", url, true);
        req.setRequestHeader('Content-Type', 'application/json');
        req.send(courseupdate);

        alert("updated");

    }

Navigating to our Power Apps Portal page, we can edit some of the text displayed on the record, and then click “Update”.

Update data directly on portal page

If we look at the data in CDS (in this case, from a Model Driven App) we see that the data was updated.

Data updated in CDS Model Driven App

Security

Security concerns

If someone were malicious, they could locate the HTTP POST URL from the browser session and build an app (or use a tool like Postman) to update data directly to your CDS without using the portal (where the pages could be protected by authentication).

Hitting F12 and taking a peek at the data being transmitted

There are likely better ways to protect the function, but a simple way is to add a condition area in your Power Automate flow to check the origin of the Http request. (Using the expression triggerOutputs()[‘headers’][‘origin’] to get origin value from header of HTTP request).

Did request come from a Power Apps Portal?

The process can be set to check the origin of the request, if it doesn’t come from the Power Apps Portals URL, then terminate the request.

The final flow for this experiment looks like the following:

Full Flow

The sample code on Github is the complete Web Template. In the Portal Management app, create a new Web Template and simply copy and fix up the code for your situation. Then create a corresponding Page Template record that points to the Web Template.

Creating Web Template

The Page Template will now be available for use in the Portal design studio. Also note that the user (or anonymous web role) will need appropriate Entity Permissions configured to actually view the data.

The sample code also contains the CSS and JavaScript for the “accordion” styling. This code was pretty much lifted verbatim from https://www.w3schools.com

Gotchas

If the Portal is high transaction, high volume, then it could potentially eat up flow allocations quickly. I could see the use cases working well for very low volume portals.

Even with checking the origin, there are other potential security concerns that would need to be addressed with this method.

In order to see the updates reflected (accurately) on the portal, a user would need to navigate away or refresh the web page where they are viewing the edited data. The CDS records should be configured with change tracking enabled to ensure the most up to date data is visible on the portal.

At this point, the code does not have a listener enabled to determine of the flow was successful or not updating the data in CDS.

Another method would be to have a single update button that would build a JSON string from all the updates. However, this could exceed limits of how much data can be passed to the HTTP request.

Summary

This example showed an simple example of how Power Automate could be used with a Power Apps Portal. There are other applications beyond creating or updating data where triggering a flow could be beneficial. Power Apps Portals do have the ability to run “classic” Dynamics 365 workflows from entity forms and entity lists. The sample provided here provides an option to run processes via Power Automate.

Cover Photo by Gordon Williams on Unsplash

Security Photo by Scott Webb on Unsplash

Nick Doelman is a Business Application MVP, a Microsoft Certified Trainer and the host of Refresh the Cache: the Power Apps Portals Podcast. Nick will be delivering the Power Apps Portals in a Day class at Scottish Summit 2020. Follow Nick on twitter @readyxrm

Appendix A – Full Web Template Code

{% extends 'Layout 1 Column' %}
{% block main %}

<!--Web Template to display a list of records, allow user to update course detail and update via Power Automate Http request-->
<!--Nick Doelman-->
<!--December 27, 2019-->

<!-- CSS style for accordion panel -->
<!-- code source: https://www.w3schools.com/ -->

<style>
/* Style the buttons that are used to open and close the accordion panel */
.accordion {
    background-color: #eee;
    color: #444;
    cursor: pointer;
    padding: 18px;
    width: 100%;
    text-align: left;
    border: none;
    outline: none;
    transition: 0.4s;
}

/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
.active, .accordion:hover {
    background-color: #ccc;
}

/* Style the accordion panel. Note: hidden by default */
.panel {
    padding: 0 18px;
    background-color: white;
    display: none;
    overflow: hidden;
}

</style>
{% fetchxml courses %}

<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct='true' returntotalrecordcount='true'>
    <entity name="avngr_course" >
        <attribute name="avngr_coursedescription" />
        <attribute name="avngr_coursename" />
        <attribute name="avngr_courselevel" />
        <attribute name="avngr_cost" />
        <attribute name="avngr_courseid" />
    </entity>
</fetch>

{% endfetchxml %}

<!-- assuming data is retrieved -->
{% if courses.results.total_record_count > 0 %}

    <!-- for each course retrieve, show the name as the accordion section and description in text area to be edited -->
    <!-- use the GUID as the id for the textarea control to retrieve later -->
    <!-- pass the GUID to the JavaScript function to update record -->
    {% for course in courses.results.entities %}

        <button class="accordion">{{course.avngr_coursename}}</button>
        <div class="panel">
            <textarea id={{course.Id}} rows="4" cols="50">{{course.avngr_coursedescription}}</textarea>
            <button onclick="updaterecord('{{course.Id}}')">Update</button>
        </div>

    {% endfor %}

{% else %}

    <h2>No records found.</h2>

{% endif %}

<script>

    //Accordion display as per https://www.w3schools.com/
    var acc = document.getElementsByClassName("accordion");
    var i;

    for (i = 0; i < acc.length; i++) {
        acc[i].addEventListener("click", function() {
        /* Toggle between adding and removing the "active" class, to highlight the button that controls the panel */
        this.classList.toggle("active");

        /* Toggle between hiding and showing the active panel */
        var panel = this.nextElementSibling;
        if (panel.style.display === "block") {
            panel.style.display = "none";
        } 
        else {
            panel.style.display = "block";
            }
        });
    }

    //Update record by calling HTTP request Power Automate flow
     function updaterecord(id) {
        //alert("Update record: " + id);
        //alert("Update text: " + document.getElementById(id).value.trim());

        //build JSON string using values from display
        var courseupdate = '{ "course" : [' +
        '{ "courseid": "' + id +'", "coursedetail": "' + document.getElementById(id).value.trim() + '"} ]}';

        //alert(courseupdate);

        //build http request string using URL from Power Automate flow
        var req = new XMLHttpRequest();
        var url = "<<HTTP POST URL HERE>>";

        //send https request to Power Automate
        req.open("POST", url, true);
        req.setRequestHeader('Content-Type', 'application/json');
        req.send(courseupdate);

        alert("updated");

    }



</script>
{% endblock %}

8 thoughts on “Trigger Power Automate flows from Power Apps Portals

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s