- Overview
- Custom activities
- Migrating Activities to .NET 6
- Release Notes
- Building Workflow Analyzer Rules
- Building Activities Project Settings
- Creating Custom Wizards
- Prioritize Activities by Scope
- UiPath.Activities.Api.Base
- UiPath.Studio.Activities.Api
- UiPath.Studio.Activities.Api.Activities
- UiPath.Studio.Activities.Api.BusyService
- UiPath.Studio.Activities.Api.ExpressionEditor
- UiPath.Studio.Activities.Api.Expressions
- UiPath.Studio.Activities.Api.Licensing
- UiPath.Studio.Activities.Api.Mocking
- UiPath.Studio.Activities.Api.ObjectLibrary
- UiPath.Studio.Activities.Api.PackageBindings
- UiPath.Studio.Activities.Api.ProjectProperties
- UiPath.Studio.Activities.Api.ScopedActivities
- UiPath.Studio.Activities.Api.Settings
- UiPath.Studio.Activities.Api.Wizards
- UiPath.Studio.Activities.Api.Workflow
- UiPath.Studio.Api.Controls
- UiPath.Studio.Api.Telemetry
- UiPath.Studio.Api.Theme
- Robot JavaScript SDK
- Triggers SDK
Developer Guide
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.
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.
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.
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.
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.
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.
- Activity: it is the smallest inspection object. For example, Variables Naming Convention, Variable Length Exceeded, and File Activities Stats are three out-of-the-box rules that can be set to inspect activities.
- Workflow: rules or counters may be set to perform checks in just a single project file. High Arguments Count and Empty Sequence are just two predefined rules that have workflow as their scope.
- Project: checks are performed at the project level, ensuring project reliability is met. Unused Dependencies is a rule applied at the project scope.
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 };
}
}
}
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.
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.
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.
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 };
}
}
}
RuleId
, RegexKey
and DefaultRegex
which is the default Regex expression associated with this rule.
RecommendationMessage
and ErrorLevel
parameters are the same as for the previously presented rule.
Counters represent checks done for revealing the number of times a particular event or condition has occurred.
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) }
};
}
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 }
};
Please bear in mind that by using this method, your package is compatible only with Studio versions 2019.10 or higher.
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());
}
}
}
HasFeature
method on the IAnalyzerConfigurationService
to register rules for a specific Studio version.
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 ofIRegisterMetadata
. - In your
Initialize
implementation cast theobject
parameter toWorkflowDesignApi
, and add everything under a Try Catch just to make sure any exceptions are managed. - Once you resolve the
WorkflowDesignApi
, theWorkflowAnalyzerConfigService
is available as a property. - At this point, you have access to the
IAnalyzerConfigurationService
detailed in the section above.
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.
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
- For per-machine
installations:
-
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)
- For per-machine
installations:
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.