Sunday, February 4, 2024

Best Practices: Sending Warnings and Error Notifications

 This is the first post of a series I intend to make about general best practices in Power Automate. My goal is to suggest more general approaches and mind sets when designing for a bigger projects. So these may not be a one-size-fits-all solution, but always a good starting point from which to expand.

What's the issue with warnings and error notifications?

Let's come straight to the point. The built-in system for error reporting is quite inadequate, at least for professional uses. Simply sending an email to the flow owner "Your flow has failed" is not an error notification! 

From the flow owner's perspective, the minimum I would expect is that the notification shows what exactly has failed. But what's worse, the owner of the flow may not be always around. In fact, it's quite common that flow development is done by an external contractor and who, after a period of roll-out, will no longer be available each time a flow fails. What then? 

Notification abstraction

And what about warnings? The reason I included them with the error notifications is that I'd like to propose a continuous issue reporting system that distinguishes severity levels and strives for a report that is complete, precise and helpful. For this reason, I'd like to propose the following system:

  • create a child flow "SendIssueNotification" that has as input parameter: 
    • Notification title
    • Severity level
    • Issue class (flow execution, API, user input, etc.)
    • Notification content
  • store the recipient of the notification in a configuration table (key/value)
The goal of this system is to minimize the actions inside the flows that deals with creating notifications. Let's offload all of that to the "SendIssueNotification" child flow, which can also apply some cosmetic features to the notification - e.g. making major errors more prominent. 

Notification Recipient(s)

As mentioned above, the recipient of the notification is stored in a separate database. This could be a general configurations table for your project, or, if different notifications should go out to different people, e.g. a role-based approach, this could be part of a separate Roles table. 

Also ask yourself, how should your issue reporting system handle...
  • recipients leaving the organisation (can be checked using the profile actions)
  • recipients being out-of-office (e.g. on vacation; can be checked in Office 365!)
  • should each role have a substitute?
  • who will be tasked with keeping this table up to date?

The Problem Code table

Remember, our goal is to reduce error handling actions inside our flows to the necessary minimum. One logical idea then would be to offload it completely to a separate database table. This table consists of all the necessary information to generate a notification: Title, severity level, issue class and content. But in addition to that, it also holds a suggestion for resolution, as well as a unique problem code name, by which the issue will be referred to. We can use such a system alongside the child flow "SendIssueNotification" - in fact it's simply a new child flow "RaiseIssueByCode" that accepts only that problem code, loads the row in the problem code table and then calls "SendIssueNotification". We cannot get rid of "SendIssueNotification" altogether because in your flows, you may encounter situations when you would like to pass an html table with data along side your notification content. However, if this is a very common occurency, you may even contemplate adding a third child flow that extends RaiseIssueByCode with an html table as input. 

So, basically, we've built abstractions to all of our issue notifications. Which makes applying changes to it very easy. 

What's the price we pay for this flexibility? Not much! Sure, we've increased the number of flows being executed, because we haven't created the emails inside our flows, but delegated the task to child flows. But we've also made our flows leaner and easier to maintain. 

Thursday, February 1, 2024

Hardening Child Flows, Part 1: Assertions

 This is part one of a new blog series about hardening child flows. The term hardening originates from metal work, where oil is used to quench hot steel in order to make it much more robust. This is also our goal. Our child flows should be as reliable as possible. More specifically:

  • They should not fail unexpectedly
  • They should handle unexpected input data as well as expected input data an react accordingly
  • They should not fail if a contained action takes longer than expected (timeout & retry policy)
  • They should have a continuance plan for each failed action (e.g. continue anyway, handle, send error message to operator and then terminate, e.g.)
  • Prior to making any sort of writing operation in the database, the input data needs to be checked (assertions)
Today, we will have a look at the last bullet point, assertions, since it's the easiest to grasp from the hardening goals mentioned. 

What are assertions

Think of assertions as conditions in the form of a boolean statement. For example, "is the input value greater or equal to zero?". If the condition is met (true), the flow is allowed to continue. If it isn't met (false), then typically the flow is terminated, but there are other options of course, that would allow your flow to execute anyway, e.g. by use of a default value instead. Assertions should always be the first actions in your flow, giving you the peace of mind, if a run made it through the assertions, the input data is OK which makes writing the rest of your actions in the flow much more easy, because you don't have to crowd your flow with special handling conditions for every possible situation regarding your input values. 

Some programming languages, like Python, have a command called assertion, which is delightfully short and concise:

assert i >=0, "Input value below zero!"

Power Automate, unfortunately, doesn't have such an action built-in. But we can easily recreate it with our own actions. Let's look at some examples

Method 0 (don't use): Terminate on failed assertion

While this is the most straight-forward method, but it has some obvious limitation. Simply letting a child flow fail is not good practice. We can do better!

At least, we managed to avoid having the flow continue with a value outside of the expected range, therefor avoiding further complications inside the subsequent actions...

Btw, don't bother writing a message in the Terminate action. Nobody will see it...


Method 1: Send a message and then terminate

Now at least a pre-defined person will receive a warning before the flow fails. This is the best option, if there is really no better way to handle the failed assertion: The usage of a default value would not be appropriate or the usage of the value cannot be skipped in subsequent actions. 

Just some quick tips: Never hard-code an email address of the person who should receive these messages. People leave companies, it happens :). A smarter way would be to put the person's email in a separate table, either specific to the project or, even better, used by all projects for role-based automated communication. Then create a child flow "Send notification to operator" where all the necessary steps of getting the person's email, compiling the email and sending it are done, allowing you to concentrate in your current flow on the message content. 

