How do I create a task definition?

Providing you are a LUSID user with sufficient privileges, you can create a task definition to model a workflow (or part of a complex workflow). A task definition works as a template, from which one or more tasks can be created, each containing its own values for the fields you define in the task definition.

Task definitions are fully customisable; you can define states, fields, triggers and guards in a task definition to create a simple workflow, or enhance and fully automate workflows by attaching workers, actions, child tasks and notifications.

Note that, once created, it is possible to update a task definition without affecting existing tasks.

Using the Workflow REST API

Currently, you can create one task definition per API call.

  1. Obtain an API access token.

  2. Call the CreateTaskDefinition API, passing in your API access token and:

    • A scope and code that together uniquely identify the task definition.

    • A displayName and optional description for the task definition.

    • The states a task created from the task definition can exist in.

    • A fieldSchema defining data fields. Note this is an optional parameter, but if used, each field must be given:

      • A name.

      • A data value type for the field. Available data types are String, Decimal, DateTime, Boolean.

      • Optionally, any readOnlyStates. This allows you to specify the field on a task is read-only in when in the specified states. Available types are InitialState, AllStates, TerminalState, SelectedStates.

    • An initialState the task should exist in when first triggered and, optionally, any fields which must contain a value for the task to successfully reach its initial state.

    • Any triggers for prompting state transitions. Note this is an optional parameter, but if used, each trigger must contain the following:

      • A name for the trigger.

      • The type of trigger - for now only External triggers are available.

    • All possible state transitions which can occur for the task. This defines if and how the task can move from one state to another. Note this is an optional parameter, but if used, within each transition you must specify the following:

      • A fromState defining the state the task must begin the transition in.

      • A toState defining the state the task should end the transition in.

      • The trigger that prompts the state transition to occur.

      • Optionally, a guard condition which must be met for the state transition to succeed. Read more about using guards.

      • Optionally, the name of an action to be taken on successful completion of a state transition, for example invoke a worker.

    • Definitions for any actions which are to be used within state transitions. Note this is an optional parameter, but if used, within each action you must specify the following:

      • A name to uniquely identify the action.

      • Optionally, a value for runAsUserId to perform the action on behalf of a service user. Read more about this.

      • The type of action from the following options:

        • RunWorker - this is the type used in the example. When using this action type, the following must also be specified:

          • The scope and code of the worker to kick off. See how to create a worker.

          • The values to pass into the worker input parameters. Here you must specify the name of a worker input parameter and either:

            • Use the MapFrom field to pass an input value from the task (defined in the task definition fieldSchema) as the worker input parameter.

            • Use the SetTo field to specify a value to set the worker input parameter to.

            Note you must only specify a non-null value for either MapFrom or SetTo - the other must be set to null.

          • Any triggers which should be activated depending on the worker's status in workerStatusTriggers.

        • CreateChildTasks

        • TriggerParentTask

        Note each action type requires a different set of parameters; you can view the schema for each type using the API reference. Read more on using actions.

Note: To avoid infinite loops occurring, there must always be at least one terminal state within the task definition, that is, a state from which no onward transitions are possible. 

The following example creates a task definition which models an operational control for approving new or updated portfolio data:

