Using Mermaid JS to generate a diagram from Power Automate
Matthew Collinge
Posted on February 20, 2024
Introduction
This is the "bonus part" of my blog series on
creating a Conversational Approval Process using Power Automate and Microsoft Teams.
I needed a way to produce an easy to read depiction of the steps of a process to users or even a way to debug what happened. After seeing Jon Russell and Mike Gowland's amazing demo of
JustAskIt on a Microsoft 365 & Power Platform Development community call I have been keen to try out Mermaid JS in Power Automate.
What is Mermaid JS?
Mermaid JS is a tool that lets you create diagrams and charts such as flowcharts, sequence diagrams, Gantt charts using simple text commands. It works by converting your text commands into a graphical representation that you can customize and share.
https://mermaid.live/
Example
Using Mermaid JS we easily can convert Markdown like this:
flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
into an image like this that lists the steps in an easy to read graphical format:
Building the Flow
Manual Trigger
We start off our Flow with the manual trigger. The first text input is varSteps, this is the steps that we are going to either create or append to our existing Flowchart.
The other text input is the Flowchart, so this is where we could insert an existing Flowchart to our Power Automate Flow.
Alphabets Variable
The first variable to initialise is Alphabets, here we will have a string value of the 26 character alphabet. We will use this to pick out the next alphabetical character if we need to append to an existing Flowchart.
Flowchart Variable
The next variable to initialise is Flowchart, which is a string variable that will capture the Flowchart text input from the manual trigger.
varSteps Variable
Then the next variable to initialise is the varSteps, which is an object variable. It will capture the text input for varSteps from the manual input, but we will wrap this inside JSON() so that it is converted to JSON from a string value.
Condition
We will start the actions with a Condition action. The condition will check whether variable('Flowchart') is equal to Null. If it is Null, then we'll create on from scratch. If it's not, then we can append to it with further Steps.
Creating a new FlowChart
I will first go through the steps that are required in the Yes or True side. In here we are just creating a brand new Flowchart that is created in the format ready for Mermaid JS. Set the variable FlowChart and in the value box we will build our markdown so that it can be read inside the Mermaid JS. We will reference the Object properties from the varSteps to build that.
- Define the type using "flowchart TD"
- Starting with “A” we will enter variables('varSteps')?['Trigger'] .
- Then for “B” we will add the variables('varSteps')?['Condition']
- Then ”B-->” for variables('varSteps')?['Outcome']
- Lastly we will insert the “C” variables('varSteps')?['Action'].
That should give us all we need to create a new Flowchart.
Appending to an Existing Flowchart
On the No / False side of the Condition Action we will be appending to an existing Flowchart. This is useful if there are multiple child flows that make up part of the full process that you are trying to build into a Flowchart. In my Conversational Approval process, there may only be one run of the Approver's Adaptive card, or there may be a need to the Requestor to respond to an Adaptive Card also. Having the Append option allows us to keep building on top of the initial FlowChart that gets created.
Compose - Last Step
We will start by extracting the alphabetical character for the last step that exists in the Flowchart. For example; if the existing Flowchart is:
flowchart TD
A[RM Request] --> B{Matthew Collinge - receives Approval Card}
B -->|Pending| C[Send back to Matthew Collinge]
Then we want the letter **“C”**
Using a Compose action we can use.
sub(lastIndexOf(variables('flowchart'), '['), 1), 1)
We will need to use this value to increment to the next letter in the alphabet.
Compose Letter No
We now get the next letter's number by using the next compose action. Referencing the alphabet variable we get the next number (D would be 4).
outputs('Compose_-_Last_Step'),1)
Compose Approval
The next compose step is actually composing the approval Markdown text, starting with the original Flowchart and then appending the next steps. It will start with the starting letter that we have derived from the previous few actions.
@{variables('FlowChart')}
@{outputs('Compose_-_Last_Step')} --> @{substring(variables('Alphabets'), add(outputs('Compose_-_Letter_No'),2),1)
}@{variables('varSteps')?['Condition']}
@{substring(variables('Alphabets'), add(outputs('Compose_-_Letter_No'),2),1)
} -->@{variables('varSteps')?['Outcome']} @{substring(variables('Alphabets'), add(outputs('Compose_-_Letter_No'),3),1)
}@{variables('varSteps')?['Action']}
The last step on this append side is setting the Flowchart variable with the output from the previous compose action, so that we have a variable with the whole Flowchart ready to convert to Mermaid JS.
Putting it all together
Compose - URL
After the Condition Action we have a single compose action.
This will concatenate a string starting with 'https://mermaid.ink/img/', and here is where we convert the Flowchart variable to base 64 giving us the correct encoding to create a URL.
concat('https://mermaid.ink/img/',base64(variables('FlowChart')))
Respond to App or Flow
All we need to do now, is respond to the App or Flow that triggered this Flow. We will use Text input of Flowchart using the Flowchart variable and a text input of Mermaid URL which includes the URL we have just generated.
Inserting this Child Flow into another Flow to capture the steps
Using the "[child] - Approver Card" as an example from my previous posts. In our Parent Flow we can construct a JSON Object inside of a Compose action.
Compose - Outcome
Capture the outcome of the Adaptive Card
outputs('Post_adaptive_card_and_wait_for_a_response')?['body/submitActionId']
Compose - Action
Convert this outcome using "If(equals" to what the follow on Action will be:
if(equals(outputs('Post_adaptive_card_and_wait_for_a_response')?['body/submitActionId'], 'approve'),'Complete Approval',
if(equals(outputs('Post_adaptive_card_and_wait_for_a_response')?['body/submitActionId'], 'respond'), 'Send back to Requestor',
if(equals(outputs('Post_adaptive_card_and_wait_for_a_response')?['body/submitActionId'], 'reject'),'Inform Requestor',
if(equals(outputs('Post_adaptive_card_and_wait_for_a_response')?['body/submitActionId'], 'acAssignCard'), 'Assign to other person'))))
Compose - varSteps
The JSON Object will be written in a format that could capture various outcomes and statuses that happen within the flow in . For Example:
{
"Trigger": "[@{variables('varObject')?['Request_Type']} Request]",
"Condition": "{@{variables('varObject')?['Approver_Name']} - receives Approval Card}",
"Outcome": "|@{outputs('Compose_-_Status')}|",
"Action": "[@{outputs('Compose_-_Outcome')}]"
}
Then we can trigger the Child Flow using 'Compose - varSteps' as a String.
string(outputs('Compose_-_varSteps'))
and then FlowChart. If we're carrying over a previous one held inside our varObject or Null if we're creating a new one.
if(empty(variables('varObject')['FlowChart']), 'null', variables('varObject')['FlowChart'])
Capture the Response
To then capture from our child flow and add it to our varObject.
Conclusion
Thank you for reading this post. Please leave feedback in the Comments if you found this useful, or if there are any improvements you could recommend. Please experiment with it too, I'd love to hear some of the outcomes.
For such a simple Flow, I found it quite difficult to write and explain, so I hope it all makes sense!
Posted on February 20, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.