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.
- Method 1: Using the Workflow REST API
- Method 2: Using the LUSID web app (coming soon)
Using the Workflow REST API
Currently, you can create one task definition per API call.
- Obtain an API access token.
- Call the CreateTaskDefinition API, passing in your API access token and:
- A
scope
andcode
that together uniquely identify the task definition. - A
displayName
and optionaldescription
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 areString
,Decimal
,DateTime
,Boolean
.
- A
- 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 onlyExternal
triggers are available.
- A
- 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.
- A
- 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
andcode
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 definitionfieldSchema
) as the worker input parameter. - Use the
SetTo
field to specify a value to set the worker input parameter to.
MapFrom
orSetTo
- the other must be set tonull
. - Use the
- Any triggers which should be activated depending on the worker's status in
workerStatusTriggers
.
- The
CreateChildTasks
TriggerParentTask
- A
- A
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"
},
{
"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>