DEV Community

Cover image for Making a Power App that makes Flows
david wyatt
david wyatt Subscriber

Posted on

Making a Power App that makes Flows

So I saw an amazing video a while back about how a Power App could create another Power App. It showed how Power Fx could create Yaml code that when pasted in created a Power App component. And I thought, well that's not creating a app, its creating parts of an app, so I wanted to make an app that made a full app. Spoiler alert, I couldn't, with all the abstraction (half of the studio is in a frameset) and the work done in the power apps api not the Dataverse, I had no chance.

So then I thought what is easier and possibly cool... could I create an app that makes a fully working Power Automate flow.

Just like Power Apps can use Yaml, Power Automate uses Json, which although a little more verbose is just as easy to work with. And the big win is it is stored fully in Dataverse.

So this is what I built:

app with flow build

My design was:

simple design

  • I would have a table with all the json code for every action
  • The App would call a flow to grab all the connection references
  • The User builds the flow definition (ClientData) json file
  • App calls flow to create record in the workflows table

Callout, I had hight hopes for lots of actions, I ended up with 6 (and 3 of those were triggers), but its just enough to say its a PoC 😎

If this sounds interesting then keep reading (if it doesn't then you have my envy)

  1. Action Table
  2. Adding/Editing Actions
  3. Connection References
  4. Creating the Flow
  5. What Hurt
  6. Learnings

1. Action Table

The action table is quite simple, it has 4 main fields

  • Name - name of action
  • Json - the json code
  • Trigger - Yes/No it is a trigger
  • Img Url - Url/base64 of the action icon

dataverse table

To get the Json I used my old tool AutoReview, I created a flow with the actions and then used AutoReview to show the Definition file (you could also export it and open up the zip file).

autoreview

Below is what a simple action like Compose looks like:

"Compose":{
  "runAfter":{
    "~runOrder~":[""Succeeded""]
  },
  "metadata"":{
  "operationMetadataId"":""f3d70cf1-be06-4f34-b84a-0befe92d23cb""
  },
  "type":"Compose",
  "inputs":"¬inputs¬"
}"
Enter fullscreen mode Exit fullscreen mode

You can see I swapped out the inputs with variables. The variables where identified by being wrapped in ¬ (e.g. ¬variable¬). This is so the App can identify those variables and highlight them to the user.

The trigger is obvious, the image, well I just spied the maker studio and stole the image source.

I also removed all white space to make the formatting as easy as possible to read.

2. Adding/Editing Actions

Here's the fun bit, building the app to create the flow.

app wire frame

The app has 2 main gallery's, the left lists all the action from the Flow Templates table, when you click the arrow it adds it to the other gallery. What ever item is selected from that second gallery is used to populate a large text input in the centre of the screen. So the idea is you add a action to your flow, select it and then edit the code.

The copy to the flow collection/gallery is below:

If(Substitute(laTrigger.Text,"Trigger: ","")="Yes",
    Set(viOrder,1)
,
    Set(viOrder,CountRows(colDefinition))
);
Collect(colDefinition,{
    Name:ThisItem.Name,
    Json:ThisItem.Json,
    Trigger:Substitute(laTrigger.Text,"Trigger: ",""),
    'Img url':ThisItem.'Img url',
    Order:viOrder 
    }
)
Enter fullscreen mode Exit fullscreen mode

The small complexities were:

