easy_flow 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/builds/easy_flow/canvas.js +1 -1
- data/app/controllers/easy_flow/flows_controller.rb +18 -4
- data/app/javascript/easy_flow/canvas/Canvas.jsx +1 -0
- data/app/javascript/easy_flow/canvas/Inspector.jsx +3 -1
- data/app/javascript/easy_flow/canvas/Records.jsx +8 -2
- data/app/models/easy_flow/all_or_any.rb +33 -0
- data/app/models/easy_flow/canvas.rb +17 -2
- data/app/models/easy_flow/compare.rb +2 -4
- data/app/models/easy_flow/compare_two_values.rb +32 -0
- data/app/models/easy_flow/completed_at.rb +34 -0
- data/app/models/easy_flow/condition.rb +3 -1
- data/app/models/easy_flow/digest.rb +31 -12
- data/app/models/easy_flow/progress/kept.rb +4 -0
- data/app/models/easy_flow/progress/loose.rb +12 -4
- data/app/models/easy_flow/progress.rb +2 -2
- data/app/models/easy_flow/run.rb +27 -6
- data/app/models/easy_flow/runner.rb +11 -11
- data/app/models/easy_flow/settings.rb +5 -2
- data/app/models/easy_flow/step.rb +6 -2
- data/app/models/easy_flow/step_type/declaration.rb +13 -3
- data/app/models/easy_flow/step_type.rb +18 -5
- data/app/models/easy_flow/validator.rb +12 -1
- data/app/models/easy_flow/wait.rb +38 -0
- data/app/views/easy_flow/flows/step.html.erb +6 -0
- data/db/migrate/20261011130000_keep_step_visits_whole_in_easy_flow_runs.rb +23 -0
- data/lib/easy_flow/engine.rb +3 -0
- data/lib/easy_flow/version.rb +1 -1
- data/the_local/agents/easy_flow-develop.md +104 -21
- data/the_local/agents/easy_flow-info.md +24 -11
- data/the_local/agents/easy_flow-install.md +19 -6
- data/the_local/interface.yml +5 -0
- metadata +6 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: easy_flow-develop
|
|
3
|
-
description: Use PROACTIVELY for adding a step type to easy_flow flows (a step that asks the visitor something, takes several answers at once, computes a value from earlier answers, counts how many times an earlier step was answered so a loop can give up after a number of tries, picks the next branch, or holds the visitor until something outside the flow has happened), showing a step differently depending on the visitor's stored run, branching on what a visitor ticked on a checklist, moving a paused run on once what it waits for has happened, refusing a blank or invalid answer to a step with a message, offering an admin a step setting whose options are read from the app's own records, letting an admin pick another flow of the same host in a step's settings, adding a step type that starts another flow and works out which flow and version to run from its own settings, adding a step type that starts another flow chosen from the visitor's run, such as its owner or its answers, offering a visitor only the answers to a step that their earlier answers allow, filling in a step that has only one possible answer without asking it, skipping a step that does not apply to a visitor, letting an admin edit a value stored on one of the app's own records from a step's settings on the canvas, serving a host's flows from the app's own controller and routes, starting a visitor's run on a published version of a flow other than the live one, acting when a visitor finishes a flow, acting on the output a visitor's flow ended with, keeping a flow from being published while a step that starts another flow names a flow or version that cannot run or leads back to the flow it sits in, finding the run of another flow that a Flow step started, the run a waiting run is waiting on, and the run that started it, sending a visitor back to the run that started a flow once that flow ends, handling a run whose starting run was removed with its flow, handling a Flow step that cannot start its flow or whose flow ended with no output, taking one file or several from a visitor at a step, showing a step's recorded answer by a label the step type works out, and reading a run's recorded answers and question labels, including every answer ticked on a checklist and every visit's answer when a flow loops back to a question already asked — MUST BE USED instead of hand-rolling questionnaire steps, checkbox lists, file upload steps, branching logic, answer lists filtered by earlier answers, skipped questions, number comparisons, hard-coded setting options, copying a record's value into a flow and back, answer validation, retry counters, polling or "come back later" pages, flow controllers or answer lookups.
|
|
3
|
+
description: Use PROACTIVELY for adding a step type to easy_flow flows (a step that asks the visitor something, takes several answers at once, computes a value from earlier answers, records several values under names an admin enters in the step's settings, counts how many times an earlier step was answered so a loop can give up after a number of tries, picks the next branch, or holds the visitor until something outside the flow has happened), branching on whether all or any of several comparisons of earlier answers hold, holding a run until a number of minutes after an earlier answer, the next time of day after it, or a date, branching on the hour, weekday or time an earlier step was completed or on the minutes between two completed steps, reading when each step was completed and when a waiting run will move on, showing a step differently depending on the visitor's stored run, branching on what a visitor ticked on a checklist, moving a paused run on once what it waits for has happened, refusing a blank or invalid answer to a step with a message, offering an admin a step setting whose options are read from the app's own records, letting an admin pick another flow of the same host in a step's settings, adding a step type that starts another flow and works out which flow and version to run from its own settings, adding a step type that starts another flow chosen from the visitor's run, such as its owner or its answers, offering a visitor only the answers to a step that their earlier answers allow, filling in a step that has only one possible answer without asking it, skipping a step that does not apply to a visitor, letting an admin edit a value stored on one of the app's own records from a step's settings on the canvas, serving a host's flows from the app's own controller and routes, starting a visitor's run on a published version of a flow other than the live one, acting when a visitor finishes a flow, acting on the output a visitor's flow ended with, telling whether a run ended at an End step or was stopped by a loop that came back with nothing new answered and at which step, keeping a flow from being published while a step that starts another flow names a flow or version that cannot run or leads back to the flow it sits in, finding the run of another flow that a Flow step started, the run a waiting run is waiting on, and the run that started it, sending a visitor back to the run that started a flow once that flow ends, handling a run whose starting run was removed with its flow, handling a Flow step that cannot start its flow or whose flow ended with no output, taking one file or several from a visitor at a step, showing a step's recorded answer by a label the step type works out, and reading a run's recorded answers and question labels, including every answer ticked on a checklist and every visit's answer when a flow loops back to a question already asked — MUST BE USED instead of hand-rolling questionnaire steps, checkbox lists, file upload steps, branching logic, answer lists filtered by earlier answers, skipped questions, number comparisons, comparisons of two earlier answers, and/or conditions over several comparisons, hard-coded setting options, copying a record's value into a flow and back, answer validation, retry counters, timers or scheduled delays, step completion timestamps, polling or "come back later" pages, flow controllers or answer lookups.
|
|
4
4
|
tools: Read, Write, Edit, Grep
|
|
5
5
|
scope: guided flows — versioned documents of steps and the connections between them, drawn on a canvas by an admin and run by a visitor one step at a time, with step types the host registers
|
|
6
6
|
---
|
|
@@ -9,19 +9,20 @@ This local follows the steps below exactly and invents none. Where a step names
|
|
|
9
9
|
|
|
10
10
|
## What easy_flow is
|
|
11
11
|
|
|
12
|
-
A Rails engine for flows an admin draws on a canvas and a visitor runs one step at a time. Each step is an instance of a step type. The engine ships
|
|
12
|
+
A Rails engine for flows an admin draws on a canvas and a visitor runs one step at a time. Each step is an instance of a step type. The engine ships thirteen: start (`:start`), end (`:terminal`, whose one setting is the output an admin writes for a flow that ends there), question (`:question`, a question with a list of answers of which the visitor picks one, which the admin can mark required so a blank answer is refused), checklist (`:checklist`, a question with a list of answers of which the visitor ticks any number, which the admin can mark required so a visitor who ticks nothing is refused), File upload (`:file_upload`, a question the visitor answers by choosing one file from a file upload control labelled with the question text, with an admin setting listing the accepted file types as comma-separated extensions such as `.pdf, .png`, which limits the visitor's file picker to those types and is shown under the control as `Accepts` followed by the list as the admin wrote it, such as `Accepts .pdf, .png`, which the admin can mark required so a visitor who chooses no file is refused, and whose recorded answer is the reference the host's file store returned for the file), five that pick a branch from earlier answers with no code — condition (`:condition`, is or is not a chosen value, where the admin must pick one of the earlier step's outputs and the answer is read from that output when the earlier step recorded several and whole otherwise, so a Condition cannot be completed on a step whose type declares no output), switch (`:switch`, follows the connection labelled with the answer), compare (`:compare`, reads the answer as a number, from the output the admin picks when the earlier step recorded several, and checks it by more than, less than, at least or at most against an amount) and Compare two values (`:compare_two_values`, reads the answers of two earlier steps the admin picks as numbers, reading each from the output the admin picks when that step recorded several, and checks the first by more than, less than, at least, at most or equal to against the second plus an amount the admin may give, a blank amount adding nothing, following its `true` or `false` connection, and following `false` when either answer is missing, is an array or is not a number) and All of or Any of (`:all_or_any`, holds a list of comparisons the admin enters and a `Must hold` setting of `all of` or `any of`, where each comparison asks for the same things as a Compare two values step, a step, its output, a comparison, an amount, and an optional second step and its output, and is checked the same way, a comparison with no second step checking the first answer against the amount alone, following its `true` connection when every comparison holds for `all of` or at least one holds for `any of`, and its `false` connection otherwise, including when it holds no comparisons, a comparison whose answer is missing, is an array or is not a number counting as not holding) — Count (`:count`, which acts without the visitor and records a whole number, how many visits to an earlier step the admin picks the run has recorded, counting only visits that gave the answer the admin picks or every visit when the admin picks none, reading that answer from the output the admin picks when the earlier step recorded several, named on the canvas `Count` followed by the earlier step's id and the answer, such as `Count tests failed`, and recording a new count under its own visit key each time a loop reaches it, so a compare step after it can end a loop once the count reaches an amount), Wait (`:wait`, which holds the run until a time worked out from an earlier step the admin picks as `Counting from`; see "Hold a run until a time"), and Flow (`:flow_step`, names another flow of the same host and the number of one of its versions, with one connection for each different output that version's End steps write, and named on the canvas by that flow's title and the version number, such as `Intake, version 2`). When a stored run stops at a Flow step, a run of the named flow is started on the named version, and the first run waits on the Flow step. When the second run ends with an output, that output is recorded in the first run at the Flow step, and the first run moves on along the connection labelled with that output. When the second run ends with no output, nothing is recorded and the first run stays waiting at the Flow step. On a question and on a checklist the admin can give each answer info, a longer explanation the visitor opens with an info button beside that answer. An admin may connect a step back to an earlier one, so a flow can ask the same question more than once, and each visit's answer is kept. The time each step visit was completed is kept together with its recorded value; see "Read when steps were completed". A File upload step takes a file only in a flow the admin set to save each step. In a flow set to save on finish or to save nothing, a visitor who chooses a file is shown `A file can only be uploaded in a flow that saves its runs.` and cannot go on with it. Before writing a step type to ask for one answer or several, to take a file, to branch on an answer or a number, to branch on how two earlier answers compare, to branch on whether all or any of several comparisons hold, to branch on when an earlier step was completed, to count the visits to an earlier step, or to hold a run until a time, ask the developer whether an admin placing one of the built-in types on the canvas is enough. None of condition, switch, compare, Compare two values or All of or Any of reads a checklist's answer, so branching on what a visitor ticked takes a step type of the app's own that defines `route`. Use this local when the app needs a step type of its own, needs a host's flows on its own pages with its own behaviour when a visitor finishes, needs a visitor's earlier answers to decide which answers a later step offers, or needs to read what a visitor answered. It assumes easy_flow is already installed and a host is declared; if not, hand off to `easy_flow-install` first.
|
|
13
13
|
|
|
14
14
|
## Interface
|
|
15
15
|
|
|
16
16
|
- `EasyFlow::Step` — a module a class includes to declare a step type with class-level words, registered with `.register`.
|
|
17
17
|
- `EasyFlow.step` — declares and registers a step type in one call from a block of the same words, for a type small enough not to need its own class.
|
|
18
18
|
- `labels_answer_by` — a word in a step type's declaration giving the label a run's recorded answer to a step of that type is shown by, on the completion page and from `EasyFlow::QuestionRunner#choice_label`.
|
|
19
|
+
- `outputs_named_by` — a word in a step type's declaration adding one output for each entry an admin enters in one of the type's list settings, named by that entry, so the admin decides the names of the values the step records.
|
|
19
20
|
- `host.allowed_answers=` — set on a host to a lambda given a step and the answers recorded so far, returning the answers that step may take in this run: several narrows the answers shown, one is recorded without showing the step, and none records a blank and skips it.
|
|
20
21
|
- `EasyFlow::FlowsController` — the visitor controller; the app subclasses it to serve one host's flows from its own routes and to change what happens at the start, on each step and at the finish.
|
|
21
22
|
- `hosted_by` — class method on a `FlowsController` subclass naming the host whose flows it serves.
|
|
22
23
|
- `routed_by` — class method on a `FlowsController` subclass naming the prefix of the app's own route names that the controller redirects and links to.
|
|
23
|
-
- `EasyFlow::Run` — the stored record of one visitor's pass through a flow, started with `EasyFlow::Run.start` on the live version or a chosen published one and pinned to that version, moved on past a waiting step with `advance`, read for the output its flow ended with by `output`, and linked to the runs its Flow steps started by `inner_runs`, to the one it is waiting on by `waiting_on`, and to the run that started it by `parent_run`.
|
|
24
|
-
- `EasyFlow::QuestionRunner` — reads a flow document: its steps, the next step for a set of answers, the answers on the path taken, and a question's text and an answer's label.
|
|
24
|
+
- `EasyFlow::Run` — the stored record of one visitor's pass through a flow, started with `EasyFlow::Run.start` on the live version or a chosen published one and pinned to that version, moved on past a waiting step with `advance`, read for the output its flow ended with by `output`, for whether it ended at an End step by `ended_at_end?`, for the step a loop stopped it at by `stopped_on_loop`, for when each step visit was completed by `completed_at` and for the time a Wait step holds it until by `held_until`, and linked to the runs its Flow steps started by `inner_runs`, to the one it is waiting on by `waiting_on`, and to the run that started it by `parent_run`.
|
|
25
|
+
- `EasyFlow::QuestionRunner` — reads a flow document: its steps, the next step for a set of answers and the times their steps were completed, the answers on the path taken, and a question's text and an answer's label.
|
|
25
26
|
|
|
26
27
|
## How to use it
|
|
27
28
|
|
|
@@ -31,9 +32,9 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
31
32
|
- It asks the visitor for input — declare `awaits_input`.
|
|
32
33
|
- It computes a value from earlier answers with no visitor input — define `process`.
|
|
33
34
|
- It only picks which connection to follow — define `route`.
|
|
34
|
-
- It holds the visitor until something outside the flow has happened, such as a payment arriving or a reviewer approving — declare `waits_until`.
|
|
35
|
+
- It holds the visitor until something outside the flow has happened, such as a payment arriving or a reviewer approving — declare `waits_until`. When what it waits for is only a time, ask the developer whether the built-in Wait step is enough first; see "Hold a run until a time".
|
|
35
36
|
A type may both `process` and `route`. A type that does none of the four is passed through without stopping when a visitor reaches it.
|
|
36
|
-
2. Create the class in the app, for example `app/models/flow_steps/rating.rb`. The class name, underscored, is the type's id (`Rating` becomes `:rating`), and that id is stored in every flow that uses it, so it must not change after admins start using the type. It must not be one of the built-in ids `start`, `terminal`, `question`, `checklist`, `file_upload`, `condition`, `switch`, `compare`, `count` or `flow_step`:
|
|
37
|
+
2. Create the class in the app, for example `app/models/flow_steps/rating.rb`. The class name, underscored, is the type's id (`Rating` becomes `:rating`), and that id is stored in every flow that uses it, so it must not change after admins start using the type. It must not be one of the built-in ids `start`, `terminal`, `question`, `checklist`, `file_upload`, `condition`, `switch`, `compare`, `compare_two_values`, `all_or_any`, `count`, `wait` or `flow_step`:
|
|
37
38
|
|
|
38
39
|
```ruby
|
|
39
40
|
module FlowSteps
|
|
@@ -79,10 +80,29 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
79
80
|
5. Use these words to declare the type. Each is called once at class level (or inside the `EasyFlow.step` block):
|
|
80
81
|
- `step_name "<label>"` — the name admins see on the canvas. Defaults to the id.
|
|
81
82
|
- `setting :name, type:, label:, options:, required:, limit:, check:, from:, outputs_of:, kept_on:, attribute:` — a field the admin fills in on the canvas. `type` is one of `:string`, `:integer`, `:float`, `:boolean`, `:select`, `:multi_select`, `:previous_step`, `:from_step`, `:list`, `:flow`, or boot raises `EasyFlow::UnknownFieldType`. A `:select` or `:multi_select` must pass `options:`, or boot raises the same error. `options:` is either an array of strings, or a lambda taking no arguments that returns one; the lambda is called each time the canvas is opened and each time an admin saves the step's settings, so the options follow the app's data without a restart (`options: -> { Region.order(:name).pluck(:code) }`). A saved setting whose value is no longer among the options is refused the next time the admin saves that step. When the options come from the app's records, ask the developer which records, which column is stored as the value, and whether the list must differ by account or host, since the lambda is passed nothing and the same list is offered everywhere the type is used. A `:list` must take a block of `setting` calls describing one entry. `:previous_step` lets the admin pick an earlier step, and is always required. `outputs_of: :<setting>` offers the outputs of the step chosen in that setting, and `from: :<setting>` offers the values of the output chosen in that setting; either one makes the type `:from_step`. `:flow` lets the admin pick one of the flows the admin's list shows on the same host, offered by title, and is stored in `node.config` as that flow's id as a string. A `:flow` setting is not checked on save against the flows offered, so code that reads it looks the flow up through the host and handles a blank or unknown id. `limit:` is the most values a `:multi_select` takes. `check:` is a lambda given the value that returns an error message, or `nil` when the value is acceptable. `label:` defaults to the name humanized. `kept_on:` and `attribute:` keep the setting's value on one of the app's own records instead of in the flow; see "Keep a setting on one of the app's records".
|
|
82
|
-
- `output :name, type:, label:, values:, from:` — a value the step records. `type` is one of `:string`, `:integer`, `:float`, `:boolean`, or boot raises `EasyFlow::UnknownOutputType`. `values:` is an array or a lambda taking the node, listing the values the output can take; the canvas offers these as the connections leaving the step, so a type that routes must declare them. `from: :<setting>` takes the values from the step chosen in that setting.
|
|
83
|
+
- `output :name, type:, label:, values:, from:` — a value the step records. `type` is one of `:string`, `:integer`, `:float`, `:boolean`, or boot raises `EasyFlow::UnknownOutputType`. `values:` is an array or a lambda taking the node, listing the values the output can take; the canvas offers these as the connections leaving the step, so a type that routes must declare them. `from: :<setting>` takes the values from the step chosen in that setting. A Condition step requires the admin to pick one of the earlier step's outputs, so a type whose answer admins should test with a Condition declares at least one `output`, with `values:` so the Condition offers answers to pick.
|
|
84
|
+
- `outputs_named_by :<list setting>` — adds one output for each entry an admin enters in that `:list` setting, named by the entry's `name`, so the setting's entry block must declare `setting :name, type: :string`. Ask the developer whether the admin or the type decides the names of the values the step records. Do not pick.
|
|
85
|
+
- The named outputs follow any outputs the type declares with `output`, in the order the admin entered them. An entry with a blank `name` adds none.
|
|
86
|
+
- Each is a `:string` output labelled with its name humanized, so an entry named `coverage` is offered as `Coverage`.
|
|
87
|
+
- They are offered wherever an admin picks one of a step's outputs: on a step of a type of the app's own through a setting declared with `outputs_of:`, on the built-in Condition, Compare, Compare two values and Count steps, and in each comparison of an All of or Any of step.
|
|
88
|
+
- A named output lists no values, so a setting declared with `from:` on it offers nothing to pick. A Condition, whose answer is required, cannot be completed on a named output, and a Count on one counts every visit. Ask the developer whether a Compare, a Compare two values or an All of or Any of step, which take no answer to pick, is enough for what admins will test.
|
|
89
|
+
- A named output is not a connection leaving the step, so a type that routes still declares its connections with `output ... values:`.
|
|
90
|
+
- The type records the values with `process`, returning a hash keyed by each name as a string. See step 6.
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
setting(:checks, type: :list) { setting :name, type: :string }
|
|
94
|
+
|
|
95
|
+
outputs_named_by :checks
|
|
96
|
+
|
|
97
|
+
def process(node, state)
|
|
98
|
+
Array(node.config["checks"]).to_h { |check| [ check["name"], CheckResult.for(check["name"]) ] }
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Here `CheckResult.for` is the app's own lookup.
|
|
83
103
|
- `names_by :setting` or `names_by { |node| ... }` — what the step is called on the canvas, from a setting or computed.
|
|
84
104
|
- `awaits_input` — the visitor is shown this step and submits an answer to it.
|
|
85
|
-
- `waits_until { |node, state| ... }` — the run stops at this step until the block returns a truthy value. See step 8.
|
|
105
|
+
- `waits_until { |node, state| ... }` or `waits_until { |node, state, times| ... }` — the run stops at this step until the block returns a truthy value. See step 8.
|
|
86
106
|
- `answer_check { |node, value| ... }` — checks a submitted answer before it is recorded. `value` is the submitted string, an array of strings when the input submits several values, or `nil` or `""` when the input sent nothing. An array may hold blank strings. Return a message to refuse the answer, or `nil` to accept it. Without it every answer is accepted, including a blank one. For a file input, see step 7.
|
|
87
107
|
- `labels_answer_by { |node, value| ... }` — the label a recorded answer to a step of this type is shown by. `value` is one recorded answer, and for an array answer the block is called once per entry. Return the label, or `nil` or `""` to fall back to the label of the question answer with that value, and then to the value itself. The block is called on every completion page and every `choice_label` call, so it must not be slow and must not change anything. Ask the developer what a visitor or admin reading the answer should see in place of the stored value.
|
|
88
108
|
- `ends_here` / `begins_here` — marks the type as an end or a start of a flow. A run that ends on a step of an `ends_here` type gives that step's `output` setting as `run.output`, so a type of the app's own that ends a flow declares `setting :output, type: :string` for the admin to fill in, or gives no output.
|
|
@@ -169,8 +189,15 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
169
189
|
- `node` has `id`, `type` and `config`; `config` is a hash of the admin's settings with string keys.
|
|
170
190
|
- `state` is the answers recorded so far, keyed by step id as strings. An answer is a string, or an array of strings for a step that takes several values, such as a checklist.
|
|
171
191
|
- `process` returns the value recorded under this step's id. It runs as soon as a visitor reaches the step, before the next step is shown.
|
|
172
|
-
- A type that declares more than one `output` returns a hash from `process`, keyed by each output's name as a string. A later
|
|
192
|
+
- A type that declares more than one `output`, or declares `outputs_named_by`, returns a hash from `process`, keyed by each output's name as a string. A later Condition, Compare, Compare two values, All of or Any of or Count step reads the output the admin picks from that hash, and reads a recorded answer that is not a hash whole.
|
|
173
193
|
- `route` returns the value that picks the connection to follow; it is compared as a string with the value each leaving connection is labelled with, so `false` follows the connection labelled `false`. Returning `nil` follows the first connection. A returned value that matches no connection's label ends the flow at that step. A route that returns `true` or `false` declares `output :result, type: :boolean, values: [true, false]`.
|
|
194
|
+
- To route on when an earlier step was completed, define `route(node, state, times)` with a third argument, or `route { |node, state, times| ... }` in the block form. A `route` defined with two arguments works as before and is not given the times.
|
|
195
|
+
- `times` is a hash keyed by step id as strings, holding the time each step on the path taken so far was completed, as a time in the app's time zone. For a step visited more than once, it holds the latest visit's time under the plain step id.
|
|
196
|
+
- A step that only routes, a start and an end record no answer, so they have no time.
|
|
197
|
+
- A step with no time is missing from the hash, so read it with `times[id]` and handle `nil`.
|
|
198
|
+
- Ask the developer what about the time decides the branch, such as the hour, the weekday or how long ago it was. Do not pick. Before writing the type, ask whether a built-in Compare, Compare two values or All of or Any of step reading the hour, weekday or minutes an earlier step was completed at is enough; see "Read when steps were completed".
|
|
199
|
+
- `process` is not given the times. A `process` defined with a third argument is handed an empty hash.
|
|
200
|
+
- A setting declared with `outputs_of:` also offers `completed_hour`, `completed_weekday` and `completed_minute` for a step that records a value. When the admin picks one, `state` holds no value under that name, so a type that reads the picked output works it out from `times` in `route`, or handles a missing value.
|
|
174
201
|
- To branch on a checklist, read its array and return one value. Ask the developer what decides the branch, such as one given answer being ticked, any of several, or how many were ticked:
|
|
175
202
|
|
|
176
203
|
```ruby
|
|
@@ -183,7 +210,7 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
183
210
|
Array(state[node.config["step"]]).include?(node.config["answer"])
|
|
184
211
|
end
|
|
185
212
|
```
|
|
186
|
-
- In a flow that loops, the first visit to a step is recorded under its id and each later visit under `<id>@<n>`, where `n` counts from 2. `route` is given the latest visit's answer under the plain step id. `process` and `waits_until` are given the answers as recorded, so the plain id holds the first visit's answer and later visits are under `<id>@2`, `<id>@3` and on. A `process` step reached again records its result under its own `<id>@<n>`. When a computing or waiting type reads an earlier step that can be asked again, ask the developer whether it should read the first visit or the latest. To end a loop after a number of visits, ask the developer whether a built-in Count step followed by a compare step is enough before writing a type that counts.
|
|
213
|
+
- In a flow that loops, the first visit to a step is recorded under its id and each later visit under `<id>@<n>`, where `n` counts from 2. `route` is given the latest visit's answer under the plain step id. The `times` given to `route` and `waits_until` hold the latest visit's time under the plain step id. `process` and `waits_until` are given the answers as recorded, so the plain id holds the first visit's answer and later visits are under `<id>@2`, `<id>@3` and on. A `process` step reached again records its result under its own `<id>@<n>`. When a computing or waiting type reads an earlier step that can be asked again, ask the developer whether it should read the first visit or the latest. To end a loop after a number of visits, ask the developer whether a built-in Count step followed by a compare step is enough before writing a type that counts.
|
|
187
214
|
7. For a type that awaits input, write the partial named in `drawn_by`, for example `app/views/flow_steps/_rating.html.erb`. It receives the display object as `step`. It renders only the input, since the page supplies the form, the Next button and the Back button. The input must be named `answers[<step id>]`, or the answer is not recorded:
|
|
188
215
|
|
|
189
216
|
```erb
|
|
@@ -211,7 +238,7 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
211
238
|
- In a flow set to save on finish or to save nothing, no file reaches the check, and it is given a string, an array of strings, or `nil` instead. Refuse an answer that does not respond to `original_filename`, as the built-in step does with `A file can only be uploaded in a flow that saves its runs.`, or that string is recorded as the answer.
|
|
212
239
|
- Declare `labels_answer_by` to show the recorded reference by a name a reader knows, such as the file's name read back from the app's store. For an array of references the block is called once per reference.
|
|
213
240
|
|
|
214
|
-
A compare step reads a typed answer such as `"12"` as the number it spells, and an answer that is missing, is an array or is not a number makes it follow its `false` connection.
|
|
241
|
+
A compare or Compare two values step reads a typed answer such as `"12"` as the number it spells, and an answer that is missing, is an array or is not a number makes it follow its `false` connection. A comparison in an All of or Any of step reads an answer the same way, and such an answer makes that comparison not hold.
|
|
215
242
|
|
|
216
243
|
A blank single answer is recorded as `""` and the visitor moves on, unless the type's `answer_check` refuses it. For a type that takes several answers, refuse nothing ticked with `Array(value).compact_blank.empty?`, since `value.blank?` is false for an array holding only the hidden blank field. When the check returns a message, nothing is recorded and the same step is shown again with that message. This holds whether the flow keeps a stored run or carries its answers in the page, and the page supplies what it needs for both, so the partial adds nothing for it. Ask the developer whether the visitor may leave this step blank, and whether that is fixed for the type or chosen per step by the admin. For a per-step choice, declare a `:boolean` setting and read it in the check:
|
|
217
244
|
|
|
@@ -235,13 +262,15 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
235
262
|
end
|
|
236
263
|
```
|
|
237
264
|
|
|
238
|
-
- The block is given `node` and `state
|
|
265
|
+
- The block is given `node` and `state`, and is not given the run or its owner. Ask the developer what the step waits for, and how the block finds it from the step's settings and the answers recorded before it. If it cannot be found from those, stop and ask, since the block has nothing else to read.
|
|
266
|
+
- A block that takes a third argument is also given `times`, a hash keyed by step id as strings holding the time each completed step was completed, as a time in the app's time zone, with the latest visit's time under the plain step id for a step visited more than once. A step with no time is missing from it. When the step waits only for a time, ask the developer whether the built-in Wait step is enough before writing the type.
|
|
239
267
|
- The block is called each time the visitor loads the step page, and each time the app calls `run.advance`. Keep it to a single lookup.
|
|
240
268
|
- While the block returns a falsy value, the visitor is shown `Waiting for <name>.` and no form, no Next button and no Back button. `<name>` is what `names_by` gives, or for a type that declares `chooses_flow` the chosen flow's title and version, or the `step_name` when there is neither, so give the type a name a visitor can read.
|
|
269
|
+
- In a flow that keeps no stored run, the waiting page also shows a `Check again` button that loads the page again with the answers and the times their steps were completed.
|
|
241
270
|
- Once the block returns a truthy value, `true` is recorded under the step's id, or the value `process` returns when the type also defines `process`, and the run goes on to the next step.
|
|
242
271
|
- Do not declare `awaits_input` on a type that waits. The waiting message replaces the form, so the input is never shown.
|
|
243
272
|
- The page does not reload itself. Ask the developer how the run should move on when what it waits for happens:
|
|
244
|
-
- The visitor reloads the page. This needs nothing more, and works whether or not the flow keeps a stored run.
|
|
273
|
+
- The visitor reloads the page, or presses `Check again` in a flow that keeps no stored run. This needs nothing more, and works whether or not the flow keeps a stored run.
|
|
245
274
|
- The app calls `run.advance` where the event is handled, such as a webhook or a job. See "Read a run and its answers". This needs a stored run, so it only applies to a flow the admin set to save each step.
|
|
246
275
|
9. Restart the server, open a flow on the canvas of each host that should offer the type, and check the type is in the palette, its settings show, and a preview walks through it. On a host that names a list of offered step types without the type's id, check the type is not in the palette. For a type that declares `starts_a_flow`, check the step is marked `Runs a flow`, that once a flow is picked in its settings clicking the step goes to that flow's page, that the `Back to` button on that page returns to the flow the step is in, and that its `Settings` button opens its settings. Also check that the step shows `missing flow` while no flow is picked, `unrunnable version` while the version number names no live or replaced version, `circular` while it names the flow it sits in, and that publishing is refused until each is fixed. For a type that declares `chooses_flow`, set the settings the block reads and check that clicking the step goes to the flow the block names, that a type with no `names_by` is named on the canvas by that flow's title and version, and that a run stopping at the step starts a run of that flow on that version. For a type that declares `chooses_flow_from_run`, start a stored run whose owner or answers the block reads, walk it to the step, and check `run.waiting_on` is a run of the flow and version the block names, and that a run for which the block names no flow raises `EasyFlow::InnerFlowError`.
|
|
247
276
|
|
|
@@ -413,41 +442,89 @@ Use this when what a visitor answered earlier decides which answers a later step
|
|
|
413
442
|
- The array narrows only what is shown. A submitted value outside it is still recorded unless the step type's `answer_check` refuses it, and `answer_check` is not given the earlier answers. Ask the developer whether that is acceptable.
|
|
414
443
|
5. Publish a flow on the host, walk it with earlier answers that allow several, one and no answers to a step, and check the step shows only the allowed answers, is filled in, and is skipped. Press Back after a filled-in step and check the visitor returns to the question before it.
|
|
415
444
|
|
|
445
|
+
### Read when steps were completed
|
|
446
|
+
|
|
447
|
+
Use this when the app or a flow's branches depend on when a visitor completed a step, such as routing differently out of hours, or reading how long a visitor took between two steps.
|
|
448
|
+
|
|
449
|
+
1. The times on a stored run need the engine's migrations installed, which `easy_flow-install` does.
|
|
450
|
+
2. What holds:
|
|
451
|
+
- Each time a step visit records a value, the current time is kept with that value as the time the visit was completed, under the same key, including each later visit's `<id>@<n>`. This covers a visitor's answer, a value `process` computed, a waiting step moving on, and an answer the host's `allowed_answers` filled in or skipped.
|
|
452
|
+
- The Back button removes the time with the value.
|
|
453
|
+
- In a flow set to save each step, each value and its time are kept on the run together as the step is completed.
|
|
454
|
+
- In a flow set to save on finish or to save nothing, the times travel in the page with the answers, and the engine's step page carries them, so a partial adds nothing for them. A flow set to save on finish keeps the times of the steps on the path taken on the run it creates. A flow set to save nothing keeps none.
|
|
455
|
+
- In a flow set to save on finish or to save nothing, the times come from the page, so a visitor can change them. Ask the developer whether anything that depends on a time, such as a Wait step or a branch on the hour, may be decided by the visitor in those flows.
|
|
456
|
+
- A value a run held before the engine kept times has no time.
|
|
457
|
+
3. Every step that records a value, which is a step that awaits input, computes a value or waits, offers three outputs wherever an admin picks one of its outputs:
|
|
458
|
+
- `completed_hour`, labelled `Hour completed` — the hour of the day it was completed, 0 to 23, in the app's time zone.
|
|
459
|
+
- `completed_weekday`, labelled `Weekday completed` — the day of the week it was completed, 1 for Monday to 7 for Sunday, in the app's time zone.
|
|
460
|
+
- `completed_minute`, labelled `Minutes since 1970 when completed` — the whole minutes from the start of 1970 to when it was completed, so two of them subtract to the minutes between two completed steps.
|
|
461
|
+
- For a step visited more than once, each reads the latest visit's time.
|
|
462
|
+
4. Compare and Compare two values read these outputs. A Compare step on `completed_hour` at least 17 branches on a step completed after 5pm. A Compare two values step comparing one step's `completed_minute` with an earlier step's `completed_minute` plus 30 branches on whether the later step was completed at least 30 minutes after the earlier one. A step with no time makes either one follow its `false` connection. Each comparison of an All of or Any of step reads them the same way as Compare two values, so one step can branch on, for example, a step completed after 5pm and on a weekday, and a step with no time makes that comparison not hold.
|
|
463
|
+
5. Condition and Count also offer these outputs but do not read them. A Condition cannot be completed on one, since there is no answer to pick, and a Count on one counts every visit.
|
|
464
|
+
6. For a type of the app's own, see the `route` bullets in step 6 of "Declare a step type" and the `waits_until` bullets in step 8.
|
|
465
|
+
7. Read the times from a stored run with `run.completed_at`; see "Read a run and its answers".
|
|
466
|
+
8. Publish a flow set to save each step with a Compare on a question's `completed_hour`, walk it, and check the branch taken matches the hour the question was completed and that `run.completed_at` holds a time for each recorded value.
|
|
467
|
+
|
|
468
|
+
### Hold a run until a time
|
|
469
|
+
|
|
470
|
+
Use this when a visitor's run should stop until a set time has passed, such as an hour after a form was submitted, the next 9:00 after it, or a fixed date.
|
|
471
|
+
|
|
472
|
+
1. Ask the developer whether an admin placing the built-in Wait step on the canvas is enough before writing a type. It is in every host's palette unless the host names a list of offered step types without `wait`, which is set through `easy_flow-install`.
|
|
473
|
+
2. The Wait step's settings, filled in by the admin:
|
|
474
|
+
- `Counting from` — an earlier step, required. The time the step was completed is the time the wait counts from, or the latest visit's time when it was visited more than once.
|
|
475
|
+
- `Minutes after it` — a whole number of minutes. Blank counts as 0.
|
|
476
|
+
- `Next time of day after it, as HH:MM` — the run is held until the first time the clock reads that hour and minute after the counted time, in the app's time zone, which is the same day when that is still to come and the next day otherwise.
|
|
477
|
+
- `Date, as YYYY-MM-DD` — the run is held until the start of that date in the app's time zone.
|
|
478
|
+
- A date wins over a time of day, and a time of day wins over minutes. The time of day and the date are not checked on save, so a value in another form is not refused.
|
|
479
|
+
- The step is named on the canvas and in the waiting message by what it waits for: the date as the admin wrote it, such as `2026-10-12`, or else the time of day followed by `after` and the counted step's id, such as `09:00 after ask`, or else the minutes followed by `minutes after` and the counted step's id, such as `30 minutes after ask`. When the date, the time of day and the minutes are all blank, the step is named by its id on the canvas and the waiting message reads `Waiting for Wait.`, and the run moves on as soon as the counted step has a time.
|
|
480
|
+
3. What then holds:
|
|
481
|
+
- Without a date, the run is held for as long as the counted step has no time, such as a step that only routes or a step not on the path taken.
|
|
482
|
+
- While held, the visitor is shown `Waiting for <name>.`, such as `Waiting for 30 minutes after ask.`, with no form, no Next button and no Back button.
|
|
483
|
+
- Once the time has passed, `true` is recorded under the step's id and the run goes on, when the visitor next loads the page or the app calls `run.advance`, the same as any waiting step. The page does not reload itself. Ask the developer how the run should move on, as in step 8 of "Declare a step type", such as a job the app schedules for `run.held_until` that calls `run.advance`.
|
|
484
|
+
- `run.held_until` gives the time a stored run stopped at a Wait step is held until, or `nil` when it is not stopped at a Wait step or the time is not known yet.
|
|
485
|
+
- In a flow set to save on finish or to save nothing, the waiting page also shows a `Check again` button, which loads the page again with the answers and the times their steps were completed, so the Wait moves on once its time has passed. Reloading the page in the browser instead takes the completion time of the step just submitted again, so a Wait counting minutes or a time of day from that step does not move on by reloading. Ask the developer whether such a flow needs to be set to save each step.
|
|
486
|
+
4. Publish a flow set to save each step with a Wait step counting a few minutes from a question, walk it to the Wait step, check the page shows `Waiting for <minutes> minutes after <question id>.` and `run.held_until` is that many minutes after the answer, and check `run.advance` after that time moves the run on. In a flow set to save nothing, walk to the same Wait step, wait past the time, press `Check again`, and check the next step is shown.
|
|
487
|
+
|
|
416
488
|
### Read a run and its answers
|
|
417
489
|
|
|
418
490
|
1. Find runs scoped to one host so one host never sees another's: `EasyFlow::Run.joins(:flow).where(easy_flow_definitions: { host: "intake" })`.
|
|
419
491
|
2. Read from a run:
|
|
420
492
|
- `run.recorded` — the answers so far, a hash keyed by step id as symbols. A value is a string, or an array of strings for a checklist or any step that takes several answers, empty when nothing was ticked. A File upload step's value is the reference the host's file store returned, not the file, and a step of the app's own that took several files holds an array of references, so the app reads each file back from its own store.
|
|
493
|
+
- `run.completed_at` — the time each step visit was completed, a hash keyed the same as `run.recorded`, each value a time in the app's time zone, including each later visit under `:"<id>@<n>"`. A value recorded before the engine kept times has no time, so its key is missing. See "Read when steps were completed".
|
|
494
|
+
- `run.held_until` — the time the run is held until while stopped at a Wait step, or `nil`. See "Hold a run until a time".
|
|
421
495
|
- `run.flow` — the flow it belongs to.
|
|
422
496
|
- `run.owner` — the optional record the run belongs to, polymorphic, set by the app. `run.label` and `run.status` are free string columns for the app's own use.
|
|
423
497
|
- `run.parent_run` and `run.parent_step` — the run whose Flow step started this one and that step's id, both `nil` for a run a visitor or the app started. Once this run ends with an output, `run.parent_run.recorded` holds that `output` under that step id. When it ends with none, nothing is recorded there. When an admin removed the flow the parent run belonged to, `parent_run` is `nil` and `parent_step` still holds the step id.
|
|
424
498
|
- `run.inner_runs` — the runs this run's Flow steps started.
|
|
425
499
|
- `run.waiting_on` — the run started at the step this run is stopped at, or `nil` when no run was started there.
|
|
426
500
|
- `run.pinned_definition` — the flow document of the version the run started on.
|
|
427
|
-
- `run.next_step(answers)` and `run.walked(answers)` — the next step, and the answers on the path taken, for a set of answers against that pinned version
|
|
501
|
+
- `run.next_step(answers)` and `run.walked(answers)` — the next step, and the answers on the path taken, for a set of answers against that pinned version, routed with the run's own `completed_at`.
|
|
428
502
|
- `run.output` — the text the admin wrote into the output setting of the End step the run ended on, read from the run's pinned version. It is `nil` when the run has not reached an End step, or ended on one whose output was left blank. A flow may have several End steps with a different output on each, so the output says which way the flow ended. Ask the developer what the app does with each output, and what it does when there is none.
|
|
503
|
+
- `run.ended_at_end?` — `true` when the run is not stopped at a step that awaits input, computes a value or waits, and the last step it reached is of a type that declares `ends_here`, such as the built-in End step, whether or not that step has an output. It is `false` while the run is still going, including while it waits at a Flow step, when a loop stopped it, and when it ended at a step that is not an end, such as a step with no connection leaving it or a `route` value that matches no connection's label. Use it to tell a run that ended at an End step with a blank output from one that stopped anywhere else, since `run.output` is `nil` for both.
|
|
504
|
+
- `run.stopped_on_loop` — the id of the step a loop came back to when it stopped the run, as a string, the plain step id and never a visit key such as `"tally@2"`, or `nil` when no loop stopped it. A loop stops the run when it comes back to a step already visited with no step that awaits input answered since that step's last visit, such as a loop of Count steps only. The run ends there, and `run.ended_at_end?` is `false`. Ask the developer what the app does with a run a loop stopped, such as showing an admin the step named. Do not pick.
|
|
429
505
|
- `run.advance` — moves the run past every waiting step whose `waits_until` block now returns a truthy value, and past every step that computes a value, and records each result on the run. It stops at the next step that asks the visitor for input, the next waiting step that is not ready, or the end. It does not call `finished`; that is called the next time the visitor loads the run's page. When it stops at a Flow step it starts that step's run, and when it reaches the end of a run a Flow step started it hands the output to the parent run; see "Follow a run into the flow a Flow step started".
|
|
430
506
|
3. To show answers with their question text and answer labels, build a runner from the run's pinned document:
|
|
431
507
|
|
|
432
508
|
```ruby
|
|
433
509
|
runner = EasyFlow::QuestionRunner.new(run.pinned_definition)
|
|
434
|
-
runner.state_on_path(run.recorded).each do |step_id, value|
|
|
510
|
+
runner.state_on_path(run.recorded, times: run.completed_at).each do |step_id, value|
|
|
435
511
|
puts "#{runner.question_text(step_id)}: #{runner.choice_label(step_id, value)}"
|
|
436
512
|
end
|
|
437
513
|
```
|
|
438
514
|
|
|
439
|
-
- `
|
|
515
|
+
- Pass `times: run.completed_at` to `state_on_path` and `next_step`. Without it, a step that routes on when an earlier step was completed finds no time, so a Compare or Compare two values reading a time follows `false`, a comparison of an All of or Any of reading a time does not hold, and the path read may differ from the one the visitor took.
|
|
516
|
+
- `state_on_path(answers, times: {})` — only the answers on the path the visitor actually took, in the order they were given, dropping answers left behind by going back. In a flow that loops, it holds every visit: the first under the step id and each later one under `:"<id>@<n>"`, such as `:"job@2"`. Ask the developer whether each visit is shown as its own line or the visits to one question are grouped together; to group them, take the part of the key before `@`.
|
|
440
517
|
- `question_text(id)` — the text of a question or checklist step, given either its id or a visit key such as `"job@2"`. For a step of any other type it returns that step's `question` setting, or `nil` when it has none.
|
|
441
518
|
- `choice_label(id, value)` — the label the step's type gives the answer with `labels_answer_by`, or else the label of one chosen answer on a question or checklist, or else the value itself. For a File upload step it is the name the host's file store gives the recorded reference, or the reference itself when the store gives none. It takes a visit key the same way as `question_text`. Handed a checklist's array, it returns one string, the label of each ticked value joined into a sentence such as `Email, Phone, and Post`, and an empty string when nothing was ticked. Ask the developer whether a checklist's answers are shown as that one sentence or each on its own; for each on its own, call it once per ticked value with `value.map { |ticked| runner.choice_label(step_id, ticked) }`.
|
|
442
519
|
- `step(id)` finds the step for either its id or a visit key.
|
|
443
|
-
- `next_step(answers, run: nil)` — the next step to show, built by its type's `displays_by`. Pass `run:` when a type's display block takes the run. Its answers are narrowed by the host's `allowed_answers` only when the runner was built with `host:`.
|
|
520
|
+
- `next_step(answers, run: nil, times: {})` — the next step to show, built by its type's `displays_by`. Pass `run:` when a type's display block takes the run. Its answers are narrowed by the host's `allowed_answers` only when the runner was built with `host:`.
|
|
444
521
|
- `steps_on_path(answers)` — the steps on the path taken, each built by its type's `displays_by` with a `nil` run.
|
|
445
522
|
- `steps`, `slug` and `headline` read the rest of the document.
|
|
446
523
|
|
|
447
524
|
## Conventions
|
|
448
525
|
|
|
449
526
|
- A step type's id comes from its class name or the id passed to `EasyFlow.step`, and it is stored in flow documents, so renaming the class or id breaks every flow that uses it.
|
|
450
|
-
- Never give a step type one of the built-in ids: `start`, `terminal`, `question`, `checklist`, `file_upload`, `condition`, `switch`, `compare`, `count`, `flow_step`.
|
|
527
|
+
- Never give a step type one of the built-in ids: `start`, `terminal`, `question`, `checklist`, `file_upload`, `condition`, `switch`, `compare`, `compare_two_values`, `all_or_any`, `count`, `wait`, `flow_step`.
|
|
451
528
|
- Register class-based step types inside `to_prepare`. A type registered anywhere else is lost on code reload in development.
|
|
452
529
|
- Registering a type does not put it in the palette of a host that names a list of offered step types without the type's id. Which step types a host offers is set through `easy_flow-install`, not here.
|
|
453
530
|
- A type that declares `ends_here` is in every host's palette, and a type that declares `begins_here` is in no host's palette.
|
|
@@ -466,19 +543,25 @@ Use this when what a visitor answered earlier decides which answers a later step
|
|
|
466
543
|
- A flow holding a `starts_a_flow` step whose `flow` names no existing flow, or whose `version` names no live or replaced version of it, or whose `flow` leads back to the flow the step sits in directly or through other flows, cannot be saved as a version or published, and these checks cannot be turned off.
|
|
467
544
|
- On the canvas, clicking a step of a `starts_a_flow` type whose `flow` setting names a flow goes to that flow's page, which has a `Back to` button to the flow the step is in, and the step's settings open from its `Settings` button.
|
|
468
545
|
- A type that routes declares the values its output takes, or the canvas offers no connections to label.
|
|
546
|
+
- An output named by `outputs_named_by` takes its name from an entry's `name`, lists no values, and is never a connection leaving the step.
|
|
469
547
|
- A run starts only on a version its own flow has published and not retired or withdrawn, and any other version raises `ActiveRecord::RecordInvalid`.
|
|
470
548
|
- Always read a run against `run.pinned_definition`, never the flow's live version, since a run keeps the version it started on after a new one is published.
|
|
471
549
|
- Always look flows and runs up through a host.
|
|
472
550
|
- In a flow that loops, a later visit's answer is keyed `<id>@<n>`, so code that reads answers never assumes one answer per step id. A step id may not contain `@`, and a flow with one is refused when published.
|
|
473
|
-
- A loop that comes back to a step with no answer given since the last visit to it ends the flow there, so every loop needs a step that awaits input.
|
|
551
|
+
- A loop that comes back to a step with no answer given since the last visit to it ends the flow there and `run.stopped_on_loop` names that step, so every loop needs a step that awaits input.
|
|
552
|
+
- `run.ended_at_end?` is `true` only for a run whose last step reached is of an `ends_here` type, so a run a loop stopped, or one that ended at a step with no connection to follow, is never counted as ended at an End step.
|
|
474
553
|
- A step's input field is named `answers[<step.id>]` for one value or `answers[<step.id>][]` for several. Any other name is ignored, and a hash is treated as blank.
|
|
475
554
|
- An answer is a string or an array of strings, so code that reads answers never assumes a string. A checklist's answer is always an array.
|
|
476
|
-
- Condition, switch and
|
|
555
|
+
- Condition, switch, compare, Compare two values and All of or Any of do not branch on an array answer. Branching on one takes an app step type that defines `route`.
|
|
477
556
|
- A `displays_by` block that takes the run handles a `nil` run.
|
|
478
557
|
- A file is taken only in a flow that keeps a stored run, only from an input named `answers[<step.id>]` or, for several files, `answers[<step.id>][]`, and only when the host has a file store, and the run records the store's reference for each file, never the file.
|
|
479
558
|
- A `labels_answer_by` block changes only how an answer is shown, never what is recorded.
|
|
480
559
|
- An answer the type's `answer_check` refuses is never recorded, and the visitor is shown the same step with the check's message. An answer no check refuses is recorded, a blank single one as `""`.
|
|
481
|
-
- A `waits_until` block is given only the step
|
|
560
|
+
- A `waits_until` block is given only the step, the answers recorded so far and, when it takes a third argument, the times their steps were completed, so what it waits for must be findable from those.
|
|
561
|
+
- A `route` is given the times steps were completed only when it takes a third argument, and `process` is never given them.
|
|
562
|
+
- Every value a step visit records is kept with the time the visit was completed, on a stored run and carried in the page otherwise, and a time carried in the page can be changed by the visitor.
|
|
563
|
+
- A runner the app builds reads times only when handed `times: run.completed_at`.
|
|
564
|
+
- A Wait step without a date holds the run for as long as the step it counts from has no time.
|
|
482
565
|
- A type that waits never also declares `awaits_input`.
|
|
483
566
|
- `run.advance` only moves a stored run, and never finishes it; the visitor's next page load does.
|
|
484
567
|
- A flow's output is free text an admin writes on each End step, so code that acts on it handles `nil` and any value it does not expect.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: easy_flow-info
|
|
3
|
-
description: Use to learn what easy_flow offers — guided flows an admin draws on a canvas and a visitor runs one step at a time, versioned documents of steps and connections, flows that loop back to ask a step again, hosts and the step types each offers its admins, runs and the published version each starts on, and the output a flow gives when it ends, the built-in step types including questions and checklists that can require an answer and explain each answer behind an info button, file upload steps that hand a visitor's file to the app's file store and read it back by its name, and branching on an answer or comparing a number, Count steps that count how often an earlier step was answered so a loop can give up after a number of tries, Flow steps that name another flow, open its canvas when their card is clicked with a button back to the flow it was opened from, start a run of it for the same owner when a stored run reaches them, are flagged when the flow or version they name cannot run or leads back to the flow they sit in, let the waiting run name that inner run, move that run on along the output it ends with, and fail with an error naming the step when its version was taken out of service or its inner run ended with no output, removing a flow and why it is refused while a waiting run depends on it, step types the host app registers, including ones that start another flow and work out which flow and version to run from their own settings and are named after that flow, or choose it from the run they are in, the answers a host allows a step in a run and the steps it fills in or passes over, the checks they make on an answer, what a step shows a visitor and how that can depend on the visitor's run, and steps that hold a run until something outside the flow has happened, and the settings an admin fills in, including picks whose options come from the app's data and settings kept on one of the app's own records.
|
|
3
|
+
description: Use to learn what easy_flow offers — guided flows an admin draws on a canvas and a visitor runs one step at a time, versioned documents of steps and connections, flows that loop back to ask a step again, a loop that stops a run when nothing new was answered and the step it came back to, whether a run ended at an End step, hosts and the step types each offers its admins, runs and the published version each starts on, each visit a run records with its value and the time the step was completed, and the output a flow gives when it ends, the built-in step types including questions and checklists that can require an answer and explain each answer behind an info button, file upload steps that hand a visitor's file to the app's file store and read it back by its name, and branching on an answer, including one named output of a step that recorded several, on comparing a number with an amount, or on comparing two earlier answers, branching on a list of such comparisons that must all hold or of which any one must hold, branching on the hour, weekday or minute a step was completed, Wait steps that hold a run until a number of minutes or a time of day after an earlier step was completed, or until a date, and are named after what they wait for, the Check again button a visitor sees at a waiting step in a flow that keeps nothing, Count steps that count how often an earlier step was answered so a loop can give up after a number of tries, Flow steps that name another flow, open its canvas when their card is clicked with a button back to the flow it was opened from, start a run of it for the same owner when a stored run reaches them, are flagged when the flow or version they name cannot run or leads back to the flow they sit in, let the waiting run name that inner run, move that run on along the output it ends with, and fail with an error naming the step when its version was taken out of service or its inner run ended with no output, removing a flow and why it is refused while a waiting run depends on it, step types the host app registers, including ones whose outputs are named by the entries an admin lists in one of their settings, ones that start another flow and work out which flow and version to run from their own settings and are named after that flow, or choose it from the run they are in, the answers a host allows a step in a run and the steps it fills in or passes over, the checks they make on an answer, what a step shows a visitor and how that can depend on the visitor's run, and steps that hold a run until something outside the flow has happened, and the settings an admin fills in, including picks whose options come from the app's data and settings kept on one of the app's own records.
|
|
4
4
|
tools: Read
|
|
5
5
|
scope: guided flows — versioned documents of steps and the connections between them, drawn on a canvas by an admin and run by a visitor one step at a time, with step types the host registers
|
|
6
6
|
---
|
|
@@ -11,22 +11,26 @@ This local explains easy_flow and the words it uses. It makes no changes and giv
|
|
|
11
11
|
|
|
12
12
|
easy_flow is a Rails engine for guided, branching flows. An admin builds a flow on a canvas by placing steps and connecting them, previews it, and publishes it. A visitor then walks the published flow one step at a time, and each answer decides which step comes next.
|
|
13
13
|
|
|
14
|
-
Reach for it when an app needs a questionnaire, an intake form, or any decision path that an admin should be able to change without a deploy. The engine ships a start step, an end step, a question step, a checklist step, a file upload step,
|
|
14
|
+
Reach for it when an app needs a questionnaire, an intake form, or any decision path that an admin should be able to change without a deploy. The engine ships a start step, an end step, a question step, a checklist step, a file upload step, five steps that pick a branch from earlier answers, a Count step that counts earlier answers, a Wait step that holds a run until a time, and a Flow step that names another flow. Anything else a flow needs to ask, do, or wait for is a step type the app declares itself.
|
|
15
15
|
|
|
16
16
|
## Interface
|
|
17
17
|
|
|
18
18
|
easy_flow declares no commands of its own for this local. Its surface is split between the other two:
|
|
19
19
|
|
|
20
20
|
- **easy_flow-install** owns putting the engine into an app: its migrations, mounting it, choosing the controller it inherits from, naming hosts and the step types each one offers, choosing the default step drawing, setting the file store that keeps uploaded files, the flow checks, and the admin pages.
|
|
21
|
-
- **easy_flow-develop** owns building on it: declaring and registering step types, including ones that read an answer back under a label of their own, ones that wait, ones that start another flow, and ones with settings kept on the app's own records, narrowing the answers a host allows a step in a run, moving a run on once what it waits for has happened, serving a host's flows from the app's own controllers and routes, and starting a run on a chosen published version, reading a visitor's run, its answers, the inner run it is waiting on
|
|
21
|
+
- **easy_flow-develop** owns building on it: declaring and registering step types, including ones that read an answer back under a label of their own, ones whose outputs are named by the entries an admin lists in one of their settings, ones that wait, ones whose waiting or routing rule reads the times steps were completed, ones that start another flow, and ones with settings kept on the app's own records, narrowing the answers a host allows a step in a run, moving a run on once what it waits for has happened, serving a host's flows from the app's own controllers and routes, and starting a run on a chosen published version, reading a visitor's run, its answers, the times its steps were completed, the inner run it is waiting on, the time a Wait step is holding it until, the output its flow ended with, whether it ended at an End step, and the step a loop came back to when it stopped the run.
|
|
22
22
|
|
|
23
23
|
## How to use it
|
|
24
24
|
|
|
25
25
|
- To get easy_flow running in an app, or to change how it is configured, including which step types a host's admins can add and where uploaded files are kept, use **easy_flow-install**.
|
|
26
26
|
- To ask a visitor for a file, an admin places a file upload step on the canvas, and the app must first have a file store set through **easy_flow-install**.
|
|
27
|
-
- To add a step type, give a step type its own rule for refusing an answer, give it its own way to read an answer back, make what a step shows depend on the visitor's run, hold a run until something outside the flow has happened, add a step type of the app's own that starts another flow, including one that chooses the flow from the run it is in, limit which answers a step offers in a run from the answers given so far, keep a step's setting on one of the app's own records, put a flow on the app's own pages, start a run on a published version other than the live one, act on what a visitor answered
|
|
27
|
+
- To add a step type, give a step type its own rule for refusing an answer, give it its own way to read an answer back, name a step type's outputs after the entries an admin lists in one of its settings, make what a step shows depend on the visitor's run, hold a run until something outside the flow has happened, make a step type's waiting or routing rule read the times steps were completed, read the time a Wait step is holding a run until, add a step type of the app's own that starts another flow, including one that chooses the flow from the run it is in, limit which answers a step offers in a run from the answers given so far, keep a step's setting on one of the app's own records, put a flow on the app's own pages, start a run on a published version other than the live one, act on what a visitor answered or when each step was completed, act on the output a visitor's flow ended with, or tell a run that ended at an End step from one a loop stopped, use **easy_flow-develop**.
|
|
28
28
|
- To give a flow a different output for each way it can end, no code is needed: an admin fills in the output on each End step on the canvas.
|
|
29
29
|
- To branch a flow on an answer or on a number the visitor gave, no code is needed: an admin places one of the built-in branching steps on the canvas.
|
|
30
|
+
- To branch a flow on how two earlier answers compare, such as whether a second reading is higher than the first, no code is needed: an admin places a Compare two values step on the canvas.
|
|
31
|
+
- To branch a flow on several comparisons at once, such as whether a reading is above one amount and a second reading is below another, no code is needed: an admin places an All of or Any of step on the canvas and lists the comparisons.
|
|
32
|
+
- To branch a flow on when a step was completed, such as the hour of the day or the weekday, or on how many minutes passed between two steps being completed, no code is needed: an admin places a Compare or Compare two values step on the canvas and picks one of the completion time outputs.
|
|
33
|
+
- To hold a run for a number of minutes after a step was completed, until the next time of day after it, or until a date, no code is needed: an admin places a Wait step on the canvas.
|
|
30
34
|
- To make a visitor answer a question or tick something on a checklist before going on, no code is needed: an admin marks that step as required on the canvas.
|
|
31
35
|
- To let a visitor tick several answers on one step, no code is needed: an admin places a checklist step on the canvas.
|
|
32
36
|
- To explain an answer to the visitor, no code is needed: an admin fills in that answer's info on a question or checklist step.
|
|
@@ -50,7 +54,7 @@ easy_flow declares no commands of its own for this local. Its surface is split b
|
|
|
50
54
|
- **Setting** — one field an admin fills in when configuring a step on the canvas. It is text, a whole number, a decimal, a yes or no, a pick from a list, several picks from a list, an earlier step, an earlier step's output, another flow of the same host, or a list of entries that each hold their own fields. A setting can be required, limited to a number of picks, or checked by a rule the step type supplies.
|
|
51
55
|
- **Options** — the values a pick-from-a-list setting offers. They are either a fixed list written into the step type, or a lookup the app provides that is read each time the canvas or a save asks for them, so they can come from the app's own data. A saved value that is not among the options is refused.
|
|
52
56
|
- **Kept setting** — a setting whose value lives on one of the app's own records rather than in the flow. The step type says how to find that record from the step's other settings and which of its attributes holds the value. The canvas shows the value read from the record, and saving the step's settings writes it straight to the record, so the flow's document and its versions never hold it and undo or publishing does not change it. When no record is found, nothing is written. When the record refuses a value, the save is refused with the record's own error and none of the step's kept settings are written.
|
|
53
|
-
- **Output** — a value a step records into the run, with a type and a label. Later steps read outputs, and a setting that points at an earlier step offers that step's known values on the canvas.
|
|
57
|
+
- **Output** — a value a step records into the run, with a type and a label. Later steps read outputs, and a setting that points at an earlier step offers that step's known values on the canvas. A step type may also take further outputs from a list setting, one for each entry the admin gives a name, labelled with that name written as a phrase, such as "First reading" for `first_reading`. An entry left without a name adds no output. Those outputs are offered wherever the step's declared outputs are, such as when a Condition, Compare, Compare two values, All of or Any of or Count step picks which output to read.
|
|
54
58
|
- **Start** and **End** — the built-in step types that begin and finish a flow. An End step has one setting, its output.
|
|
55
59
|
- **Flow output** — the text an admin writes into the output setting of an End step, given as the result of a stored run that has reached that step. A flow may hold several End steps with a different output on each, so the output says which way the flow ended. A run that has not reached an End step, or that ended on one whose output was left blank, gives no output.
|
|
56
60
|
- **Question** — the built-in step type that asks the visitor: a question text, an optional category, a yes-or-no required setting, and a list of answers, each with a value, a label, a weight, a hint and an info. The visitor picks one answer, and that answer's value is recorded.
|
|
@@ -62,9 +66,15 @@ easy_flow declares no commands of its own for this local. Its surface is split b
|
|
|
62
66
|
- **Read-back** — the list a visitor sees when a flow finishes: each question, checklist or file upload asked, beside the label of the answer given. A step type may give its own label for an answer, and a file upload's answer is read back as the name the file store gives for it. A checklist's ticked answers are read back as their labels joined into one phrase, such as "Email, Phone and Post", and an answer with no label is read back as its value.
|
|
63
67
|
- **Required question** — a question step the admin has marked required. A blank answer to it is refused with the message "Fill this in to go on."
|
|
64
68
|
- **Required checklist** — a checklist step the admin has marked required. Submitting it with nothing ticked is refused with the message "Tick at least one to go on."
|
|
65
|
-
- **Condition** — a built-in branching step that checks whether an earlier step's answer is, or is not, a chosen value, and follows the true or the false connection.
|
|
69
|
+
- **Condition** — a built-in branching step that checks whether an earlier step's answer is, or is not, a chosen value, and follows the true or the false connection. The admin must also pick which of that step's outputs to test, and a Condition step with none picked is reported as missing that setting. When the earlier step recorded several outputs, only the picked one is tested, and when it recorded a single answer, that answer is tested whole.
|
|
66
70
|
- **Switch** — a built-in branching step that follows the connection labelled with an earlier step's answer.
|
|
67
71
|
- **Compare** — a built-in branching step that reads an earlier step's answer as a number and checks it against an amount by more than, less than, at least or at most, then follows the true or the false connection. When the earlier step recorded several outputs, the admin picks which one. An answer that is missing or is not a number decides false.
|
|
72
|
+
- **Compare two values** — a built-in branching step that reads two earlier steps' answers as numbers and checks the first against the second by more than, less than, at least, at most or equal to, then follows the true or the false connection. For each side, when that earlier step recorded several outputs, the admin picks which one. An optional amount is added to the second answer before the check, so "more than" with an amount of 5 is true only when the first answer exceeds the second by more than 5, and a blank amount adds nothing. When either answer is missing or is not a number, the step decides false.
|
|
73
|
+
- **All of or Any of** — a built-in branching step that holds a list of comparisons and a "Must hold" setting of "all of" or "any of", then follows the true or the false connection. Each comparison names an earlier step and, when that step recorded several outputs, which one, then more than, less than, at least, at most or equal to, then an amount, a second step with its output, or both. A comparison with a second step holds the same way a Compare two values step would decide, adding the amount to the second answer, and one with no second step checks the first answer against the amount alone. "All of" decides true only when every comparison holds, and "any of" when at least one does. A comparison whose answer is missing or is not a number does not hold, and a step with no comparisons decides false.
|
|
74
|
+
- **Completion time** — the time a step was completed, kept for every visit beside the value that visit recorded. Every step that records a value has one, including steps that act on their own and waiting steps, not only steps a visitor answers. A stored run saves it with the run. A flow that carries its answers in the page carries each completion time in the page too, gives a value sent without a time the time it arrived, and drops a time it cannot read. A run stored when such a flow finishes keeps the completion times of the visits on its path. Going back discards the last visit's completion time with its value.
|
|
75
|
+
- **Completion time outputs** — three outputs every step that records a value offers beside its own: "Hour completed", the hour from 0 to 23 in the app's time zone, "Weekday completed", from 1 for Monday to 7 for Sunday, and "Minutes since 1970 when completed", the whole minutes since the start of 1 January 1970 in UTC. Each is read from the completion time of the latest visit to that step, and a step with no completion time has none of them. Compare, Compare two values and All of or Any of read them, so Compare two values with "more than" and an amount of 30 on two steps' minutes is true when the first step was completed more than 30 minutes after the second. Condition and Count list them among a step's outputs but do not read the time when one is picked.
|
|
76
|
+
- **Wait** — a built-in waiting step that holds a run until a time. The admin picks the earlier step it counts from and gives a number of minutes after it, a time of day as HH:MM, or a date as YYYY-MM-DD. A date holds the run until the start of that day in the app's time zone and wins over the other two, and it holds even when the earlier step has no completion time. Otherwise a time of day holds the run until the next time that time of day comes after the earlier step's completion time, the same day when it is still to come and the next day when it is not, and it wins over minutes. With neither, the run is held for the minutes after the earlier step's completion time, and blank minutes hold it not at all. While the earlier step has no completion time and no date is set, the run stays held. It is counted from the completion time of the latest visit to that step. Like any waiting step, once its time has come the run moves past it the next time the visitor's page is loaded or the app moves the run on.
|
|
77
|
+
- **Wait step name** — what a Wait step is called on its canvas card and in the "Waiting for" line a visitor sees, following the same order as the rule that holds the run. A date set names it by that date, such as "2026-12-01". Otherwise it is named by the time of day, or by the minutes followed by "minutes", then "after" and the earlier step, such as "09:00 after signup" or "30 minutes after signup". A Wait step with no date, time of day or minutes has no name of its own, so its canvas card shows the step's id and the visitor sees "Waiting for Wait".
|
|
68
78
|
- **Count** — a built-in step that acts on its own and records a whole number: how many visits to an earlier step the run has recorded. When the admin picks an answer, only visits that gave that answer are counted, and when the admin picks none, every visit is counted. When the earlier step recorded several outputs, the admin picks which one the answer is read from. On the canvas the step is named "Count" followed by the earlier step and the answer it counts, such as "Count tests failed". Each visit to a Count step in a loop records a new count under its own visit key, so a Compare step after it can end the loop once the count reaches an amount.
|
|
69
79
|
- **Flow step** — a built-in step that names another flow and the number of one of its versions. The admin picks the flow from the same host's flows, listed by title as the admin's list of flows shows them. The step has one connection for each different output the End steps of that version write, and none when no flow or version is picked or no End step there has an output. On the canvas its card is named after the flow's title and the version, such as "Onboarding, version 2", and is marked "Runs a flow". Once a flow is picked, clicking the card opens that flow's canvas, and the card's Settings button opens the step's own settings instead. A canvas opened from a Flow step's card shows a "Back to" button named after the title of the flow it was opened from, or its slug when it has no title, which returns to that flow's canvas. The button only appears when that flow belongs to the same host, and to the same owner when the host has one. While the admin is connecting steps, clicking the card picks it as the connection's target. A run that reaches a Flow step stops there. When the run is a stored run, it starts one inner run of the named flow on the picked version. A run that carries its answers in the page, and an admin's preview, stop at the Flow step, start no run, and never move past it.
|
|
70
80
|
- **Unrunnable Flow step** — a Flow step whose picked flow or version cannot be run. Its card shows "missing flow" when no flow is picked or the picked flow no longer exists, and "unrunnable version" when the picked flow has no version with that number or that version is a draft, retired or withdrawn. These are always on, unlike the optional checks below, and the flow holding such a step cannot be saved as a new version or published until it is fixed.
|
|
@@ -74,17 +84,20 @@ easy_flow declares no commands of its own for this local. Its surface is split b
|
|
|
74
84
|
- **Inner run** — the run a Flow step starts. It records which run started it and at which step, and that run holds at most one inner run for each Flow step, however often it is moved on. It belongs to the same owner as the run that started it. When the picked version has been retired or withdrawn, no inner run is started and the run that reached the Flow step fails with an error naming that step. While the run that started it is stopped at the Flow step, that run can name the inner run it is waiting on, and it names none when it is stopped at any other step or has finished. Its steps that act on their own run as soon as it starts, and it stops at its first step that asks for input or waits.
|
|
75
85
|
- **Handing back** — what happens when an inner run ends. Its flow output is recorded in the run that started it, as that run's answer at the Flow step, and a background job then moves that run on along the connection labelled with the output. An inner run that ended with no output records nothing in the run that started it, and the background job then fails with an error naming the Flow step, so that run does not move on. An output is handed back only once, so an inner run moved on again after it ended changes nothing. Because the move happens in a background job, the app must run its jobs for a run to go past a Flow step.
|
|
76
86
|
- **Waiting step** — a step whose type carries a rule saying whether what it waits for has happened, such as a payment clearing or a document being signed. A run that reaches it stops there, and the visitor sees "Waiting for" followed by the step's name, with no form to submit. Once the rule says it has happened, the run moves past the step the next time the visitor's page is loaded or the app moves the run on, and the step is recorded as `true`.
|
|
87
|
+
- **Check again** — a button a visitor sees under the "Waiting for" line in a flow that carries its answers in the page. It loads the step page again with the answers and their completion times, so the run moves past a waiting step whose rule now says it has happened, such as a Wait step whose time has come. A stored run shows no such button, since loading its page again does the same.
|
|
77
88
|
- **Registry** — the list of step types the app has registered, alongside the built-in ones. A step whose type is not registered is not shown or run as that type, and its answers are not checked.
|
|
78
89
|
- **Host** — a named part of the app that owns a set of flows. A host sets the visitor layout and the admin layout, how admins are authenticated, how visitors are authorized, how a refusal is answered, and optionally which step types it offers and which answers it allows a step in a run. Flows are always looked up through a host, so one host never sees another's flows.
|
|
79
90
|
- **Offered step types** — the list of step types a host names for its admins to add. A host that names no list offers every registered step type. The list only narrows the palette.
|
|
80
91
|
- **Allowed answers** — a rule a host may carry that looks at a step and the answers given so far in a run and names the answer values that step allows. When it names nothing for a step, the step offers every answer. When it names several, a question or checklist offers only those. When it names exactly one, the step records that answer without being shown, and when it names none, the step is passed over and records a blank answer. This holds in a stored run, a run moved on outside a visitor's request, a flow that carries its answers in the page, and an admin's preview. Going back skips the steps the host filled in and returns to the last step the visitor was shown.
|
|
81
92
|
- **Palette** — the step types an admin can add on a host's canvas. It never holds the start step, always holds the end step, and otherwise holds the step types the host offers. A step already in a flow whose type the host does not offer is still drawn and run.
|
|
82
|
-
- **Run** — one visitor's pass through a flow. A run may belong to one of the app's own records, its owner, which the app sets. A run is pinned to the version it started on, so publishing a new version does not change a run already under way. A run starts on the live version unless the app chooses another of the flow's published versions. A run is refused when the chosen version is a draft, is retired or withdrawn, or belongs to another flow. It records each
|
|
83
|
-
- **Stored run** and **carried answers** — the two ways a visitor's progress is held. A stored run is saved by the app and found again by its id. A flow that keeps nothing carries the answers given so far in the page itself and sends them along with each step.
|
|
84
|
-
- **
|
|
93
|
+
- **Run** — one visitor's pass through a flow. A run may belong to one of the app's own records, its owner, which the app sets. A run is pinned to the version it started on, so publishing a new version does not change a run already under way. A run starts on the live version unless the app chooses another of the flow's published versions. A run is refused when the chosen version is a draft, is retired or withdrawn, or belongs to another flow. It records each visit, and going back discards the last visit on the path.
|
|
94
|
+
- **Stored run** and **carried answers** — the two ways a visitor's progress is held. A stored run is saved by the app and found again by its id. A flow that keeps nothing carries the answers given so far, and their completion times, in the page itself and sends them along with each step.
|
|
95
|
+
- **Visit entry** — what a stored run keeps for one visit: the value the step recorded and the step's completion time, held together as one entry. Reading a run's answers gives each visit's value, and reading its completion times gives each visit's time. A run stored before entries existed holds bare values, and the engine's migration wraps each one in an entry with no completion time, and running that migration back unwraps them.
|
|
96
|
+
- **Answers** — the recorded values of a run, keyed by visit. The next step is always worked out from these values, their completion times and the connections in the pinned version.
|
|
85
97
|
- **Loop** — a connection that leads back to a step the run has already passed, so that step is visited again. Branching steps read the most recent answer to a step, so a loop ends when an answer sends the run down a different connection. Reading a finished run again follows the same connections it took at the time, since each branching step reads the answers as they stood at that visit.
|
|
86
|
-
- **Visit** — one time a run reaches a step. The first visit is keyed by the step id alone, and each later visit is keyed by the step id, an `@`, and the visit number, such as `size@2`. Every visit's
|
|
87
|
-
- **Stopped loop** — a run that comes back to a step with no new answer given since its last visit there ends at that point rather than cycling forever. Only answers the visitor gives count, so a loop made only of steps that act on their own stops the first time it comes round.
|
|
98
|
+
- **Visit** — one time a run reaches a step. The first visit is keyed by the step id alone, and each later visit is keyed by the step id, an `@`, and the visit number, such as `size@2`. Every visit's value and completion time are kept, and a finished run lists each one.
|
|
99
|
+
- **Stopped loop** — a run that comes back to a step with no new answer given since its last visit there ends at that point rather than cycling forever. Only answers the visitor gives count, so a loop made only of steps that act on their own stops the first time it comes round. A stored run stopped this way names the step the loop came back to, and does not count as having ended at an End step.
|
|
100
|
+
- **Ended at an End step** — whether a stored run's path finished on an End step, or on a step of the app's own type that ends a flow. It does not while the run is still going or held at a step, when a loop stopped it, or when it stopped at a step with no connection to follow.
|
|
88
101
|
- **Reserved `@`** — a step id may not contain `@`, since that mark numbers later visits. A flow holding such a step id is reported as invalid.
|
|
89
102
|
- **Refused answer** — an answer the step's answer check turned down. Nothing is recorded for that step, and the visitor is shown the same step again with the check's message. This works the same whether the flow keeps a stored run or carries its answers in the page.
|
|
90
103
|
- **Blank answer** — a step the visitor leaves blank. On a step whose answer check does not refuse it, such as a question or checklist that is not required, the blank is recorded and the visitor moves on to the next step. This works the same whether the flow keeps a stored run or carries its answers in the page, and every value ticked on a checklist is carried either way.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: easy_flow-install
|
|
3
|
-
description: Use to hook easy_flow into a project — copying and running its migrations, mounting the engine for each host, setting the controller it inherits from, declaring hosts with their layouts, access methods, the record that owns their flows and the step types each offers, choosing the default step drawing, setting the file store that keeps files visitors upload, turning on optional checks, giving the app a job queue that moves a run on once the flow its Flow step, or its own step type that starts a flow, started ends, and reaching the admin pages.
|
|
3
|
+
description: Use to hook easy_flow into a project — copying and running its migrations, mounting the engine for each host, setting the controller it inherits from, declaring hosts with their layouts, access methods, the record that owns their flows and the step types each offers, choosing the default step drawing, setting the file store that keeps files visitors upload, turning on optional checks, setting the time zone Wait steps and step completion times are read in, giving the app a job queue that moves a run on once the flow its Flow step, or its own step type that starts a flow, started ends, and reaching the admin pages.
|
|
4
4
|
tools: Bash, Read, Edit
|
|
5
5
|
scope: guided flows — versioned documents of steps and the connections between them, drawn on a canvas by an admin and run by a visitor one step at a time, with step types the host registers
|
|
6
6
|
---
|
|
@@ -27,7 +27,7 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
27
27
|
1. Confirm the app has Keystone UI installed (`keystone_ui` in the Gemfile). easy_flow's controllers use its helpers and it is not pulled in by easy_flow. If it is missing, stop and hand off to the `keystone_ui-install` local before continuing.
|
|
28
28
|
2. Read the `keystone_ui` version in the app's `Gemfile.lock`. easy_flow's visitor pages pass each answer's info text to Keystone UI's radio cards and checkbox rows and draw a File upload step with Keystone UI's file upload control, and easy_flow is built against `keystone_ui` 0.27.0. If the app's version is older, ask the developer whether to run `bundle update keystone_ui` before continuing.
|
|
29
29
|
3. Add `gem "easy_flow"` to the host's `Gemfile` and run `bundle install`.
|
|
30
|
-
4. Run `bin/rails easy_flow:install:migrations`, then `bin/rails db:migrate`. This writes
|
|
30
|
+
4. Run `bin/rails easy_flow:install:migrations`, then `bin/rails db:migrate`. This writes seven migrations into `db/migrate` and updates `db/schema.rb`.
|
|
31
31
|
5. Ask the developer which hosts the app needs. A host is one part of the app that owns its own set of flows, and one host never sees another's flows. Ask for each host's name and the path it is served under.
|
|
32
32
|
6. Ask the developer which controller the engine should inherit from. Choosing `"ApplicationController"` gives the engine the app's own authentication methods and helpers. Create `config/initializers/easy_flow.rb` and set it on the first line:
|
|
33
33
|
|
|
@@ -45,7 +45,7 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
45
45
|
host.visitor_authorization_method = :easy_flow_visitor_permitted?
|
|
46
46
|
host.refusal_method = :refuse_flow
|
|
47
47
|
host.owner_method = :current_account
|
|
48
|
-
host.offers = %i[question checklist file_upload condition switch compare count flow_step]
|
|
48
|
+
host.offers = %i[question checklist file_upload condition switch compare compare_two_values all_or_any count wait flow_step]
|
|
49
49
|
end
|
|
50
50
|
```
|
|
51
51
|
|
|
@@ -55,7 +55,7 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
55
55
|
- `visitor_authorization_method` — a method on the base controller, called with the flow, returning true when the visitor may run it. With none set, every visitor is refused.
|
|
56
56
|
- `refusal_method` — a method on the base controller, called with the refusal error when a visitor is refused or a flow is unpublished or withdrawn. With none set, the response is `404 Not Found`.
|
|
57
57
|
- `owner_method` — a method on the base controller, called with no arguments, returning the record whose flows the current request works with, such as the signed-in account. When set, the admin pages list, create and edit only that record's flows, a visitor runs only that record's flows, and two records may each hold a flow with the same slug. With none set, every flow in the host is shared by everyone who reaches it. Ask the developer whether each host's flows belong to one record or are shared.
|
|
58
|
-
- `offers` — the step types this host's admins can add from the canvas palette, as a list of step type names. With none set, every registered step type is offered. The engine's own names are `question`, `checklist`, `file_upload`, `condition`, `switch`, `compare`, `count` and `flow_step`, `file_upload` being the File upload step, which needs the file store from step 10, `count` being the Count step, which counts how often an earlier step was answered, and `flow_step` being the Flow step, which names one of the same host's flows. A step type the app declares is named by the id it was declared with, or after its class when it is a step class, so `Steps::Notify` is `notify`. The End step is offered whether it is listed or not, and the Start step is never offered. Ask the developer which step types each host should offer.
|
|
58
|
+
- `offers` — the step types this host's admins can add from the canvas palette, as a list of step type names. With none set, every registered step type is offered. The engine's own names are `question`, `checklist`, `file_upload`, `condition`, `switch`, `compare`, `compare_two_values`, `all_or_any`, `count`, `wait` and `flow_step`, `file_upload` being the File upload step, which needs the file store from step 10, `compare_two_values` being the Compare two values step, which compares the outputs of two earlier steps, `all_or_any` being the All of or Any of step, which decides whether all or any of several such comparisons hold, `count` being the Count step, which counts how often an earlier step was answered, `wait` being the Wait step, which holds a run until a time counted from an earlier answer or until a date, and `flow_step` being the Flow step, which names one of the same host's flows. A step type the app declares is named by the id it was declared with, or after its class when it is a step class, so `Steps::Notify` is `notify`. The End step is offered whether it is listed or not, and the Start step is never offered. Ask the developer which step types each host should offer.
|
|
59
59
|
|
|
60
60
|
Each method named here must exist on the base controller. Ask the developer to point at it or write it. Do not invent its logic.
|
|
61
61
|
8. Mount the engine in `config/routes.rb`, once per host. The host name in `defaults` must match a name declared in step 7. When there is more than one mount, give each an `as:` name:
|
|
@@ -90,7 +90,8 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
90
90
|
- With no store set, a visitor who submits a file at any step of a stored run gets the app's error page, raised as `EasyFlow::NoFileStore`.
|
|
91
91
|
11. Leave `EasyFlow.check` out unless the developer asks for it. The engine already turns on `:unrouted_value`, `:unfollowed_path` and `:dead_end` at boot. Any other name raises `EasyFlow::UnknownCheck` when the app boots.
|
|
92
92
|
12. Check that the app loads Active Job (`require "rails/all"` or `require "active_job/railtie"` in `config/application.rb`) and has a queue adapter that runs jobs from the `default` queue. When a run started by a Flow step, or by a step type of the app's own that starts another flow, ends, the engine records its output in the run it was started from and queues a job on `default` that moves that run on. If no adapter runs that queue, the run that holds the step stays at it. If `config.active_job.queue_adapter` is not set for an environment, ask the developer which adapter to use there.
|
|
93
|
-
13.
|
|
93
|
+
13. Check `config.time_zone` in `config/application.rb`. A Wait step's time of day and date, and the "Hour completed" and "Weekday completed" outputs every completed step offers, are read in that zone, and with none set it is UTC. If it is not set, ask the developer which zone the app's admins mean when they write a time.
|
|
94
|
+
14. Start the server and open `<mount path>/manage/flows` for each host.
|
|
94
95
|
|
|
95
96
|
## Conventions
|
|
96
97
|
|
|
@@ -101,7 +102,12 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
101
102
|
- After publishing a flow with a checklist, check that a visitor can tick several answers and go on, and that a checklist marked required refuses to go on with nothing ticked.
|
|
102
103
|
- After upgrading easy_flow, run `bin/rails easy_flow:install:migrations` again and then `bin/rails db:migrate`. Only migrations the app does not already have are copied.
|
|
103
104
|
- After setting a host's `owner_method`, check that two different owners each see only their own flows on `<mount path>/manage/flows`.
|
|
105
|
+
- After install, check that a Condition step placed on the canvas, once its earlier step is picked, offers that step's outputs, a comparison of "is" or "is not", and the answers of the output picked. It is named after that step, the comparison and the answer, decides a true or false result, and needs no setting in the initializer beyond being listed in a host's `offers` when that host sets `offers`.
|
|
104
106
|
- After install, check that a Count step placed on the canvas, once its earlier step and answer are picked, is named "Count" followed by that step and that answer. It needs no setting in the initializer beyond being listed in a host's `offers` when that host sets `offers`.
|
|
107
|
+
- After install, check that a Compare two values step placed on the canvas offers the outputs of each earlier step picked for its two sides, a comparison of "more than", "less than", "at least", "at most" or "equal to", and an amount added to the second side. It decides a true or false result and is connected onward once for each. It needs no migration and no setting in the initializer beyond being listed in a host's `offers` when that host sets `offers`.
|
|
108
|
+
- After publishing a flow with a Compare two values step, check that a run whose answer on either side is not a number follows the connection for a false result.
|
|
109
|
+
- After install, check that an All of or Any of step placed on the canvas asks whether "all of" or "any of" its comparisons must hold, and for a list of comparisons. Each comparison offers the outputs of the earlier step picked for it, a comparison of "more than", "less than", "at least", "at most" or "equal to", an amount, and an optional second step with its output. With no second step, the output is compared with the amount alone. The step decides a true or false result and is connected onward once for each. It needs no migration and no setting in the initializer beyond being listed in a host's `offers` when that host sets `offers`.
|
|
110
|
+
- After publishing a flow with an All of or Any of step, check that a run follows the connection for a false result when the step holds no comparisons, and that a comparison whose answer is not a number counts as not holding.
|
|
105
111
|
- After install, check that a Flow step placed on the canvas offers the host's flows by title as its flow setting. With a host's `owner_method` set, it offers only that owner's flows. It needs no setting in the initializer.
|
|
106
112
|
- After choosing a flow on a Flow step, check that its canvas card reads "Runs a flow", that clicking the card opens the chosen flow's canvas under the same mount path, and that its Settings button opens the step's settings instead.
|
|
107
113
|
- After opening a chosen flow's canvas from a Flow step's card, check that its header shows a "Back to" button naming the flow it was opened from, by title or by slug when it has no title, and that the button opens that flow's canvas. A canvas opened from the flow list shows no such button.
|
|
@@ -115,7 +121,14 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
115
121
|
- After install, check that removing a flow from `<mount path>/manage/flows` shows "Flow removed." and takes the flow off the list. Removing a flow deletes its versions and its runs, and needs no setting in the initializer.
|
|
116
122
|
- After publishing a flow with a Flow step, check that while a run of it is stopped at that Flow step, removing the chosen flow is refused and the flow list shows "This flow cannot be removed while" followed by the waiting flow's title, or its slug when it has no title, and "waits on one of its runs". If no message is shown, check that the admin layout shows `flash[:alert]`.
|
|
117
123
|
- A step type of the app's own that starts another flow is treated as a Flow step by every check above: the "missing flow", "unrunnable version" and "circular" flags, the refusal to remove a flow a waiting run depends on, and the job on `default`. It runs the flow and version its own settings name, or the ones its declaration works out from its other settings, and needs no setting in the initializer beyond being listed in a host's `offers` when that host sets `offers`.
|
|
124
|
+
- A step type of the app's own whose outputs an admin names on each step offers those names wherever an earlier step's outputs are offered: on a Condition step, on each side of a Compare two values step, and in each comparison of an All of or Any of step. It needs no setting in the initializer beyond being listed in a host's `offers` when that host sets `offers`.
|
|
118
125
|
- A step type of the app's own that chooses the flow to start from the run it is in is the exception. It is never flagged "missing flow", "unrunnable version" or "circular", so a flow holding it can be published with no flow in its settings. When a run reaches it and it chooses no flow, the engine raises `EasyFlow::InnerFlowError` naming the step and starts no run, and the visitor gets the app's error page. When the run it started ends at an End step with no output, the engine records `ended` as the step's answer and the job on `default` moves the run on from it, rather than failing.
|
|
126
|
+
- Every stored run keeps each step's value together with the time the step was completed. An app upgrading from a version that kept values alone runs `bin/rails easy_flow:install:migrations` and `bin/rails db:migrate` before any stored run takes an answer. The migration wraps each value its existing runs already hold, and those values carry no completion time. Running the migration back drops every completion time.
|
|
127
|
+
- After install, check that a Compare step, a Compare two values step, or a comparison of an All of or Any of step, once its earlier step is picked, offers "Hour completed", "Weekday completed" and "Minutes since 1970 when completed" beside that step's own outputs. They need no setting in the initializer.
|
|
128
|
+
- After install, check that a Wait step placed on the canvas asks for the earlier step it counts from, minutes after it, a time of day as HH:MM and a date as YYYY-MM-DD. It needs no migration beyond the one that keeps each step's completion time with its value and no setting in the initializer beyond being listed in a host's `offers` when that host sets `offers`.
|
|
129
|
+
- After filling in a Wait step, check its name on the canvas. With a date set it is named by the date alone, and the run waits until that date. Otherwise, with a time of day set, it is named by that time followed by "after" and the step it counts from, and the run waits until that time of day first comes after the step was answered. Otherwise it is named by the minutes, as "<n> minutes after" followed by the step it counts from, and the run waits that many minutes past the answer. With none of the three set it has no name of its own, and a visitor held at it sees "Waiting for Wait."
|
|
130
|
+
- The engine queues no job for a Wait step. After publishing a flow with a Wait step, check that a visitor held at it sees "Waiting for" followed by the step's name, and that a stored run moves on when the visitor's page is loaded after its time has come. Moving a run on with no visitor present belongs to the `easy_flow-develop` local.
|
|
131
|
+
- After publishing a flow with a Wait step whose "What this keeps of a run" detail is "Nothing" or "The whole run once it finishes", check that a visitor held at it sees a "Check again" button, and that pressing it after the time has come carries their answers and completion times forward and moves them past the Wait step.
|
|
119
132
|
- After setting `EasyFlow.file_store`, publish a flow with a File upload step whose "What this keeps of a run" detail is "Every answer as it is given", upload a file as a visitor, and check that the completion page shows the name the store gives it.
|
|
120
133
|
- A File upload step takes a file only in a flow that keeps every answer as it is given. In a flow that keeps nothing or keeps the run only once it finishes, the visitor is told "A file can only be uploaded in a flow that saves its runs." and cannot go on with a file chosen.
|
|
121
134
|
- A File upload step's "Accepted file types" setting is a comma-separated list of extensions, such as `.pdf, .png`. A file of another kind is refused with a message naming the accepted kinds and is never passed to the file store.
|
|
@@ -123,4 +136,4 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
123
136
|
- After setting a host's `offers`, check that the canvas palette on that host's `<mount path>/manage/flows` lists only those step types and End.
|
|
124
137
|
- Taking a step type off a host's `offers` removes it from the palette only. Steps of that type already in the host's flows stay in them and keep running.
|
|
125
138
|
- The initializer runs once at boot, so a change to it needs a server restart.
|
|
126
|
-
- Declaring step types, including settings kept on the app's own records, serving a host's flows from the app's own controllers and routes, and reading runs, answers
|
|
139
|
+
- Declaring step types, including settings kept on the app's own records, serving a host's flows from the app's own controllers and routes, and reading runs, answers, the output a flow ended with, whether a run ended at an End step and the step a loop stopped a run at are out of scope here. They belong to the `easy_flow-develop` local.
|