SDK
latest
false
Banner background image
Developer Guide
Last updated Mar 23, 2024

Building Workflow Analyzer Rules

Workflow Analyzer is a tool for making sure your project follows best practices, maintainability, readability, performance, reusability, reliability, and security requirements.

These concepts are important for ensuring clean and reliable workflows that can sum up large automation projects.

Note: To build custom rules, you need the UiPath.Activities.Api package from the Official feed. The SDK package must be used as a development dependency in your custom project. Read more about Development Dependencies.

Watch the video below to get step-by-step instructions on how to build custom rules, and how to create custom activities with the Using The Activity Creator extension.

In its off-the-shelf form, Workflow Analyzer is integrated into Studio and incorporates validation and analyzer capabilities. Analysis cannot be done if validation returns errors.

Workflow Analyzer Concepts

In order to build custom rules for your project, a number of concepts should be defined to better understand how Workflow Analyzer works behind the scenes.

Note:

When building custom rules, target the .NET version depending on the version of Studio and the project compatibility:

  • Studio 2021.4 and earlier: .NET Framework 4.6.1.
  • Studio 2021.10.6 and later: .NET Framework 4.6.1 for Windows-legacy projects, .NET 6 for Windows and cross-platform projects.

Rules and Counters

Workflow Analyzer makes use of certain criteria to ensure project reliability is met. These checks are performed using carefully defined rules and counters.

A rule represents a requirement that must be met. It can be set to check the inspection object against the rule definition.

A counter represents a check done for revealing the number of times a particular event or condition has occurred.

For each unmet rule, a message is listed in the Error List panel in the form of a warning, error, info, or verbose messages. These listings also contain recommendations for making changes and ensuring rules are met.

Note: When creating custom rules and counters, we recommend following the naming convention detailed in the About Workflow Analyzer page.

Inspection Object

Workflow Analyzer is capable of performing an in-depth analysis of a predefined object. The object represents the scope to be analyzed, the area in which the rule or counter is enforced.

Creating Rules

