Automated Workflows (.NET)
The Automated Workflows API allows developers to integrate with the automated workflows framework to programmatically register actions, register triggers and send triggers. Review the content in Fundamentals of automated workflows before you start writing code to interact with automated workflows.
You can also interact with the Automated Workflows API through the REST API. See Automated Workflows service.
Working with the Automated Workflows API
Note: The examples below assume that you already know how to create a Kepler Service, and how to create event handlers in Relativity.
Instantiating the Automated Workflows Manager
You will use the AutomatedWorkflowsManager class to obtain a handle on the API that is exposed on the SDK. Once you have the API, you will be able to call functions that will register actions and triggers, send triggers and so on. Below is a collapsed view of the automated workflows class and constructor that you will need to utilize:
public class AutomatedWorkflowsManager: IAutomatedWorkflowsManager
{
public AutomatedWorkflowsManager(IHelper relativityHelper)
{
/*...*/
}
// ...
}
The AutomatedWorkflowsManager constructor requires an IHelper constructor dependency. Let us construct an example of a class that uses the provided AutomatedWorkflowsManager and provide the dependencies. To do this we will inject IHelper in the constructor of SomeClass, so that automated workflows can use it.
public class SomeClass
{
private readonly IAutomatedWorkflowsManager _automatedWorkflowsManager;
public SomeClass(IHelper helper)
{
_automatedWorkflowsManager = new AutomatedWorkflowsManager(helper);
}
}
A couple of important notes about the code snippet above:
- We Injected IHelper into the class via construction.
- We instantiated AutomatedWorkflowsManager and assigned it to a private IAutomatedWorkflowsManager interface. It is considered a best practice to rely on abstractions, in this case the interface instead of concrete implementations, the class.
Note: Although it will not be covered in detail on this topic, it is considered best practice to apply the Inversion of Control (IoC) principle when dealing with construction dependencies. In this case IAutomatedWorkflowsManager is a construction dependency of SomeClass. To achieve this you should register the interface and concrete class on your Dependency Injection framework of choice. By default, Relativity comes integrated with Castle Windsor. The registration looks like this:
Component.For<IIAutomatedWorkflowsManager>().ImplementedBy<AutomatedWorkflowsManager>().LifeStyle.Transient;
It is recommended to use a Transient Lifestyle for the IAutomatedWorkflowsManager IoC registration.
With the instance of IAutomatedWorkflowsManager available, you can now proceed to the other examples below.
If you are going to use the Automated Workflows SDK on an event handler or Kepler Service, note that the IHelper instance that AutomatedWorkflowsManager requires is different on an event handler versus a Kepler Service. On a Kepler Service, you can obtain it via Constructor Injection, as shown in the code snippet below:
public class ExampleCustomService: IExampleCustomService
{
public ExampleCustomService(IHelper helper) {}
}
On an event handler, the Helper is a built-in property. Inside any non-static method in the event handler you can just reference the Helper property, as shown below:
this.Helper
Using the SDK on a Kepler Service
This section explains how to use the SDK on a Kepler service. This section assumes you read the section on how to instantiate AutomatedWorkflowsManager and obtain its dependencies.
To begin, create an example Kepler Service Endpoint that is going to call the AutomatedWorkflows API. In this example, a service with one endpoint that performs an operation using the POST HTTP method by receiving a workspaceID as part of the URL:
using System.Threading.Tasks;
using Relativity.API;
using Relativity.AutomatedWorkflows.SDK;
using Relativity.Kepler.Services;
namespace Relativity.AutomatedWorkflows.Services.V2 {
/// <summary>
/// A Kepler Service Module
/// </summary>
[ServiceModule("Automated Workflows Example Module")]
[RoutePrefix("relativity-automated-workflows", VersioningStrategy.Namespace)]
// ReSharper disable once UnusedType.Global
public interface IAutomatedWorkflowsSdkModule {
}
/// <summary>
/// A Response from the Example Custom Service
/// </summary>
public class ExampleResponse {
public bool Success {
get;
set;
}
public string Message {
get;
set;
}
}
/// <summary>
/// The Interface that describes the Example Custom Service
/// </summary>
[WebService("Example")]
[ServiceAudience(Audience.Private)]
public interface IExampleCustomService {
[HttpPost]
[Route("~/workspace/{workspaceID:int}/example")]
Task <ExampleResponse> ExecuteExampleAsync(int workspaceID);
}
public class ExampleCustomService: IExampleCustomService {
public Task <ExampleResponse> ExecuteExampleAsync(int workspaceID) {
return Task.FromResult(new ExampleResponse());
}
}
}
Next, inject IAutomatedWorkflowsManager to the service
public class ExampleCustomService: IExampleCustomService
{
private readonly IAutomatedWorkflowsManager _automatedWorkflowsManager; // Created a private readonly field
public ExampleCustomService(IHelper helper)
{
_automatedWorkflowsManager = new AutomatedWorkflowsManager(helper); // instantiated the class and assigned
}
public Task<ExampleResponse> ExecuteExampleAsync(int workspaceID)
{
// here we can start using _automatedWorkflowsManager to call the different automatedworkflow endpoints
return Task.FromResult(new ExampleResponse());
}
}
You can now use the IAutomatedWorkflowsManager abstraction, the interface, in any method inside a Kepler Service
Using the SDK on an Event Handler
This section explains how to use the SDK on an Event Handler. This section assumes you read the section on how to instantiate AutomatedWorkflowsManager and obtain its dependencies.
First, create an Empty Event Handler, in this example a Pre-Save Event Handler. The Execute method runs on every change of an RDO and before it is persisted:
using kCura.EventHandler;
using kCura.EventHandler.CustomAttributes;
namespace Relativity.AutomatedWorkflows.EventHandlers {
[Description("Example Pre Save Event Handler")]
[System.Runtime.InteropServices.Guid("249EE7C0-AF23-4846-BAA8-545EBF4C107A")]
public class ExamplePreSaveEventHandler: kCura.EventHandler.PreSaveEventHandler, ICanExecuteWithLimitedContext {
public override Response Execute() {
// code starts here
}
public override FieldCollection RequiredFields {
get;
} = new FieldCollection();
}
}
Next, add IAutomatedWorkflowsManager to the Event Handler
[Description("Example Pre Save Event Handler")]
[System.Runtime.InteropServices.Guid("249EE7C0-AF23-4846-BAA8-545EBF4C107A")]
public class ExamplePreSaveEventHandler: kCura.EventHandler.PreSaveEventHandler, ICanExecuteWithLimitedContext {
private readonly IAutomatedWorkflowsManager _automatedWorkflowsManager;
public ExamplePreSaveEventHandler() {
_automatedWorkflowsManager = new AutomatedWorkflowsManager(Helper);
}
// For Unit Testing
protected ExamplePreSaveEventHandler(IAutomatedWorkflowsManager automatedWorkflowsManager) {
_automatedWorkflowsManager = automatedWorkflowsManager;
}
public override Response Execute() {
// start using it here
}
public override FieldCollection RequiredFields {
get;
} = new FieldCollection();
}
The instance is now ready to use.
Registering your Trigger
This section assumes you read the section on how to instantiate AutomatedWorkflowsManager and obtain its dependencies.
To register a trigger, we need to utilize the IAutomatedWorkflowsManager abstraction. The RegisterTriggerAsync method requires two parameters:
- A Workspace ID.
- The body of the Trigger to register. See RegisterTriggerBody.
While the workspace ID is fairly straightforward, the body of the trigger might not be. Let us walk though creating a trigger body step-by-step.
First, define a Label, ID and Version for your trigger. See Versioning your trigger or action for more on versioning. The idea is to have your trigger name as a namespace-like string, and a version that can be bumped on future breaking changes.
var trigger = new RegisterTriggerBody
{
Label = "Example Trigger",
ID = "my-company/example-trigger",
Version = 1,
Group = TriggerGroup.Search,
SelectableAsInitialTrigger = true
};
Next, add inputs and provide a list of InputDefinition. In this example, we define a trigger with a drop-down input field, having two options:
var trigger = new RegisterTriggerBody
{
Label = "Example Trigger",
ID = "my-company/example-trigger",
Version = 1,
SelectableAsInitialTrigger = true,
Inputs = new List<InputDefinition>
{
new InputDefinition
{
Label = "Input 1",
Placeholder = "My Input Placeholder",
ID = "input1",
UIElementType = UIElementType.Dropdown,
InputSources = new List<InputSource>
{
new InputSource
{
Label = "Option 1",
Value = "option1"
},
new InputSource
{
Label = "Option 2",
Value = "option2"
}
}
}
}
};
RegisterTriggerBody
Property | Description | Type | Required |
---|---|---|---|
Label | Label that will be presented to the user for the trigger. | String | true |
ID | Identifier that will be used by Automated Workflows to trigger a workflow. Example: relativity@new-documents-added | String | true, must be unique |
Group | A string that is used by the user interface to group your action amongst the same group. | String See Group Types for a list of available values. |
true |
Version | Version of the trigger. | Integer | true, defaults to 1 |
SelectableAsInitialTrigger | Specifies if this trigger can be added via the UI by the user as the initial trigger to start an automated workflow. | bool | false, defaults to true |
Inputs | A list of input definitions that define the type of input fields and its values. | InputDefinition[] See Input Definition. |
false |
States | Possible states for triggering a workflow. | String[] See Trigger States. |
false |
Finally, call RegisterTriggerAsync to register the trigger as shown below. Your Example Trigger trigger should then be available in the workspace.
await _automatedWorkflowsManager.RegisterTriggerAsync(workspaceID, trigger);
Full code snippet to register the trigger:
var trigger = new RegisterTriggerBody
{
Label = "Example Trigger",
ID = "my-company/example-trigger",
Version = 1,
SelectableAsInitialTrigger = true,
Inputs = new List<InputDefinition>
{
new InputDefinition
{
Label = "Input 1",
Placeholder = "My Input Placeholder",
ID = "input1",
UIElementType = UIElementType.Dropdown,
InputSources = new List<InputSource>
{
new InputSource
{
Label = "Option 1",
Value = "option1"
},
new InputSource
{
Label = "Option 2",
Value = "option2"
}
}
}
}
};
await _automatedWorkflowsManager.RegisterTriggerAsync(workspaceID, trigger);
Trigger states
Trigger states are states that can trigger a workflow or mark an action, within a workflow, as complete. The default states are:
- complete
- complete-with-errors
- error
- canceled
Drop-down with Object Manager Query
Instead of having hard-coded values on the drop-down, you may want to obtain those values from a query. Below is an example on how to achieve this by using the ObjectManagerQueryInputSource property. Replace InputSources with ObjectManagerQueryInputSource as shown in the example below. Refer to this page for more details on supported query conditions.
new InputDefinition
{
Label = "Input 1",
Placeholder = "My Input Placeholder",
ID = "input1",
UIElementType = UIElementType.Dropdown,
ObjectManagerQueryInputSource = new ObjectManagerInputs
{
ValueFieldName = "ArtifactID",
LabelFieldName = "Name",
ArtifactTypeID = 29,
Condition = "\'Type\' LIKE \'dtSearch\'"
}
}
- ValueFieldName—the field to obtain the value from.
- LabelFieldName—the field to obtain the label from.
- ArtifactTypeID—artifact type ID to run the query against.
- Condition—Object Manager query Language condition.
Registering your action
This section assumes you read the first section of usage examples on how to instantiate AutomatedWorkflowsManager and obtain its dependencies. To register an action, we need to utilize the IAutomatedWorkflowsManager abstraction. The RegisterActionAsync method requires two parameters:
- A Workspace ID.
- The body of the Action to register. See RegisterActionBody.
While the workspace ID is fairly straightforward, the body of the action might not be. Let us walk though creating an action body step-by-step.
First we need a Label, ID and Version for your action, and finally a group. See Versioning your trigger or action for more on versioning. The idea is to have your action name as a namespace-like string and a version that can be bumped on future breaking changes.
var action = new RegisterActionBody
{
Label = "Action Label",
ID = "my-company/example-action",
Version = 1,
Group = ActionGroup.Miscellaneous
};
Next, we will add an Inputs property that will provide a list of InputDefinition. Each element in the list defines the label and value for an input field in the UI. In the example below, we have an action with one drop-down input field containing a list of two hard-coded values:
var action = new RegisterActionBody
{
Label = "Action Label",
ID = "my-company/example-action",
Version = 1,
Group = "Miscellaneous",
Inputs = new List<InputDefinition>
{
new InputDefinition
{
Label = "Field Label",
ID = "input1",
Placeholder = "Field Placeholder",
UIElementType = UIElementType.Dropdown,
InputSources = new List<InputSource>
{
new InputSource
{
Label = "Input Label",
Value = "Value"
},
new InputSource
{
Label = "Input Label 2",
Value = "Value 2"
}
}
}
}
};
Example of action from the code snippet above
Now, define what the action is going to do. This is described in the Steps property. You have two options:
- an action can perform a Request/Response (RPC) operation
- an action can perform a Long Running (LongRunningRPC) operation
RegisterActionBody
Property | Description | Type | Required |
---|---|---|---|
Label | Used by the user interface as the label of the action. | String | true |
ID | A string that would universally identify given action across different Relativity instances. Example: relativity@update-dt-search-index. | String | true |
Group | A string that is used by the user interface to group your action amongst the same group. | String See Group Types for a list of available values. |
true |
Version | Version of the action. | Integer | true |
Inputs | Input fields that are displayed in the UI. | InputDefinition[] See InputDefinition. |
false |
Steps | Steps for action. |
ActionStepDefinition[] |
true |
InputDefinition
Property | Description | Type | Required |
---|---|---|---|
Label | Used by the user interface as the label of the input field. | String | true |
ID | Unique ID of the field on that trigger/action. | String | true |
Placeholder | HTML placeholder attribute of the field. | String | false |
UIElementType | The type of field to create. | String See Field Types. |
true |
InputSources | Hardcoded list of values to display and to allow the user to select. | InputSource[] See InputSource. |
false |
ObjectManagerQueryInputSource | Information to get dynamic list of values from Object Manager. | ObjectManagerInputs See ObjectManagerQueryInputSource. |
false |
BooleanInputSource | Information to set boolean label/value for field. | BooleanInputSource See BooleanInputSource. |
false |
Properties | Contains the expression that is evaluated to determine the visibility of the input field. | InputDynamicProperties See InputDynamicProperties. |
false |
Field types
- Dropdown—a HTML Dropdown that allows one option selection.
- Hidden—an input that is not visible to the user, uses a default value.
- MultiCheckbox—list of Checkboxes, allows multiple choice.
- Email—email text area that allows multiple emails separated by ";".
- RadioButtons—radio button list, allows one choice.
- Switch—Boolean toggle (true/false).
InputSource
Property | Description | Type | Required |
---|---|---|---|
Label | Label of the drop-down option. | String | true |
Value | Value of the drop-down option. | String | true |
Children | Children. | InputDefinition[] See InputDefinition. |
false |
ObjectManagerQueryInputSource
Property | Description | Type | Required |
---|---|---|---|
LabelFieldName | The field to obtain the label from | String | true |
ValueFieldName | The field to obtain the value from | String | true |
Guid | The GUID of the object type to run the query against | Guid | false |
ArtifactTypeID | Artifact type ID to run the query against | Integer | false |
Condition | Object Manager query Language condition | String | false |
BooleanInputSource
Property | Description | Type | Required |
---|---|---|---|
TrueLabel | The GUID of the object type that the Object Manager Query will query. | Guid | false |
FalseLabel | The artifact id of the object type. | Integer | false |
TrueValue | The property retrieved from the Object Manager Query that will be displayed for the corresponding object in the UI. | String | true |
FalseValue | The value that will be stored for the selected object. | String | true |
ActionStepDefinition
Property | Description | Type | Required |
---|---|---|---|
Conditions | A conditional statement used to determine if the action step should be executed. Conditions added to this array will be treated as 'AND' conditions. To create an 'OR' condition, create a separate 'step' within the 'steps' array. | String[] | false |
Type | Type of job the step will run. | String See ActionStepType. |
true |
Http | Information for the http endpoint that will be called. | ActionStepHttp See ActionStepHttp. |
true |
StatusCheck | Information for the http endpoint that will be called in a long running operation. | StatusCheckDefinition See StatusCheckDefinition. |
false, only required when Type is LongRunningRPC |
Triggers | A list of triggers that must be complete before an action is marked as complete and next action, in workflow, is executed. | ActionStepCompletionTrigger[] See ActionStepCompletionTrigger. |
false, only required when Type is LongRunningRPC |
NotifiesCompletion | bool | false, defaults to false |
ActionStepType
Type of job the step will run. There are 2 types of jobs
- LongRunningRPC—a step that registers triggers and requires them to fire before moving to the next step or action.
- RPC—a simple remote procedure call, HTTP request, that does not require a trigger to continue to the next step or action, just a successful response on the request.
ActionStepHttp
Property | Description | Type | Required |
---|---|---|---|
Url | The HTTP endpoint from the API to call. | String | true |
Headers | The headers to send with the request. | JSON Object | false |
Body | The message content to send in the request. | JSON Object | false |
Method | The HTTP method type. For example, GET, POST, and others. | String | true |
StatusCheckDefinition
Property | Description | Type | Required |
---|---|---|---|
RetryLimit | The message content to send in the request. | Integer | false |
Http | The HTTP method type. For example, GET, POST, and others. | ActionStepHttp See ActionStepHttp. |
false |
ActionStepCompletionTrigger
Property | Description | Type | Required |
---|---|---|---|
Label | The label of the trigger from the UI. | String | false |
ID | The id of the trigger, shared across multiple Relativity instances, that executes when the action has completed or errored. This will become the topic name. | String | false |
SelectableAsInitialTrigger | Specifies if the trigger can be added to the workflow by a user. | Integer | false |
TriggerInputs | ActionInputRef[] See ActionInputRef. |
true | |
States | States when action is marked as complete. | String[] See Trigger States. |
false |
ActionInputRef
Property | Description | Type | Required |
---|---|---|---|
ActionInputID | The id of the input that will be used in the trigger definition. | String | false |
Group types
- Analytics
- Document Update
RPC
An RPC step is the best choice for HTTP calls that do not take long to respond, such as:
- Sending an email
- Triggering another operation as a fire-and-forget
- Raising an event
- Enqueuing messages
RPC actions should send an HTTP response without human recognizable delays. If your endpoint takes longer than one second, consider applying an asynchronous pattern to it. For longer calls, the Long Running RPC is the preferred approach.
To add a RPC step to our action, you need to add RPC structure to the Steps list:
var action = new RegisterActionBody
{
Label = "Action Label",
ID = "my-company/example-action",
Version = 1,
Group = "Miscellaneous",
Inputs = new List<InputDefinition>
{
new InputDefinition
{
Label = "Field Label",
ID = "input1",
Placeholder = "Field Placeholder",
InputSources = new List<InputSource>
{
new InputSource
{
Label = "Input Label",
Value = "Value"
}
}
}
},
Steps = new List<ActionStepDefinition>
{
new ActionStepDefinition
{
Type = ActionStepType.RPC,
Http = new ActionStepHttp
{
Url = "@{variables('RelativityInstanceURL')}/MyCompany.REST/API/v1/notification/workspace/@{variables('WorkspaceID')}/email/send",
Headers = new Dictionary <string,string>
{
{
"x-csrf-header",
"-"
},
{
"content-type",
"application/json"
}
},
Body = new
{
WorkflowArtifactId = "@variables('WorkflowArtifactId')",
WorkflowRunId = "@workflow().run.id",
ActionDefinitionId = "@variables('ActionDefinitionId')",
ActionIndex = "@variables('ActionIndex')",
SequenceId = "@variables('SequenceId')"
},
Method = ActionHttpMethodType.Post,
},
}
}
};
- new ActionStepDefinition—this is the class that contains the definition of both RPC and LongRunningRPC.
- Type—the type of the step. RPC in this example.
- Url—the URL to send the request. Note the code-like language used in the string of the URL. See Action Definition Variables
- Headers—the headers to send with the request.
- Body—a free form object that will be sent with the request.
- Method—the HTTP Method to send the request. Note that their values are not capitalized. The supported methods are:
- Get
- Post
- Put
- Delete
- Patch
Long Running RPC
A Long Running RPC is an operation that initiates a process, and eventually reports its completion status. Eventually can mean seconds, minutes, hours, or even days. Examples of these long-running actions include building a dtSearch or building a Search Terms Report.
To avoid getting the automated workflow stuck, the Long Running RPC runs in an asynchronous manner:
- First, the action, your HTTP call, is invoked and we receive a success, indicating that the job/operation was started.
- Meanwhile, the workflow carries on with other duties and listens to events of completion of the job/operation that was previously started.
- Eventually, we will receive an event with the completion of the job.
Below code snippet demonstrates the whole object structure of the Long Running RPC:
new RegisterActionBody
{
Label = "Build dtSearch Index",
ID = RelativityActionID.BuildDtSearch,
Group = "Search",
Inputs = new List<InputDefinition>
{
new InputDefinition
{
Label = "Index",
ID = "index-id",
UIElementType = UIElementType.Dropdown,
Properties = new InputDynamicProperties(),
ObjectManagerQueryInputSource = new ObjectManagerInputs
{
ValueFieldName = "ArtifactID",
LabelFieldName = "Name",
ArtifactTypeID = 29,
Condition = "\'Type\' LIKE \'dtSearch\'"
}
}
},
Steps = new List<ActionStepDefinition>
{
new ActionStepDefinition
{
Type = ActionStepType.LongRunningRPC,
Http = new ActionStepHttp
{
Url = "@{variables('RelativityInstanceURL')}/MyCompany.REST/API/compute/workspace/@{variables('WorkspaceID')}/dtSearchIndexes/@{variables('index-id')}/buildIndex",
Method = ActionHttpMethodType.Post,
Body = "{ \"isActive\": true }",
Headers = new Dictionary <string, string>
{
{
"x-csrf-header",
"-"
},
{
"content-type",
"application/json"
}
}
},
StatusCheck = new StatusCheckDefinition
{
Http = new ActionStepHttp
{
Url = "@{variables('RelativityInstanceURL')}/MyCompany.REST/API/indexes/v1/workspaces/@{variables('WorkspaceID')}/dtsearch-indexes/@{variables('index-id')}/index-status",
Method = ActionHttpMethodType.Get,
Body = "",
Headers = new Dictionary<string,string>
{
{
"x-csrf-header",
"-"
},
{
"content-type",
"application/json"
}
}
},
},
Triggers = new List<ActionStepCompletionTrigger>
{
new ActionStepCompletionTrigger
{
Label = "Example Completed",
ID = "my-company/on-example-completed",
SelectableAsInitialTrigger = true,
TriggerInputs = new List<ActionInputRef>
{
new ActionInputRef
{
ActionInputID = "index-id"
}
}
}
}
}
};
The first thing to notice is the similar initial structure of the Http property with the Http property of the RPC approach. See Action Definition Variables.
The next is the StatusCheck property. It contains an Http property that works the same way as the Http property above. As mentioned before, the Long Running RPC works asynchronously. It listens to an event/trigger of completion of the job/operation that was called, defined in the http property. However, there may be scenarios where the event fails to be sent or received, even after retries. To ensure the workflow is not going to wait forever, the system pings your action to inquire of its status after an initial timeout has elapsed. This is done by using the definition of the StatusCheck.Http property and invoking the HTTP request. If you use a Long Running RPC action, you must implement a Status Check, also called Get Status, API. Your API must follow the JSON Schema below on the response. The string values of Status can be found in C# on the TriggerState class of the SDK.
{
"$id": "[https://example.com/example.json](https://example.com/example.json)",
"$schema": "[https://json-schema.org/draft-07/schema](https://json-schema.org/draft-07/schema)",
"examples": [
{
"Status": "complete"
}
],
"required": [
"Status"
],
"type": "object",
"properties": {
"Status": {
"$id": "#/properties/Status",
"default": "",
"description": "Status of the Job/Operation",
"examples": [
"complete"
],
"title": "The Status schema",
"enum": [
"complete",
"complete-with-errors",
"error",
"canceled"
],
"type": "string"
}
},
"additionalProperties": true
}
The last portion of the Long Running RPC object is the Triggers property:
Triggers = new List<ActionStepCompletionTrigger>
{
new ActionStepCompletionTrigger
{
Label = "Example Completed",
ID = "my-company/on-example-completed",
SelectableAsInitialTrigger = true,
TriggerInputs = new List<ActionInputRef>
{
new ActionInputRef
{
ActionInputID = "index-id"
}
}
}
}
This defines what triggers your action plans on sending back to Relativity. This includes the ID of the trigger, a Label to be used on the user interface, and you can optionally decide that a trigger you send can trigger other workflows by setting the SelectableAsInitialTrigger value to true.
The triggers defined here are also listened by the automated workflow while it is running. When the system receives a trigger stating that your job was CompleteWithErrors, automated workflows will tag the state of the action inside the automated workflow as CompleteWithErrors. The user will then be able to see that the action completed with errors.
Full Sample of a LongRunningRPC:
new RegisterActionBody
{
Label = "Example Action",
ID = "my-company/example-action",
Group = "Search",
Inputs = new List<InputDefinition>
{
new InputDefinition
{
Label = "Index",
ID = "index-id",
UIElementType = UIElementType.Dropdown,
ObjectManagerQueryInputSource = new ObjectManagerInputs
{
ValueFieldName = "ArtifactID",
LabelFieldName = "Name",
ArtifactTypeID = 29,
Condition = "\'Type\' LIKE \'dtSearch\'"
}
}
},
Steps = new List<ActionStepDefinition>
{
new ActionStepDefinition
{
Type = ActionStepType.LongRunningRPC,
Http = new ActionStepHttp
{
Url = "@{variables('RelativityInstanceURL')}/MyCompany.REST/API/compute/workspace/@{variables('WorkspaceID')}/dtSearchIndexes/@{variables('index-id')}/buildIndex",
Method = ActionHttpMethodType.Post,
Body = "{ \"isActive\": true }",
Headers = new Dictionary<string, string>
{
{
"x-csrf-header",
"-"
},
{
"content-type",
"application/json"
}
}
},
StatusCheck = new StatusCheckDefinition
{
Http = new ActionStepHttp
{
Url = "@{variables('RelativityInstanceURL')}/MyCompany.REST/API/indexes/v1/workspaces/@{variables('WorkspaceID')}/dtsearch-indexes/@{variables('index-id')}/index-status",
Method = ActionHttpMethodType.Get,
Body = "",
Headers = new Dictionary<string, string>()
{
{
"x-csrf-header",
"-"
},
{
"content-type",
"application/json"
}
}
},
},
Triggers = new List <ActionStepCompletionTrigger>
{
new ActionStepCompletionTrigger
{
Label = "dtSearch Index Build Completed",
ID = "relativity/on-example-completed",
SelectableAsInitialTrigger = true,
TriggerInputs = new List<ActionInputRef>
{
new ActionInputRef
{
ActionInputID = "index-id"
}
}
}
}
}
}
};
await _automatedWorkflowsManager.RegisterActionAsync(workspaceID, action);
Registering your Action
Call RegisterActionAsync to register your action. Once this is complete, your action should now be available in the workspace.
await _automatedWorkflowsManager.RegisterActionAsync(workspaceID, action);
Action Definition Variables
The URLs and bodies of the Http property contain code. That code allows you to create URL and body templates when creating your action because the workspaceID for example will vary from workflow to workflow. Syntax rules:
- Start with an “@”
- If string interpolated, wrap with “{” and “}”
- a function call, for example, “variables”
- parameters between parenthesis: “()”
- string parameters use single quotes
URL example:
"@{variables('RelativityInstanceURL')}/MyCompany.REST/API/indexes/v1/workspaces/@{variables('WorkspaceID')}/dtsearch-indexes/@{variables('index-id')}/index-status"
Step body example. Note that the example below is not string interpolated, so they do not need to be wrapped with curly braces.
{
WorkflowArtifactId = "@variables('WorkflowArtifactId')",
WorkflowRunId = "@workflow().run.id",
ActionDefinitionId = "@variables('ActionDefinitionId')",
ActionIndex = "@variables('ActionIndex')",
SequenceId = "@variables('SequenceId')"
}
In the example you can see that we concatenate a few variables in between a string that is in the format of a URL. The variables/functions available:
- RelativityInstanceURL—the base URL of the Relativity instance
- WorkspaceID—the workspace ID of the automated workflow
- Any defined input.ID—any input ID you define on your action is available to use here.
- WorkflowArtifactId—the ID of the automated aorkflow
- @workflow().run.id—the Identifier of a Run of an Automated Workflow. This is unique per automated workflow per run
- SequenceId—the Sequence Identifier where the action is placed within the workflow. Because of the deprecation of sequences, this value will always be “seq-1”.
- ActionDefinitionId—the ID of the action.
- ActionIndex—the Zero-Based index of the action within a list of actions. It is the order of the action in a workflow within the sequence of actions. The combination SequenceId-ActionIndex-ActionDefinitionId identifies an action in an automated workflow. You may have two actions with the same ID in a workflow, but they will have different Indexes.
Registering your action with a dynamic rule
By default, all input fields are visible in the user interface. Dynamic rules allow you to control this behavior by defining an expression that determines the visibility of an input field. In the action registration, the rule is defined for each input field that must be shown conditionally.
In order to add dynamic rules to an input field in your action, you will simply need to add a Properties property of type InputDynamicProperties .
Below is an example on how to achieve this by adding the Properties property to your InputDefinition.
Inputs = new List<InputDefinition>
{
new InputDefinition
{
Label = "All Documents",
ID = "set-field-on-all",
Placeholder = null,
DefaultValue = "true",
UIElementType = "Switch",
BooleanInputSource = new BooleanInputSource
{
TrueValue = "true",
FalseValue = "false",
TrueLabel = "Yes",
FalseLabel = "No"
}
},
new InputDefinition
{
Label = "Saved Search",
ID = "saved-search-artifact-id",
Placeholder = "Select",
UIElementType = "Dropdown",
ObjectManagerQueryInputSource = new ObjectManagerInputs
{
LabelFieldName = "Name",
ValueFieldName = "ArtifactID",
Guid = "00000000-0000-0000-0000-000000000000",
ArtifactTypeID = 15
},
Properties = new InputDynamicProperties
{
VisibleExpression: "@action.inputs.set-field-on-all.value == 'false'"
}
}
}
InputDynamicProperties
Property | Description | Type | Required |
---|---|---|---|
VisibleExpression | Expression to evaluate field visibility. See Expression Syntax. | String | true |
Visible | bool | false, defaults to false |
Hidden Field example
Visible Field example
In the above code snippet, there are two input fields (All Documents and Saved Search). The Saved Search field will only be visible if the value of the All Documents field is false. The dynamic rule expression always starts with @action.inputs, followed by the ID set-field-on-all of the input field that will be evaluated.
Syntax
Tokens | Description |
---|---|
@ | Expression Start |
. | Property Accessor |
( and ) | Enclose |
OR | Or Logic Operator |
AND | And Logic Operator |
== | Equal Boolean Operator |
!= | Not Equal Boolean Operator |
Syntax example
((@action.inputs.index-id.value == 1234567) AND (@action.inputs.report-id.value == 1234567))
- ( Opening Parenthesis
- ( Left Hand Operand Start
- @ Expression Start - Required to start any expression
- action Reads "action" property from the Global Context
- . Property Accessor
- inputs Reads the "input" property from the Global Context
- . Property Accessor
- index-id Reads the "Input" object of Input ID "index-id" (Dt Search Index)
- . Property Accessor
- value Reads the value of the inputID, selected by the user
- == Equals Sign
- 1234567 Number Literal - Max value from C# long.MaxValue = 9223372036854775807
- ) Left hand operand Closing Parenthesis
- AND And Logic Operator
- ( Right Hand Operand Starting Parenthesis
- Same as Number (3)
- Same as Number (4)
- Same as Number (5)
- Same as Number (6)
- Same as number (7)
- report-id Reads the "Input" object of input ID "report-id"(Search Terms Report)
- Same as Number (5)
- Same as Number (10)
- == Equals Sign
- 7654321 Number literal
- ) Right Hand Operand Closing Parenthesis
- ) Closing Parenthesis
Limitations
- Max Expression String Length
- To Be Defined
- Max Expression Execution Time
- To Be Defined
- Number Min and Max Values
- Min: -9223372036854775807
- Max: 9223372036854775807
Examples
Expression | Description |
---|---|
@(1 == '1') | Evaluates to False. Type check is strict |
Versioning your trigger or action
Eventually you may need to update your trigger or action. Consider that your trigger/action may already be used by a number of Automated Workflows. Changing the signature of your trigger/action may break those workflows. To prevent this, versioning is used to prevent breaking existing workflows. You must increment the version of your trigger/action whenever breaking changes are identified. For example:
- Adding fields: New fields may cause workflows with a previous version to stop working.
- Removing Fields: Removing a field that a workflow uses may impact on its execution.
While trying to update a trigger, the SDK API wil throw an exception with validation messages stating that your payload has breaking changes. The easiest way to deal with these errors is by incrementing the version by 1. Workflows using the previous version of an action or trigger will not immediately use your new action/trigger.
Updating workflows to use the latest version of a trigger/action
Updating workflows to use the latest version of actions and triggers is a manual process at this time. You will need to edit automated workflows to use the latest version of an action or trigger. When viewing, the automated workflow will display the current version of the action it is currently using. When in edit mode, the workflow will request the latest version and fields that are new will be displayed as empty. Fields that are removed, will be removed. You will not be able to save the workflow until all required fields are provided.
Sending a trigger/triggering workflows that use your trigger
This section assumes you read the section on how to instantiate AutomatedWorkflowsManager and obtain its dependencies.
Sending a trigger notifies Relativity and any matched Automated Workflow that expect that trigger. Workflows matched by the trigger sent will start executing.
Scenarios to use the Send Trigger API:
- LongRunningRPC. You must implement a trigger to notify Relativity that your job/operation is complete, complete with errors, failed, or canceled.
- Triggering another set of workflows inside your action. Inside an endpoint call, you may want to notify Relativity of an event. And that event will trigger other workflows listening on it.
Sending a trigger involves you specifying an existing pre-registered trigger, either via the Register Trigger API or via the Register Action API, as shown below:
var sendTrigger = new SendTriggerBody
{
ID = "my-company/on-example-completed",
Inputs = new List<TriggerInput>
{
new TriggerInput
{
ID = "index-id",
Value = "431216"
}
},
Message = "A Message",
State = "complete",
Version = 1,
};
_automatedWorkflowsManager.SendTriggerAsync(workspaceID, sendTrigger);
SendTriggerBody
Property | Description | Type | Required |
---|---|---|---|
ID | ID of the trigger to send. | String | true |
Inputs | Triggers have inputs, and you must specify any required inputs to the API. | TriggerInput[] See TriggerInput. |
false |
Message | You can provide a message that can be used by UI to display information on the completion. | String | false |
State | The state of the trigger. It must match one of the states defined by your Register Trigger/Action API call. Note that in Get Status API you must implement with a LongRunningRPC have a specific set of values that are strict to it. See Response Schema on the LongRunningRPC section. This is not the case with custom triggers. You can define other states than the ones in the TriggerState enum, but ensure the ones used in a LongRunningRPC do not deviate from the enum provided by the SDK. | String | true |
Version | You must decide which version you are notifying Relativity with. You may have multiple versions of a trigger and want only to trigger a specific version. | Integer | true |
TriggerInput
Property | Description | Type | Required |
---|---|---|---|
ID | Name of the input that is entered through the UI. | String | true |
Value | Value of the input that is entered through the UI. | String | true |
Validating your workflow
Before you run a workflow, you can use the ValidateWorkflowAsync method to see if your workflow has any validation errors. This will allow developers to fix any potential errors before running their workflows.
Validate workflow example:
var validationResponse = _automatedWorkflowsManager.ValidateWorkflowAsync(workspaceID, workflowID);
Validate workflow sample response:
// valid workflow
{
Success = true,
Messages = []
}
// invalid workflow
{
Success = false,
Messages = ['error message']
}
Code samples
Register a trigger
var trigger = new RegisterTriggerBody
{
Label = "Example Trigger",
ID = "my-company@example-trigger",
Version = 1,
SelectableAsInitialTrigger = true,
Inputs = new List<InputDefinition>
{
new InputDefinition
{
Label = "Input 1",
Placeholder = "My Input Placeholder",
ID = "input1",
UIElementType = UIElementType.Dropdown,
InputSources = new List<InputSource>
{
new InputSource
{
Label = "Option 1",
Value = "option1"
},
new InputSource
{
Label = "Option 2",
Value = "option2"
},
}
}
}
};
await _automatedWorkflowsManager.RegisterTriggerAsync(workspaceID, trigger);
Register an action
var action = new RegisterActionBody
{
Label = "Example Action",
ID = "relativity/example-action",
Group = "Search",
Inputs = new List<InputDefinition>
{
new InputDefinition
{
Label = "Index",
ID = "index-id",
UIElementType = UIElementType.Dropdown,
ObjectManagerQueryInputSource = new ObjectManagerInputs
{
ValueFieldName = "ArtifactID",
LabelFieldName = "Name",
ArtifactTypeID = 29,
Condition = "\'Type\' LIKE \'dtSearch\'"
}
}
},
Steps = new List<ActionStepDefinition>
{
new ActionStepDefinition
{
Type = ActionStepType.LongRunningRPC,
Http = new ActionStepHttp
{
Url = "@{variables('RelativityInstanceURL')}/MyCompany.REST/API/Compute/workspace/@{variables('WorkspaceID')}/indexes/@{variables('index-id')}/build",
Method = ActionHttpMethodType.Post,
Body = "{ \"isActive\": true }",
Headers = new Dictionary <string, string>()
{
{
"x-csrf-header",
"-"
},
{
"content-type",
"application/json"
}
}
},
StatusCheck = new StatusCheckDefinition
{
Http = new ActionStepHttp
{
Url = "@{variables('RelativityInstanceURL')}/MyCompany.REST/API/indexes/v1/workspaces/@{variables('WorkspaceID')}/indexes/@{variables('index-id')}/status",
Method = ActionHttpMethodType.Get,
Body = "",
Headers = new Dictionary <string, string>()
{
{
"x-csrf-header",
"-"
},
{
"content-type",
"application/json"
}
}
},
},
Triggers = new List<ActionStepCompletionTrigger>
{
new ActionStepCompletionTrigger
{
Label = "dtSearch Index Build Completed",
ID = "relativity@on-dt-search-build-completed",
SelectableAsInitialTrigger = true,
TriggerInputs = new List<ActionInputRef>
{
new ActionInputRef
{
ActionInputID = "index-id"
}
}
}
}
}
};
_automatedWorkflowsManager.RegisterActionAsync(workspaceID, action);
Send a trigger
var sendTrigger = new SendTriggerBody
{
ID = "my-company/on-example-completed",
Inputs = new List<TriggerInput>
{
new TriggerInput
{
ID = "index-id",
Value = "431216"
}
},
Message = "A Message",
State = "complete",
Version = 1,
};
_automatedWorkflowsManager.SendTriggerAsync(workspaceID, "my-company/on-example-completed", sendTrigger);
Deleting a trigger
Deleting a trigger is straight forward and only requires a workspaceId, triggerID and the version of the trigger.
await _automatedWorkflowsManager.DeleteTriggerAsync(workspaceId, triggerID, version);
Deleting an action
Deleting an action is straight forward and only requires a workspaceId, actionID and the version of the action.
await _automatedWorkflowsManager.DeleteActionAsync(workspaceId, actionID, version);