The Dataverse Yes/No column, because I stored it in a collection (and I was tired and couldn't be bothered to do it right) it was a pain to read, so I cheated and grabbed it from the gallery label (I removed the header text with Substitute and yes I know a mid would have been better but I was in a lazy mood).

Triggers, I had them in the same list so I needed to force them to the top (order 1). I also had to filter the actions to hide the triggers once one was added to the flow.

If(IsEmpty(Filter(colDefinition,Trigger="Yes")),
    colTemplates
,
    Filter(colTemplates,Trigger='Trigger (Flow Templates)'.No)
)
Enter fullscreen mode Exit fullscreen mode

To update the action when the json was edited was pretty simple. OnChange I added:

If(!toDefinition.Checked,
    Patch(colDefinition,gaDefinition.Selected,
        {Json:Self.Value}
    )
,
    Set(vsDefinition,Self.Value);
);
ClearCollect(colParameters,
    MatchAll(Self.Value,"¬.*?¬")
)
Enter fullscreen mode Exit fullscreen mode

The condition was because the input is also used to edit the entire flow definition

The ClearCollect at the bottom was for the input (¬variable¬) I mentioned earlier (So I have inputs, variables, parameters to describe the same thing, dam am I terrible at naming things).

It uses a regex to find any variables that are still in the json. The collection is then used to populate a horizontal gallery at the bottom of the screen.

3. Connection References

Connection references, this was a bit of a challenge. So I decided to take the easy approach (cough, PoC, cough).

OnStart all of your connection References are grabbed from the connectionreferenes table and returned to the App.

flow get connection references

The json code we need to create is:

    "shared_sharepointonline": {
      "connectionName": "976205de-7b08-ef11-9f89-000d3a315ba7",
      "connectionReferenceLogicalName": "wd_sharedsharepointonline_247f5__US",
      "source": "Invoker",
      "id": "/providers/Microsoft.PowerApps/apis/shared_sharepointonline",
      "displayName": "SharePoint-ConnectionReferenceName",
      "tier": "NotSpecified"
    }
Enter fullscreen mode Exit fullscreen mode

And this is the expression we use within the select (so it adds for every connection reference).

   "@{replace(item()?['connectorid'],'/providers/Microsoft.PowerApps/apis/','')}": {
      "connectionName": "@{item()?['connectionreferenceid']}",
      "connectionReferenceLogicalName": "@{item()?['connectionreferencelogicalname']}",
      "source": "Invoker",
      "id": "@{item()?['connectorid']}",
      "displayName": "@{item()?['connectionreferencedisplayname']}",
      "tier": "NotSpecified"
    }

Enter fullscreen mode Exit fullscreen mode

The first key (shared_sharepointonline) is actually the unique id that the action uses to reference the connection reference. So if you have more then one SharePoint connection you would also see shared_sharepointonline_1, shared_sharepointonline_2 etc. To keep the PoC mojo going I used just first (so no_1), and did the same for the actions.

To add the connection reference I used the 'Copy()' expression. You select the connection reference, it adds it to your clipboard, you then paste it into the full definition file in the connectionreference object.

add connection reference

FYI I also did the same trick for conditions and other containers. You add and select them, then copy a connection from the templates gallery and paste it in the relevant actions object.

4. Creating the Flow

To create the flow we also need the definition json to add the actions. I add that to the same Datavere Flow Templates table, and as its trigger field was null I could filter it out of everything.

{
"properties":{
"connectionReferences":{
},
"definition":{
"$schema":"https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"contentVersion":"1.0.0.0",
"parameters":{
"$authentication":{
"defaultValue":{
},
"type":"SecureObject"
}
},
"triggers":{
~triggers~
},
"actions":{
~actions~
}
}
},
"schemaVersion":"1.0.0.0"
}
Enter fullscreen mode Exit fullscreen mode

There are 2 inputs/variables/parameters here, triggers and actions. I used a slightly different wrapper (~) this time just to keep it identifiable.

Set(vsActions,
    Concat(Filter(colDefinitionUpdated,Trigger="No"),Json,",")
);
Set(vsDefinition,
    Substitute(
        Substitute(
            LookUp('Flow Templates',Name="Definition").Json
        ,
            "~actions~"
        ,
            vsActions
        )
    ,
        "~triggers~"
    ,
        LookUp(colDefinitionUpdated,Trigger="Yes").Json
    )
)
Enter fullscreen mode Exit fullscreen mode

The Concat() expression is perfect here, it concatenates the actions with a ','.
Then a double Substitute (why I why is this not replace, like JavaScript and even Power Automate 😒).

Now we have our definition complete we just need to create a record. I tried to do this directly in Power Apps, but had issues with the 'Primary Entity' field, so I took the easy way (see the pattern there 😎) and got a flow to do it.

The Add a new row code action does not require all of the fields, just the below (I used code snippet to show inputs but you can also see the full code in from the download at the end of the article).

parameters": {
            "entityName": "workflows",
            "item/xaml": "@null",
            "item/category": 5,
            "item/primaryentity": "none",
            "item/name": "@triggerBody()['text_1']",
            "item/type": 1,
            "item/clientdata": "@triggerBody()['text']",
            "item/subprocess": false,
            "item/runas": 0
        },