curl -X POST 'https://<your-domain>.lusid.com/workflow/api/taskdefinitions' 
  -H 'Authorization: Bearer <your-api-access-token>'
  -H 'Content-Type: application/json-patch+json'
  -d '{
  "id": {
    "scope": "approvals",
    "code": "dataApproval"
  },
  "displayName": "Data Approval",
  "description": "A task definition for a data approval task",
  "states": [
    { "name": "Pending" },
    { "name": "Denied" },
    { "name": "NotRequired" },
    { "name": "Approved" },
    { "name": "Done" },
    { "name": "Error" }
  ],
  "fieldSchema": [
    {
      "name": "assignee",
      "type": "String"
    },
    {
      "name": "dataToApproveDescription",
      "type": "String",
      "readOnlyStates": { "stateType": "TerminalState" }
    },
    {
      "name": "portfolioScope",
      "type": "String"
    },
    {
      "name": "portfolioCode",
      "type": "String"
    },
    {
      "name": "displayName",
      "type": "String"
    },
    {
      "name": "createdDate",
      "type": "DateTime"
    },
    {
      "name": "baseCurrency",
      "type": "String"
    },
    {
      "name": "reasonForDecision",
      "type": "String"
    }
  ],
  "initialState": {
    "name": "Pending",
    "requiredFields": [
      "dataToApproveDescription"
    ],
  },
  "triggers": [
    {
      "name": "grant",
      "trigger": {
        "type": "External"
      }
    },
    {
      "name": "workerComplete",
      "trigger": {
        "type": "External"
      }
    },
    {
      "name": "workerError",
      "trigger": {
        "type": "External"
      }
    },
    {
      "name": "cancel",
      "trigger": {
        "type": "External"
      }
    },
    {
      "name": "reject",
      "trigger": {
        "type": "External"
      }
    }
  ],
  "transitions": [
   
    {
      "fromState": "Pending",
      "toState": "NotRequired",
      "trigger": "cancel",
      "guard": "fields['assignee'] exists"
    }, 
    {
      "fromState": "Pending",
      "toState": "Denied",
      "trigger": "reject",
      "guard": "fields['reasonForDecision'] exists"
    },
  
    {
      "fromState": "Pending",
      "toState": "Approved",
      "trigger": "grant",
      "guard": "fields['portfolioScope'] exists and fields['portfolioCode'] exists",
      "action": "StartWorker"
    },
    {
      "fromState": "Approved",
      "toState": "Done",
      "trigger": "workerComplete"
    },
    {
      "fromState": "Approved",
      "toState": "Error",
      "trigger": "workerError"
    }
  ],
  "actions": [
  {
    "name": "StartWorker",
    "actionDetails" : {
      "type": "RunWorker",
      "workerId": {
        "scope": "Finbourne-Examples",
        "code": "UpsertPortfolio"
      },
      "workerStatusTriggers": {
        "failedToStart": "workerError",
        "failedToComplete": "workerError",
        "completedWithResults": "workerComplete",
        "completedNoResults": "workerError"
      },
      "workerParameters": {
       "CreatedDate": {
         "MapFrom": "createdDate",
         "SetTo": null
       },
       "Currency": {
         "MapFrom": "baseCurrency",
         "SetTo": null
       },
       "EntityCode": {
         "MapFrom": "portfolioCode",
         "SetTo": null
       },
       "EntityScope": {
         "MapFrom": "portfolioScope",
         "SetTo": null
       },
       "PortfolioName": {
         "MapFrom": "displayName",
         "SetTo": null
       }
      }
    }
  }]
}'

Part of a response is as follows:

 {
  "id": {
    "scope": "approvals",
    "code": "dataApproval"
  },
  "version": {
    "asAtCreated": "2023-05-15T11:08:15.1522320+00:00",
    "userIdCreated": "00ujk6twb4jDcHGjN2p8",
    "asAtModified": "2023-05-15T11:08:15.1522320+00:00",
    "userIdModified": "00ujk6twb4jDcHGjN2p8",
    "asAtVersionNumber": 1
  },
  "displayName": "Data Approval",
  "description": "A task definition for a data approval task",
  ...

Once you have created a task definition, you can create a task each time you want to set the workflow in motion.

Updating a task definition

To update a task definition, you can call the UpdateTaskDefinition API, passing in the scope and code and, importantly, a full task definition. Please note: 

  • When updating a task definition, any field values not provided in the request are set to null.

  • Only new tasks created from the updated task definition are affected by updates. Existing tasks operate using the task definition asAtVersionNumber that existed when the task was created.

Using the LUSID web app

<Coming soon>