From the Official feed (https://pkgs.dev.azure.com/uipath/Public.Feeds/_packaging/UiPath-Official/nuget/v3/index.json), install the UiPath.Activities.Api package.

To help you create a custom rule, let's look at a current predefined rule in the Workflow Analyzer, Variable Length Exceeded. This rule checks whether the length of variables defined in the project is smaller than 20 characters. The rule's inspection object is activity.

Behind the scenes, the rule looks like this:

// This static class is not mandatory. It just helps organizining the code.
internal static class VariableLengthRule
    {
  // This should be as unique as possible, and should follow the naming convention.
        private const string RuleId = "ST-NMG-008";
        internal static Rule<IActivityModel> Get()
        {
            var rule = new Rule<IActivityModel>("Variable Length Rule", RuleId, Inspect)
            {
                RecommendationMessage = Recommendation,
              /// Off and Verbose are not supported.
                ErrorLevel = System.Diagnostics.TraceLevel.Warning
            };
            return rule;
        }
  
              // This is the function that executes for each activity in all the files. Might impact performance.
        // The rule instance is the rule provided above which also contains the user-configured data.
                  private static InspectionResult Inspect(IActivityModel activityModel, Rule ruleInstance)
        {
            var messageList = new List<string>();
            foreach(var activityModelVariable in activityModel.Variables)
            {
                if (activityModelVariable.DisplayName.Length > 20)
                {
                    messageList.Add($"The variable {activityModelVariable.DisplayName} has a length longer than 20");
                }
            }
            if (messageList.Count > 0)
            {
                return new InspectionResult()
                {
                    ErrorLevel = ruleInstance.ErrorLevel,
                    HasErrors = true,
                    RecommendationMessage = ruleInstance.RecommendationMessage,
                  // When inspecting a model, a rule can generate more than one message.
                    Messages = messageList
                };
            }
            else
            {
                return new InspectionResult() { HasErrors = false };
            }
        }
    }// This static class is not mandatory. It just helps organizining the code.
internal static class VariableLengthRule
    {
  // This should be as unique as possible, and should follow the naming convention.
        private const string RuleId = "ST-NMG-008";
        internal static Rule<IActivityModel> Get()
        {
            var rule = new Rule<IActivityModel>("Variable Length Rule", RuleId, Inspect)
            {
                RecommendationMessage = Recommendation,
              /// Off and Verbose are not supported.
                ErrorLevel = System.Diagnostics.TraceLevel.Warning
            };
            return rule;
        }
  
              // This is the function that executes for each activity in all the files. Might impact performance.
        // The rule instance is the rule provided above which also contains the user-configured data.
                  private static InspectionResult Inspect(IActivityModel activityModel, Rule ruleInstance)
        {
            var messageList = new List<string>();
            foreach(var activityModelVariable in activityModel.Variables)
            {
                if (activityModelVariable.DisplayName.Length > 20)
                {
                    messageList.Add($"The variable {activityModelVariable.DisplayName} has a length longer than 20");
                }
            }
            if (messageList.Count > 0)
            {
                return new InspectionResult()
                {
                    ErrorLevel = ruleInstance.ErrorLevel,
                    HasErrors = true,
                    RecommendationMessage = ruleInstance.RecommendationMessage,
                  // When inspecting a model, a rule can generate more than one message.
                    Messages = messageList
                };
            }
            else
            {
                return new InspectionResult() { HasErrors = false };
            }
        }
    }
The RuleId parameter requires the name of your rule. In this example, the Variable Length Exceeded rule carries the ST-NMG-008 rule ID and follows the Rule Naming Convention. Please note that the naming convention used by the rules available by default in Studio is not mandatory.
The RecommendationMessage parameter requires a message to be displayed as a recommendation to help the user solve any inconsistencies found after the analysis is done. The message should be succinct and offer clear steps.
The ErrorLevel parameter states the default action to be taken in case the condition isn't met. In this example, the rule throws a warning. Default actions can be error, warning, info or verbose.

Build rules with parameters

The situation changes slightly when we want to build rules that contain customizable parameters. One of such rules is Variables Naming Convention. Its inspection element is activity and carries a default Regex expression, which can be changed.

Behind the scenes, this rule looks like this:

internal static class VariableNamingRule
    {
        private const string RuleId = "ST-NMG-001";
        private const string RegexKey = "Regex";
        private const string DefaultRegex = @"^([A-Z]|[a-z])+([0-9])*$";
        internal static Rule<IActivityModel> Get()
        {
            var rule = new Rule<IActivityModel>(Strings.ST_NMG_001_Name, RuleId, Inspect)
            {
                RecommendationMessage = Recommendation,
                ErrorLevel = System.Diagnostics.TraceLevel.Warning
            };
            rule.Parameters.Add(RegexKey, new Parameter()
        }
                                
       private static InspectionResult Inspect(IActivityModel activityModel, Rule ruleInstance) 
        {
          // This retrieves the parameter value from the rule instance as configured by the user, if not, the default value.
            var setRegexValue = ruleInstance.Parameters[RegexKey]?.Value;
          
            var regex = new Regex(setRegexValue);
            var messageList = new List<string>();
            foreach (var activityModelVariable in activityModel.Variables)
            {
                if(!regex.IsMatch(activityModelVariable.DisplayName))
                {
                    messageList.Add(string.Format(Strings.ST_NMG_001_ErrorFormat, activityModelVariable.DisplayName, setRegexValue));
                }
            }
            if(messageList.Count > 0)
            {
                return new InspectionResult()
                {
                    ErrorLevel = ruleInstance.ErrorLevel,
                    HasErrors = true,
                    RecommendationMessage = ruleInstance.RecommendationMessage,
                    Messages = messageList
                };
            }
            else
            {
                return new InspectionResult() { HasErrors = false };
            }
        }
    }internal static class VariableNamingRule
    {
        private const string RuleId = "ST-NMG-001";
        private const string RegexKey = "Regex";
        private const string DefaultRegex = @"^([A-Z]|[a-z])+([0-9])*$";
        internal static Rule<IActivityModel> Get()
        {
            var rule = new Rule<IActivityModel>(Strings.ST_NMG_001_Name, RuleId, Inspect)
            {
                RecommendationMessage = Recommendation,
                ErrorLevel = System.Diagnostics.TraceLevel.Warning
            };
            rule.Parameters.Add(RegexKey, new Parameter()
        }
                                
       private static InspectionResult Inspect(IActivityModel activityModel, Rule ruleInstance) 
        {
          // This retrieves the parameter value from the rule instance as configured by the user, if not, the default value.
            var setRegexValue = ruleInstance.Parameters[RegexKey]?.Value;
          
            var regex = new Regex(setRegexValue);
            var messageList = new List<string>();
            foreach (var activityModelVariable in activityModel.Variables)
            {
                if(!regex.IsMatch(activityModelVariable.DisplayName))
                {
                    messageList.Add(string.Format(Strings.ST_NMG_001_ErrorFormat, activityModelVariable.DisplayName, setRegexValue));
                }
            }
            if(messageList.Count > 0)
            {
                return new InspectionResult()
                {
                    ErrorLevel = ruleInstance.ErrorLevel,
                    HasErrors = true,
                    RecommendationMessage = ruleInstance.RecommendationMessage,
                    Messages = messageList
                };
            }
            else
            {
                return new InspectionResult() { HasErrors = false };
            }
        }
    }
Notice it calls the RuleId, RegexKey and DefaultRegex which is the default Regex expression associated with this rule.
The RecommendationMessage and ErrorLevel parameters are the same as for the previously presented rule.

Build Counters

Counters represent checks done for revealing the number of times a particular event or condition has occurred.

Their structure is a bit different from that of rules, in the sense that the only available ErrorLevel parameter for counters is Info. Therefore, the expression for defining the error level for a counter should look like this ErrorLevel = System.Diagnostics.TraceLevel.Info.

Let's take File Activities Stats as an example of how counter rules look behind the scenes:

internal static class NumberOfActivitiesInFile
    {
        private const string RuleId = "ST-ANA-009";
        internal static Counter<IActivityModel> Get()
        {
            return new Counter<IActivityModel>(Strings.ST_ANA_009_Name, RuleId, Inspect);
        }
  
  // A Counter<T> receives the entire collection of T objects in the parent structure. e.g. activities in workflow, workflows in project.
  private static InspectionResult Inspect(IReadOnlyCollection<IActivityModel> activities, Counter ruleInstance)
        {
            return new InspectionResult()
            {
              // For a counter, the error level is always info, even if not set here.
                ErrorLevel = System.Diagnostics.TraceLevel.Info,
              // For a counter, the Has Errors field is always ignored.
                HasErrors = false,
                Messages = new List<string>() { string.Format(Strings.ST_ANA_009_ErrorFormat,  activities.Count) }
            };
        }internal static class NumberOfActivitiesInFile
    {
        private const string RuleId = "ST-ANA-009";
        internal static Counter<IActivityModel> Get()
        {
            return new Counter<IActivityModel>(Strings.ST_ANA_009_Name, RuleId, Inspect);
        }
  
  // A Counter<T> receives the entire collection of T objects in the parent structure. e.g. activities in workflow, workflows in project.
  private static InspectionResult Inspect(IReadOnlyCollection<IActivityModel> activities, Counter ruleInstance)
        {
            return new InspectionResult()
            {
              // For a counter, the error level is always info, even if not set here.
                ErrorLevel = System.Diagnostics.TraceLevel.Info,
              // For a counter, the Has Errors field is always ignored.
                HasErrors = false,
                Messages = new List<string>() { string.Format(Strings.ST_ANA_009_ErrorFormat,  activities.Count) }
            };
        }

Build Rules for StudioX

By default, rules only apply to the Studio profile. To make a rule appear in the StudioX profile as well, add the ApplicableScopes property and configure it to include BusinessRule. For example, you can add the property like this:
var rule = new Rule<IActivityModel>(Strings.ORG_USG_001_Name, RuleId, Inspect)
            {
                RecommendationMessage = Strings.ORG_USG_001_Recommendation,
                ErrorLevel = TraceLevel.Error,
                //Must contain "BusinessRule" to appear in StudioX, rules always appear in Studio
                ApplicableScopes = new List<string> { RuleConstants.BusinessRule }
            };var rule = new Rule<IActivityModel>(Strings.ORG_USG_001_Name, RuleId, Inspect)
            {
                RecommendationMessage = Strings.ORG_USG_001_Recommendation,
                ErrorLevel = TraceLevel.Error,
                //Must contain "BusinessRule" to appear in StudioX, rules always appear in Studio
                ApplicableScopes = new List<string> { RuleConstants.BusinessRule }
            };

Rules Registration

Registration Interface Method

Please bear in mind that by using this method, your package is compatible only with Studio versions 2019.10 or higher.

Implement the IRegisterAnalyzerConfiguration interface with the following method Initialize(IAnalyzerConfigurationService workflowAnalyzerConfigService).
using UiPath.Studio.Activities.Api;
using UiPath.Studio.Activities.Api.Analyzer;
using UiPath.Studio.RulesLibrary.Rules.Naming;
namespace UiPath.Studio.RulesLibrary
{
    public class RegisterAnalyzerConfiguration : IRegisterAnalyzerConfiguration
    {
        public void Initialize(IAnalyzerConfigurationService workflowAnalyzerConfigService)
        {
            // Naming
            workflowAnalyzerConfigService.AddRule(VariableNamingRule.Get());
            workflowAnalyzerConfigService.AddRule(DisplayNameDuplicationRule.Get());
            workflowAnalyzerConfigService.AddRule(VariableNameDuplicationRule.Get());
            workflowAnalyzerConfigService.AddRule(ArgumentNamingRule.Get());
            workflowAnalyzerConfigService.AddRule(VariableLengthRule.Get());
        }
    }
}using UiPath.Studio.Activities.Api;
using UiPath.Studio.Activities.Api.Analyzer;
using UiPath.Studio.RulesLibrary.Rules.Naming;
namespace UiPath.Studio.RulesLibrary
{
    public class RegisterAnalyzerConfiguration : IRegisterAnalyzerConfiguration
    {
        public void Initialize(IAnalyzerConfigurationService workflowAnalyzerConfigService)
        {
            // Naming
            workflowAnalyzerConfigService.AddRule(VariableNamingRule.Get());
            workflowAnalyzerConfigService.AddRule(DisplayNameDuplicationRule.Get());
            workflowAnalyzerConfigService.AddRule(VariableNameDuplicationRule.Get());
            workflowAnalyzerConfigService.AddRule(ArgumentNamingRule.Get());
            workflowAnalyzerConfigService.AddRule(VariableLengthRule.Get());
        }
    }
}
Note: Call the HasFeature method on the IAnalyzerConfigurationService to register rules for a specific Studio version.

IRegisterMetadata Method

Please note that this method is available only for Studio versions higher than 2019.6, and it's not as suitable as the registration interface method.

  • Add a method void Initialize(object api) to your implementation of IRegisterMetadata.
  • In your Initialize implementation cast the object parameter to WorkflowDesignApi, and add everything under a Try Catch just to make sure any exceptions are managed.
  • Once you resolve the WorkflowDesignApi, the WorkflowAnalyzerConfigService is available as a property.
  • At this point, you have access to the IAnalyzerConfigurationService detailed in the section above.

Add Rules to Studio

Custom Workflow Analyzer rules can be integrated in Studio in two ways:

  • at global level by adding the external assembly (.dll) in the Studio install location
  • at project level by installing the custom activities pack.

At Global Level

To make custom rules available in all the projects created in your instance of Studio, you must add the external assembly (.dll) packages to a specific folder from which Studio loads custom rules. By default, the folder path is:

  • In Studio versions prior to 2021.10:

    • For per-machine installations: %ProgramFiles%\UiPath\Studio\Rules
    • For per-user installations: %LocalAppData%\Programs\UiPath\Studio\Rules
  • In Studio 2021.10.6 and later:

    • For per-machine installations: %ProgramFiles%\UiPath\Studio\Rules\net6.0 (for rules targetting Windows and cross-platform projects) and %ProgramFiles%\UiPath\Studio\net461\Rules (for rules targetting Windows-legacy projects)
    • For per-user installations: %LocalAppData%\Programs\UiPath\Studio\Rules\net6.0 (for rules targetting Windows and cross-platform projects) and %LocalAppData%\Programs\UiPath\Studio\net461\Rules (for rules targetting Windows-legacy projects)

Optionally, you can change the folder from which Studio loads custom rules by going to Home (Studio Backstage View) > Settings > Locations and defining a Custom Workflow Analyzer rules location. This feature is available in Studio v2020.10. and later.

Follow the steps detailed in the Creating a Custom Activity page to export the code as a .dll file.

At Project Level

To make custom rules available only for a certain project, create a NuGet package (.nupkg) and install it in a Studio project as a dependency.

Was this page helpful?

Get The Help You Need
Learning RPA - Automation Courses
UiPath Community Forum
Uipath Logo White
Trust and Security
© 2005-2024 UiPath. All rights reserved.