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.

The Relativity.AutomatedWorkflows.SDK contains this API. For compatibility and package installation instructions, see Download the SDKs and NuGet packages.

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:

Copy
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.

Copy
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: 

Copy
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:

Copy
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:

Copy
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

Copy
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:

Copy
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

Copy
[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:

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.

Copy
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:

Copy
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.

Copy
  await _automatedWorkflowsManager.RegisterTriggerAsync(workspaceID, trigger);

Full code snippet to register the trigger:

Copy
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
Note: You do not need to specify the trigger states. They are available by default. Currently, custom states are not supported

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.

Copy
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:

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.

Copy
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:

Copy
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[]

See 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).
Note: For each field type, you can use a built-in property like UIElementType.MultiCheckbox or a hardcoded string like "MultiCheckbox"
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
Note: If ObjectManagerQueryInputSource or BooleanInputSource is defined, then InputSources should not be present in the body.
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
Note: If InputSources or BooleanInputSource is defined, then ObjectManagerQueryInputSource should not be present in the body.
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
Note: If InputSources or ObjectManagerQueryInputSource is defined, then BooleanInputSource should not be present in the body.
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.
Note: You can use the built-in property like ActionStepType.LongRunningRPC or a hard-coded string like "LongRunningRPC".
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:

Copy
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:

  1. First, the action, your HTTP call, is invoked and we receive a success, indicating that the job/operation was started.
  2. Meanwhile, the workflow carries on with other duties and listens to events of completion of the job/operation that was previously started.
  3. 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:

Copy
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.

Copy
{

  "$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:

Copy
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:

Copy
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.

Copy
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.

Copy
{
  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.

Copy
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))
  1. ( Opening Parenthesis
  2. ( Left Hand Operand Start
  3. @ Expression Start - Required to start any expression
  4. action Reads "action" property from the Global Context
  5. . Property Accessor
  6. inputs Reads the "input" property from the Global Context
  7. . Property Accessor
  8. index-id Reads the "Input" object of Input ID "index-id" (Dt Search Index)
  9. . Property Accessor
  10. value Reads the value of the inputID, selected by the user
  11. == Equals Sign
  12. 1234567 Number Literal - Max value from C# long.MaxValue = 9223372036854775807
  13. ) Left hand operand Closing Parenthesis
  14. AND And Logic Operator
  15. ( Right Hand Operand Starting Parenthesis
  16. Same as Number (3)
  17. Same as Number (4)
  18. Same as Number (5)
  19. Same as Number (6)
  20. Same as number (7)
  21. report-id Reads the "Input" object of input ID "report-id"(Search Terms Report)
  22. Same as Number (5)
  23. Same as Number (10)
  24. == Equals Sign
  25. 7654321 Number literal
  26. ) Right Hand Operand Closing Parenthesis
  27. ) Closing Parenthesis
Note: The above expression will only display an input field when both inputs index-id and report-id selected by the user have the respected artifactID's.
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:

Copy
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:

Copy
// valid workflow
{
    Success = true,
    Messages = []
}
Copy
// invalid workflow
{
    Success = false,
    Messages = ['error message']
}

Code samples

Register a trigger

Copy
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"
                },
            }
        }
    }
};
Copy
await _automatedWorkflowsManager.RegisterTriggerAsync(workspaceID, trigger);

Register an action

Copy
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"
                        }
                    }
            }
        }
    }
};
Copy
_automatedWorkflowsManager.RegisterActionAsync(workspaceID, action);

Send a trigger

Copy
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,
};
Copy
_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);			
Note: A trigger cannot be deleted if it is part of an action in a workflow, or the trigger is set as an initial trigger of a workflow.

Deleting an action

Deleting an action is straight forward and only requires a workspaceId, actionID and the version of the action.

Copy
await _automatedWorkflowsManager.DeleteActionAsync(workspaceId, actionID, version);
Note: An action cannot be deleted if it is part of any workflow in the workspace, or any of its trigger is set as an initial trigger of a workflow.