Method 2: Limit the value range with compose - don't terminate

If the fact that your input value is below zero can simply be ignored in your flow and a default value (e.g. 0 in this example) can be used instead, then you could use a compose action to set the lower limit of your value. Just make sure to use the output of this compose instead of the trigger input value throughout your flow. 

Of course the major advantage is, that the flow is allowed to continue. This may however mask a potential problem with your data. Beware!

Method 3: Use coalesce() to catch empty input value (null)

The coalesce() Function is a great way to assure a value is not null. Basically it evaluates: 
Is a given value null? 
No? Great, use it as is. 
Yes, it's null? Then use this instead ... 






Final tip: Put your assertions in a scope


Putting all of your assertions in a scope we keep them separated from the rest of your flow, hence improving legibility of your flow.

Wednesday, January 31, 2024

Avoiding nested loops and conditions in Power Automate

What's the issue with nesting? 

Nested loops or conditions make your flow progressively more difficult to read and understand. And quite often they are unnecessary. In the case of loops (Apply to each), Power Automate is quite trigger happy and immediately encloses new actions in an Apply to each loop when you access an element of an array. And in the case of conditions, you often only need actions in one branch - but the second (empty) branch still takes up screen real estate. Also important, Power Automate has a nesting limit of 8 levels, not that I expect many of us to run into this limitation. Last but  not least, Apply to each loops are painfully slow, especially when variables are written inside the loop. But that's a topic for another post. 

What are your options?

For nested "Apply to each" loops: 

  • Replacing actions inside your Apply to each loop with Filter and/or Select actions. 
  • Only need to access the first element in the array? The function first() is a good option (although with some pitfalls...)
  •  Doing complex things inside your loop? Have you considered encapsulating it in its own child flow?
For nested conditions:
  • Consider using an if() expression instead of a condition
  • Use the Terminate action to remove some of the nesting levels, especially while checking for halting conditions ("Does X contain a value? If not -> exit flow")
  • Have you considered using a Switch action instead of a nested condition?
  • Have you tried adding multiple condition rows to your condition?
Let's quickly look at some of these options.

Filter and Select actions

These are extremely useful and efficient actions to handle arrays. In fact, they are so efficient that they often can be completed in a 1/100th of the time it takes to accomplish the same thing with an Apply to each loop. I encourage you to really try and master these two actions! A good place to learn them is on DamoBird365's Youtube videos, e.g. this one. A great introduction to Select by Alireza Aliabadi you can finde here.

first()

Let's say your variable called names contains three names: ["Joe", "Bill", "Max"].
first(variables('names') will of course return Joe. So couldn't you just use first() in all cases where an Apply to each loop is unnecessarily created? You could, yes, just be aware that first(), when used on an empty array, will return null, which maybe be incompatible with subsequent actions in your flow. 

Loading a single Dataverse row without first()

You may have noticed that eventhough your primarily name column may only contain unique entries, trying to load a single entry using such a name column will always return an array (with one or zero rows in it). That means every time you'd like to access data in that row you'd have to use first()? There are better solutions!
a) Load the row based on the unique GUID, using the action "Get a Row by ID". This is guaranteed to return only a single row. If necessary load this GUID using a separate "List Rows" (Dataverse). In the subsequent actions, always refer to the output of "Get a Row by ID" - no first() required!

Loading a single Sharepoint list row without first()?

The approach is similar to the method for Dataverse. "Get item" will try an load a Sharepoint list row using the column "ID". It's also a hidden column, that's created automatically. So you may have to first use "Get items" with a Filter query to extract the hidden ID value. Check out the example below. Now you can use dynamic content to access the values inside of your target row!



Conclusion

Let me know, if there's a specific approach that you'd like to hear more about. 

Sunday, January 28, 2024

Welcome to "Professional Power Automate"!

 Hi everyone and welcome to the "Professional Power Automate" blog. When I was learning Power Automate, I often ran into the issue that there are hardly and blogs on more advanced topics, like "what are best practices to create robust flows" or "what should I consider when deciding a complex system of child flows?". Writing blog posts is also a great way for me to collect my findings and bring them into a more concise form. In that regard, writing is also part of my own learning journey. I also would love this to be a community effort in the sense that we all have different approaches and practices. So, your comment is always welcome. However, all comments are moderated to avoid spam and off-topic discussions. Please be patient, if it takes a couple of days for your comment to pass moderation :).

What this blog is for

  • Designing complex flow systems
  • Best practices
  • Advanced discussions
  • Hardening flows
  • Premium license aspects and workarounds
  • Creating more efficient and fast flows
  • Helper tools that make designing and developing flows easier
  • Dataverse tips
  • Speeding up your workflow

What this blog is NOT about

  • off-topic discussions, i.e. anything outside of the Power Automate and Dataverse
  • not primarily a support blog!
  • Eventhough the name may suggest it: Power Automate for Desktop is considered off-topic (at least for now)

About me

My name is John Flury and I've been working in IT here in Zurich, Switzerland for the last 17 years, after studying computer science at the University of Zurich. My programming background is in Java and C/C++, but I was never a full-time developer. As an IT manager, my focus have been mainly business processes. As such, it's no surprise that Power Automate caught my attention. Since about 2 years I teach Power Automate beginner classes and also work as freelance Power Platform consultant and developer. In my freetime I enjoy being a "Maker", creating interactive installations for kids and build things in and around our house, using Arduino, 3D-Printing, wood and metal. 

MS Forms for Power Platform solutions

 The Core Issue You follow the best practice of working with three environments : DEV, TEST and PROD MS forms cannot be deployed as a soluti...