WHILE condition
Repeats the ops nested underneath it for as long as a set of comparisons against the stash holds true. The comparisons are checked before every pass, including the first, so a condition that is already false means the body never runs at all.
Use it to poll: wait for a remote job to report a finished state, drain a queue of items one at a time, keep asking for a lock until you get it. When you want the body to run at least once before the first check, use DO-WHILE condition instead. When you want a single pass with no repetition, use IF var condition THEN, which has the identical condition builder.
Fields¶
When sits at the top. Under it, each comparison gets its own box titled Condition holding the
four fields below plus its own Remove button. A new op opens with one Condition box already
there and When blank.
When¶
Decides how the condition rows combine into the single answer that drives the loop.
| When | The loop keeps going while |
|---|---|
All |
every row is true |
Any |
at least one row is true |
None |
no row is true |
None is the opposite of Any, not the opposite of All. One true row ends the loop.
Leave When unset and the answer is false whatever the rows say, so the body never runs and the
rule walks straight past. Nothing warns you about it. Pick a value even when you only have one row.
Rows stop being evaluated as soon as one of them settles the answer, but they are not necessarily read in the order the form lists them. Do not write a row that only makes sense after an earlier row has passed. Put that guard in an enclosing IF var condition THEN instead.
Stash Variable¶
The left side of the comparison, and the value that has to change for the loop to end. Write the
name on its own, with no ${} wrapper: last_state, not ${last_state}.
Dots walk into nested data, so topic.status reads a field out of a loaded topic. List positions
take brackets: bar_items[0].name, not bar_items.0.name. A path that does not resolve produces an
empty value rather than an error, which is why IS EMPTY and NOT EMPTY are the safe way to ask
whether something arrived at all.
The form will not let you save a row with this field blank.
Operator¶
Picks the comparison, listed in full below. Your choice reshapes the rest of the row.
IS TRUE, IS FALSE, IS EMPTY and NOT EMPTY hide the value field because they only look at the
left side, and the checkboxes appear only for the operators that honour them.
Value¶
The right side of the comparison. This field does expand ${var} placeholders against the stash, so
${expected_state} and release/${version} both work. The asymmetry trips people up: the left
field is a path, the right field is a template.
Expansion happens again on every pass, not once when the loop starts, so a placeholder here tracks the stash the same way the left side does. Either side can be the one the body moves.
For IN and NOT IN, type a comma-separated list. For LIKE (regular expression) and
NOT LIKE (regular expression), type a regular expression.
Options¶
Two checkboxes, shown only for the operators that accept them.
Ignore case lowercases both sides before comparing. On LIKE and NOT LIKE it makes the pattern
itself case-insensitive.
Numeric switches from text ordering to number ordering. As text, 9 sorts after 10 and 007 is
not 7, so tick it for counters, attempt numbers, sizes, anything you are counting down to zero.
Add condition and Remove¶
Add condition appends another row. Remove deletes the row it sits in. Every row answers to the
same When setting; rows have no weight and no nesting of their own. For mixed and/or logic, nest a
second loop or move the test into IF condition THEN.
Operators¶
| Operator | True when | Value field | Options |
|---|---|---|---|
IS TRUE |
the variable holds a non-empty, non-zero value; for a list, any element does | hidden | none |
IS FALSE |
nothing in the variable is truthy | hidden | none |
IS EMPTY |
the variable is unset, an empty string, an empty list or an empty hash | hidden | none |
NOT EMPTY |
the variable holds something | hidden | none |
EQUALS = |
the two sides match exactly | text | case, numeric |
NOT EQUALS != |
the sides differ | text | case, numeric |
GREATER THAN >, GREATER THAN OR EQUALS TO >=, LESS THAN <, LESS THAN OR EQUALS TO <= |
the ordering holds | text | case, numeric |
LIKE (regular expression) |
the expression matches somewhere in the value | regex | case |
NOT LIKE (regular expression) |
the expression does not match | regex | case |
IN |
the variable equals one of the comma-separated entries | list | case |
NOT IN |
the variable matches none of them | list | case |
HAS |
the variable is a list or hash and the value is one of its entries | text | case |
NOT HAS |
that list does not contain the value | text | case |
IN and HAS get swapped constantly. IN asks whether one stash value appears in a list you typed
into the form. HAS asks whether a value you typed appears in a list sitting in the stash. Draining
a list of pending items is HAS or NOT EMPTY, never IN.
IS EMPTY on a list is true as soon as one element is empty, not only when the whole list is.
Polling a list that can contain a blank entry ends the loop earlier than you meant. Compare a count
instead when that is possible.
An operator the server does not recognise stops the rule with an error instead of evaluating false. The form can only produce the operators above, so this reaches you through a hand-edited rule.
Ending the loop¶
Nothing here advances anything on your behalf. The body has to change what the rows read, and it has to change it in the stash the loop can see. An op that writes inside STASH LOCAL is invisible to the next check, and the loop spins forever.
There is no iteration cap and no default time limit. A loop whose body never moves the tested value runs until the job is cancelled. Two things bound it:
Timeouton the op's Options tab. It arms a clock before the first check and aborts with a timeout error when the seconds run out. The clock covers the whole loop, not a single pass, so a poll expected to take ten passes of five seconds needs a value above fifty, not above five.- A counter of your own. Increment a stash variable in the body and add a second row that stops the loop past a limit.
Timeout is weaker than it looks here. Expanding a ${} placeholder anywhere disarms that clock as
a side effect, and every service op expands its own configuration before it runs, so a body with any
service op in it loses the timeout on the first pass. The counter row is what actually bounds a loop
that does real work.
A nested op that fails ends the loop and the job. WHILE does not catch errors. Wrap the body in TRY statement with CATCH statement if a failed pass should be survivable.
The loop writes nothing of its own into the stash, and nothing in the job log marks a pass, so a
long poll shows only whatever the body logs. Add LOG Message in the body
when you want to see progress. Return Key on the Options tab has no effect on this op.
Combining with other ops¶
Pair the loop with something that refreshes the value it tests. A service that reloads a record, a Server CODE block that recomputes it, or SET VAR after a check.
Sleep for a number of seconds inside the body keeps a polling loop from hammering whatever it is asking. Without it the loop runs flat out.
Use PUSH VAR to build a list and a NOT EMPTY row to drain it, or skip
the loop entirely and use FOREACH CI when you already have the whole
list up front.
RETRY is the better choice when you are repeating because something failed, rather than because a value has not changed yet.
Examples¶
Poll a remote deployment until it reaches a settled state, with a guard against the states that mean it will never settle.
When: All
last_state NOT EQUALS READY
last_state NOT EQUALS ERROR
last_state NOT EQUALS CANCELED
-> Sleep for a number of seconds 10
-> Server CODE refresh last_state
Keep going while a piece of work is still outstanding, with the body removing one item per pass.
When: Any
pending_items NOT EMPTY
Count down a budget of passes, using a numeric comparison so 10 sorts above 9.
When: All
attempts_left GREATER THAN 0 (Numeric)