Enter fullscreen mode Exit fullscreen mode

create flow

The flow also grabs the environment id and the new flows resourceid so that we can return a link to the new flow.

https://make.powerautomate.com/environments/@{workflow()['tags']['environmentName']}/@{outputs('Get_a_row_by_ID')?['body/resourceid']}
Enter fullscreen mode Exit fullscreen mode

And that's it:

5. What Hurt

Well a quick one here, and this one on me. I thought the Copy() to clipboard was broken. After much wasted investigation I found out it doesn't work in the studio, but when published all good 😒

RunAfter was probably the biggest challenge. The flow does not run in order of the json, but it goes backward and uses the runAfter value to create the order. On top of this the first in a container doesn't have a runAfter, but uses the order. i.e. if in a condition then the first action does not runAfter the condition, its blank and this time follows the json order, confusing I know 😕

runafter

So I need to loop over every action and replace the runAfter with the next item in the collection, but with these conditions:

  • Not if manually edited by the user
  • Not if first in container

My approach was 2 fold, I used the variable substitute approach, so if edited it would leave it. For the first in the container I did a check for if it was the second item (i.e. first after the trigger) and substituted the variable with "". As for other containers like condition/loops, I left that to the user to fix (cough, PoC, cough)😎. If I was to fix it I would do one of 2 approaches:

  • If action contained "actions", split at it, replace first runAfter variable with "" and then put back together.
  • Pass it to a flow which passed it to a Office Script that did similar thing but with the power of TypeScript to make it easier.

The code to do the loop ended up like this:

ClearCollect(colDefinitionUpdated,Filter(colDefinition,Trigger<>""));
ForAll(colDefinitionUpdated,
    If(ThisRecord.Order>2,
        Patch(colDefinitionUpdated,ThisRecord,
            {
            Json:Substitute(ThisRecord.Json,
                    "~runOrder~"
                ,
                    Index(colDefinition,ThisRecord.Order-1).Name
                )
            }
        )                               
    ,
        If(ThisRecord.Order=2,         
            Patch(colDefinitionUpdated,ThisRecord,
                {
                Json:Substitute(ThisRecord.Json,
                        vsRunAfter
                    ,
                        ""
                    )
                }
            )            
        ) 
    )    
);   
Enter fullscreen mode Exit fullscreen mode

with vsActions being:

vsRunAfter= Char(34)&"~runOrder~"&Char(34)&":["&Char(34)&"Succeeded"&Char(34)&"]";
//"~runOrder~":["Succeeded"]
Enter fullscreen mode Exit fullscreen mode

I tried with clever regex's, but as with every experience I have had with regex's, it didn't work after 10th attempt, I hated them a little more, and then gave up.

Another issue is the resourceid for the link. After the run is created there appears to be no resourceid, so the link opens to the environment not the flow. I suspect a delay or patch will allow it to be generated.

I also found one more issue that at time of print I couldn't fix. Some connection references would not allow me to turn the flow on until I went into make.powerautomate.com and saved the flow (no changes). It was showing I didn't have access to the connection in the connection reference, but it was mine. I'm guessing that its either because I went with a simple connection reference schema (the new one is a lot more complex), or the UI does something clever and shares connection (thought not had this issue before when created flow through the Dataverse API).

error

6. Learnings

So am I going to finish this, hell no. It's a silly idea and would never be as good as the proper ui, but that wasn't the point. The point was to learn, and I learned more about the flow json and how the api works. I also tested the Power Apps modern controls, yep still not there yet for proper uses.

I also had some ideas that could become something, my first was to avoid the actions and target templates. I could create templates and then get a flow/any workflow to spin them up when required. This could be cool for SharePoint sites that have on folder triggers etc.

The second idea was to use it as purely an editor, pull in already built flows and edit them in code. This would be useful for quick parameter changers that could be a pain in the ui. I'm going to spin up a model driven app for my next project to see how useful I could make it.


If you want to look more at the code (or crazily want to build out the project) as always the solution is here, I also included the 6 actions I made for you to import into the Dataverse table.

Top comments (2)

Collapse
 
mcombp profile image
Matthew Collinge

Epic!! What a great insight on what can be done.
Are you that frustrated with the new Flow Designer, that you decided to build your own? 😁

Collapse
 
balagmadhu profile image
Bala Madhusoodhanan

awesome