LetsFlow
Search…
Under one condition
Use conditions for actions and transitions.
The previous tutorial had our two actors introducing themselves. In that scenario we defined an action and a state for each actor. When scenarios becomes more complex and the number of actors increase, having to define actions and states per actor quickly adds up.
Create a new subdirectory named condition .
Since both actors need to perform the same action, we can reduce the scenario by creating a single introduce action that can be performed by both actors.
1
Feature: Two actors meet at a conference and exchange information.
2
3
Background:
4
Given a chain is created by "Joe"
5
Given "Joe" creates the "main" process using the "condition" scenario
6
And "Joe" is the "initiator" actor of the "main" process
7
And "Jane" is the "recipient" actor of the "main" process
8
9
Scenario:
10
When "Joe" runs the "introduce" action of the "main" process with:
11
| name | Joe Smith |
12
| organization | LTO Network |
13
Then the "initiator" actor of the "main" process has:
14
| name | Joe Smith |
15
| organization | LTO Network |
16
And the "main" process is in the "wait_on_recipient" state
17
18
When "Jane" runs the "introduce" action of the "main" process with:
19
| name | Jane Wong |
20
| organization | Acme Inc |
21
Then the "recipient" actor of the "main" process has:
22
| name | Jane Wong |
23
| organization | Acme Inc |
24
And the "main" process is completed
Copied!

Multiple actors for an action

Allowing 2 actors to perform an action can be done by replacing the actor with a list of actors.
The update instruction needs to update the actor that performed the action. This actor can be selected using current.actor.
YAML
JSON
1
actions:
2
introduce:
3
actors:
4
- initiator
5
- recipient
6
update: current.actor
7
8
states:
9
initial:
10
action: introduce
11
transition: wait_on_recipient
12
wait_on_recipient:
13
action: introduce
14
transition: :success
Copied!
1
"actions": {
2
"introduce": {
3
"actors": [
4
"initiator",
5
"recipient"
6
],
7
"update": "current.actor"
8
}
9
},
10
"states": {
11
"initial": {
12
"action": "introduce",
13
"transition": "wait_on_recipient"
14
},
15
"wait_on_recipient": {
16
"action": "introduce",
17
"transition": ":success"
18
}
19
}
Copied!
This doesn't quite cut it though, because the initiator could now introduce himself twice to complete the process. That's not what we want.

Conditional action

We'll add a second Scenario section to make sure a second introduction is ignored.
1
Feature: Two actors meet at a conference and exchange information.
2
3
Background:
4
Given a chain is created by "Joe"
5
Given "Joe" creates the "main" process using the "handshake" scenario
6
And "Joe" is the "initiator" actor of the "main" process
7
And "Jane" is the "recipient" actor of the "main" process
8
9
Scenario:
10
...
11
12
Scenario:
13
When "Joe" runs the "introduce" action of the "main" process with:
14
| name | Joe Smith |
15
| organization | LTO Network |
16
Then the "initiator" actor of the "main" process has:
17
| name | Joe Smith |
18
| organization | LTO Network |
19
And the "main" process is in the "wait_on_recipient" state
20
21
When "Joe" runs the "introduce" action of the "main" process with:
22
| name | Joe Smith |
23
| organization | LTO Network |
24
Then the "main" process is in the "wait_on_recipient" state
Copied!
Running this test will fail, because the process is completed and not in the wait on recipient state.
We'll change the scenario, so an actor can only introduce himself/herself if the actor's name is unknown.
We can add a condition to an action. The condition is always interpreted as boolean. With data instructions like <eval>, the condition can be determined based on process data.
YAML
JSON
1
actions:
2
introduce:
3
actors:
4
- initiator
5
- recipient
6
condition: !eval current.actor.name == null
7
update: current.actor
8
9
states:
10
initial:
11
action: introduce
12
transition: wait_on_recipient
13
wait_on_recipient:
14
action: introduce
15
transition: :success
Copied!
1
"actions": {
2
"introduce": {
3
"actors": [
4
"initiator",
5
"recipient"
6
],
7
"condition": {
8
"<eval>": "current.actor.name == null"
9
},
10
"update": "current.actor"
11
}
12
},
13
"states": {
14
"initial": {
15
"action": "introduce",
16
"transition": "wait_on_recipient"
17
},
18
"wait_on_recipient": {
19
"action": "introduce",
20
"transition": ":success"
21
}
22
}
Copied!
Data instructions can only use data that's available in the process, as they must be deterministic for all parties to have the same result.
current.actor is not available for the properties like the title (action, response and state) and state instructions. The actor isn't determined yet when these are calculated.

