Looping within Workflows

Iterate over multiple items within a workflow

Usage

Loops are used when you need to repeat a set of the same nodes a specific number of times.

NOTE - task loops do not have the concept of a for loop or do while loop. For that type of functionality consider using a recursive routine. Instead, iterations of a loop happen, essentially, all at once.

Loops are comprised of two different nodes, Loop Head and Loop Tail

Loop Head

The Loop Head node controls how many times the loop is processed and what data is used with the XML or JSON parameters. The Loop Head is also referred to as Loop Begin in some documentation.

Example Loop Head node.

LoopHead1

Set the XML document in the Data Source parameter, and the XSL in the Loop Path parameter.

Variable Name this optional parameter was used to identify a variable from the xml / json to be used in each loop iteration. It's common now to use the output of the loop head node (<%=@results['Loop Head']['Value']%>) from the parameter list.

XML Example:

XML (Data Source)

     <user>
       <id>han solo</id>
       <id>darth vader</id>
       <id>leia organa</id>
     </user>

XSL (Loop Path)

//user/id

In this example the loop would process three times passing the value, han solo, darth vader, and then leia organa.

JSON Example:

["han solo","darth vader","leia organa"]

Loop path: $.[*]

Note: This online path evaluator can help you find the correct path you are looking for.

Loop Tail

The Loop Tail controls where the end of the loop is, and when the loop is exited. The Loop tail is also referred to as Loop End in some documentation.

Settings in the Loop Tail are the same as a Join node.

LoopTail1

Type selection determines when the Loop Tail node completes and progresses farther through the tree.

  • All is every loop instance processes completely through to the Loop Tail
  • Any is the first instance that completes the loop. No other instance will move forward.
  • Some requires you to determine how many loop instances must complete before the Loop Tail completes.

Connecting

Connecting nodes in a loop is similar to normal connecting. Inside the loop (between the Loop Head and Loop Tail) nodes connect normally. The difference for a loop, is that you must also connect the Loop Head directly to the Loop Tail node. This connection tells the engine what nodes are between the Loop Head and Loop Tail and are then included in an individual loop instance.

Example

Below is a very simple loop.

LoopExample

In this loop you can see that the Loop Head is connected directly to the Loop Tail, along with the node(s) that run as part of the loop.

Run Example

Example run using the hard coded values from above.

LoopRun

The important thing to notice on this run is that after the tasks for the initial Loop Head and Loop Tail (lines three and four), there are individual Loop head Tasks and Triggers for each instance the loop is run. The tasks are differentiated by the index number of their execution (for example, the first is [0]).

The details for one of the Echo nodes is shown to see its output. The parameter for the Echo node is <%=@results['Loop Head']['Value']%>

Using Loop Values

Accessing Node Results From Within the Loop

In the Task Tree, results from a node can be accessed and used by another node or a connector.  If both nodes are within a loop, the results of the source node will return a value from only within the context of that same loop iteration. 

Content of Echo Node ParameterResult
<%=@results['Get Email'].inspect%>{“output”=>”[email protected]”}
<%=@results['Get Email']['output']%>[email protected]

Accessing Node Results From Outside the Loop

When accessing the results from outside to the loop the, the same code that was used inside the loop, the source node returns a more complex Hashof values.  The hash now contains the values from each loop iteration.

Content of Echo Node ParameterResult
<%=@results['Get Email'].inspect%>{0=>{"output"=>"[email protected]"}, 1=>{"output"=>""}, 2=>{"output"=>"[email protected]"}, 3=>{"output"=>"[email protected]"}}
<%=@results['Get Email']['output']%>empty

It is also important to note that if you have a routine inside the loop, it's output isn't quite the same as a handler's. Outside the loop, a routine's results would also seem empty without the inspect, but with the inspect they look as follows:

Content of Echo Node ParameterResult
<%=@results['Individual Approval Routine'].inspect%>{0=>{"Run Id"=>16430, "Source Id"=>nil, "Tree Id"=>278, "Trigger Id"=>33544}, 1=>{"Run Id"=>16431, "Source Id"=>nil, "Tree Id"=>278, "Trigger Id"=>33545, "Approval Response"=>"Approve", "Denial Comments"=>nil, "Approver Login ID"=>"aramey"}, 2=>{"Run Id"=>16432, "Source Id"=>nil, "Tree Id"=>278, "Trigger Id"=>33546}}

So they must be access slightly differently (as referenced below).

Using the Results

In the examples from above the only immediately usable results are returned from <%=@results['Get Email']['output']%> and only when accessing a result from within the loop.  When used outside the loop no value is returned.

Additional efforts are needed to access the results from outside the loop but there is also increased usefulness. Knowing that it returns a hash we can use Ruby methods to evaluate it and manipulate it as we would any other Ruby hash.  Now other Ruby Methods can be used to manipulate the result set.

All nodes return a string.  Therefore, an Array will be converted to a string as the result of a node.  The inspect method, used frequently below, converts the array into a string representation which is identifiable as an array before the node naturally converts it to a string.  However the quotes, less than and greater than are encoded when viewed in the Task Management Console.  In order to better display the results, the examples have decoded the symbols back to the orignal values.

Ruby MethodContent of Echo Node ParameterDescriptionResult
inspect<%=
@results['Get Email'].inspect
%>
Using the inspect method revels that @results['Get Email'] returns a hash which combines all values for the Get Email node from within the loop.{0=>{"output"=>"[email protected]"}, 1=>{"output"=>""}, 2=>{"output"=>"[email protected]"}, 3=>{"output"=>"[email protected]"}}
collect<%=
@results['Get Email'].collect{ |key,values|
values['output']
}.inspect
%>
The collect method invokes block once for each element of self. Creates a new array containing the values returned by the block.  The contents of the block is "values['output']" and there for returns the "output" value for each element.["[email protected]", "[email protected]", "", "[email protected]"]
collect with out inspect<%=
@results['Get Email'].collect{|key,values|
values['output']
}
%>
Without the inspect to create a string representation of the array the values in the array are combined into one value w/o and separator.[email protected]@[email protected]
compact<%=
@results['Get Email'].collect{|key,values|
if (values.has_key?('output'))
 values['output']
 end
}.compact.inspect
%>
By using an if statement in the block to ignore the empty array objects and then using the compact method any empty results are removed from the results.["[email protected]", "[email protected]", "[email protected]"]
join<%=
@results['Get Email'].collect{|key,values|
if (values.has_key?('output'))
 values['output']
 end
}.compact.join('; ')
%>
The inspect's results are not useful in a Task tree to other node.  By using the join method a list of all results joined with a value of "; ".[email protected]; [email protected]; [email protected]
first<%=
@results['Get Email'].collect{|key,values|
if (values.has_key?('output'))
 values['output']
 end
}.compact.first
%>
Returns the first value in the hash.  If a results come from a deferred node this can be used to get the value of the node which was first completed.[email protected]
include?<%=
@results['Get Email'].collect{|key,values|
if (values.has_key?('output'))
 values['output']
 end
}.compact.include?('[email protected]')
%>
include? can be used to evaluate is a specific value is included in the results.true
count<%=
@results['Get Email'].collect{|key,values|
if (values.has_key?('output'))
 values['output']
 end
}.compact.count('[email protected]')
%>
The count method can be used to count the amount of times which a specific values exists in an array1
length<%=
@results['Get Email'].collect{|key,values|
if (values.has_key?('output'))
 values['output']
 end
}.compact.length
%>
The length method can be used display the length of the array.3