Transition validation

An ill intended Joe could still get around this by not specifying his name when he introduces himself. Since the name isn't set he can perform introduce while in the wait on recipient state.
We don't want the process to fail if Joe doesn't give his name. Instead the process will stay in the initial state until he does.
1
Feature: Two actors meet at a conference and exchange information.
2
3
Background:
4
Given a chain is created by "Joe"
5
Given "Joe" creates the "main" process using the "handshake" scenario
6
And "Joe" is the "initiator" actor of the "main" process
7
And "Jane" is the "recipient" actor of the "main" process
8
9
Scenario:
10
...
11
12
Scenario:
13
...
14
15
Scenario:
16
When "Joe" runs the "introduce" action of the "main" process with:
17
| organization | LTO Network |
18
Then the "initiator" actor of the "main" process has:
19
| name | |
20
| organization | LTO Network |
21
And the "main" process is in the "initial" state
22
23
When "Joe" runs the "introduce" action of the "main" process with:
24
| nonsense | chatter |
25
Then the "main" process is in the "initial" state
26
27
When "Joe" runs the "introduce" action of the "main" process
28
Then the "main" process is in the "initial" state
29
30
When "Joe" runs the "introduce" action of the "main" process with:
31
| name | Joe Smith |
32
Then the "initiator" actor of the "main" process has:
33
| name | Joe Smith |
34
| organization | LTO Network |
35
And the "main" process is in the "wait_on_recipient" state
Copied!
To achieve this, add a condition to the transition in the initial state. Transition conditions are evaluated after the action is performed and updated instructions are processed.
Jane is also be required to give her name.
YAML
JSON
1
actions:
2
introduce:
3
actors:
4
- initiator
5
- recipient
6
condition: !eval current.actor.name == null
7
update: current.actor
8
9
states:
10
initial:
11
action: introduce
12
transitions:
13
- transition: wait_on_recipient
14
condition: !eval actors.initiator.name != null
15
wait_on_recipient:
16
action: introduce
17
transitions:
18
- transition: :success
19
condition: !eval actors.recipient.name != null
Copied!
1
"actions": {
2
"introduce": {
3
"actors": [
4
"initiator",
5
"recipient"
6
],
7
"condition": {
8
"<eval>": "current.actor.name == null"
9
},
10
"update": "current.actor"
11
}
12
},
13
"states": {
14
"initial": {
15
"action": "introduce",
16
"transitions": [
17
{
18
"transition": "wait_on_recipient",
19
"condition": {
20
"<eval>": "actors.initiator.name != null"
21
}
22
}
23
]
24
},
25
"wait_on_recipient": {
26
"action": "introduce",
27
"transitions": [
28
{
29
"transition": ":success",
30
"condition": {
31
"<eval>": "actors.recipient.name != null"
32
}
33
}
34
]
35
}
36
}
Copied!

Any order will do

According to the scenario, Joe must first give his name and then Jane should give her name. But the order isn't really important. Both actors should introduce themselves with their name. When that's done, the process is complete.
1
Feature: Two actors meet at a conference and exchange information.
2
3
Background:
4
Given a chain is created by "Joe"
5
Given "Joe" creates the "main" process using the "handshake" scenario
6
And "Joe" is the "initiator" actor of the "main" process
7
And "Jane" is the "recipient" actor of the "main" process
8
9
Scenario:
10
When "Jane" runs the "introduce" action of the "main" process with:
11
| name | Jane Wong |
12
| organization | Acme Inc |
13
Then the "recipient" actor of the "main" process has:
14
| name | Jane Wong |
15
| organization | Acme Inc |
16
And the "main" process is in the "initial" state
17
18
When "Joe" runs the "introduce" action of the "main" process with:
19
| name | Joe Smith |
20
| organization | LTO Network |
21
Then the "initiator" actor of the "main" process has:
22
| name | Joe Smith |
23
| organization | LTO Network |
24
And the "main" process is completed
Copied!
The introduce action can already be performed by both actors in the initial state and only by actor of whom the name is not know yet. We only need to combine the two states.
YAML
JSON
1
$schema: "https://specs.letsflow.io/v0.2.0/scenario/schema.json#"
2
title: Under one condition
3
4
actors:
5
initiator:
6
$schema: "http://json-schema.org/schema#"
7
title: Initiator
8
type: object
9
properties:
10
name:
11
type: string
12
organization:
13
type: string
14
recipient:
15
$schema: "http://json-schema.org/schema#"
16
title: Recipient
17
type: object
18
properties:
19
name:
20
type: string
21
organization:
22
type: string
23
24
actions:
25
introduce:
26
actors:
27
- initiator
28
- recipient
29
condition: !eval current.actor.name == null
30
update: current.actor
31
32
states:
33
initial:
34
action: introduce
35
transitions:
36
- transition: :success
37
condition: !eval actors.initiator.name != null && actors.recipient.name != null
Copied!
1
{
2
"$schema": "https://specs.letsflow.io/v0.2.0/scenario/schema.json#",
3
"title": "A proper introduction",
4
"actors": {
5
"initiator": {
6
"$schema": "http://json-schema.org/schema#",
7
"title": "Initiator",
8
"type": "object",
9
"properties": {
10
"name": {
11
"type": "string"
12
},
13
"organization": {
14
"type": "string"
15
}
16
}
17
},
18
"recipient": {
19
"$schema": "http://json-schema.org/schema#",
20
"title": "Recipient",
21
"type": "object",
22
"properties": {
23
"name": {
24
"type": "string"
25
},
26
"organization": {
27
"type": "string"
28
}
29
}
30
}
31
},
32
"actions": {
33
"introduce": {
34
"actors": [
35
"initiator",
36
"recipient"
37
],
38
"condition": {
39
"<eval>": "current.actor.name == null"
40
},
41
"update": "current.actor"
42
}
43
},
44
"states": {
45
"initial": {
46
"action": "introduce",
47
"transitions": [
48
{
49
"transition": ":success",
50
"condition": {
51
"<eval>": "actors.initiator.name != null && actors.recipient.name != null"
52
}
53
}
54
]
55
}
56
}
57
}
Copied!

Now you!

In the "A proper introduction" tutorial, you modified the scenario adding steps for asking the e-mail address from the recipient.
In that scenario, we only want to only consider the process to successfully completed if the recipient has given her e-mail address. If the init_iator ends the conversation or if the _recipient doesn't give her e-mail address, the process is cancelled.
Rather than making an separate action or response for the recipient to skip not give the e-mail address, use a condition to check to transition to ":success" or ":cancelled".
  1. 1.
    Copy the introduction scenario and test file you modified as the previous assignment.
  2. 2.
    Edit main.feature, specifying that the process is cancelled if it's completed by the initiator.
  3. 3.
    Add a third Scenario section, copying the second. Change the last action so the reply of recipient on the request doesn't have any data. In this case the process should also be cancelled.
  4. 4.
    Modify the scenario so the new test succeeds.
Last modified 2yr ago