easy_flow 0.4.6 → 0.4.7
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/controllers/easy_flow/flows_controller.rb +21 -2
- data/app/models/easy_flow/step_type/declaration.rb +5 -1
- data/app/models/easy_flow/step_type.rb +6 -1
- data/app/models/easy_flow/steps/question.rb +2 -0
- data/app/views/easy_flow/flows/step.html.erb +5 -0
- data/lib/easy_flow/version.rb +1 -1
- data/the_local/agents/easy_flow-develop.md +15 -4
- data/the_local/agents/easy_flow-info.md +10 -5
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 92d52fb553f82cf1f206f0257d5d4fdaa0718ec6197ccabf016e6b20107371da
|
|
4
|
+
data.tar.gz: 66a5f5930c064d692b3c8b410174119e697c97b6a9ec47e8e9bffccd7abf7f5c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a982c31c2a100d8f74b6d75346200f68c1aad256e2ff87b24267fc509fca46ef19da19fb169f48798d4f33cf44d6dee011164f2a7df3dc4107b9b1cdbe081b43
|
|
7
|
+
data.tar.gz: 97ee4e459a2c25a0ef77b5433e056cfa83ccc40a9b0cf73abad201b9fcec9d5ea6e8079396d6d7ad4ed3ec904d7035c103b9ba045fd81d394096c339903cda80
|
|
@@ -17,6 +17,7 @@ module EasyFlow
|
|
|
17
17
|
@answers = @progress.recorded
|
|
18
18
|
@question = @guide.next_step(@answers)
|
|
19
19
|
@drawing = @guide.drawing_at(@answers)
|
|
20
|
+
flash.now[:alert] = @refused if @refused
|
|
20
21
|
return render :step if @question
|
|
21
22
|
|
|
22
23
|
render_completion
|
|
@@ -103,12 +104,30 @@ module EasyFlow
|
|
|
103
104
|
end
|
|
104
105
|
|
|
105
106
|
def submitted_answers
|
|
106
|
-
params.fetch(:answers, {}).permit(*@guide.steps.map(&:id)).to_h.symbolize_keys
|
|
107
|
+
answers = params.fetch(:answers, {}).permit(*@guide.steps.map(&:id)).to_h.symbolize_keys
|
|
108
|
+
asked = @guide.step(params[:asked].to_s)
|
|
109
|
+
return answers unless asked
|
|
110
|
+
|
|
111
|
+
answer = answers.fetch(asked.id.to_sym, "")
|
|
112
|
+
@refused = answer_problem(asked, answer)
|
|
113
|
+
@refused ? answers.except(asked.id.to_sym) : answers.merge(asked.id.to_sym => answer)
|
|
107
114
|
end
|
|
108
115
|
|
|
109
116
|
def record_submitted
|
|
110
117
|
id, value = params.fetch(:answers, {}).permit(*asked).to_h.first
|
|
111
|
-
|
|
118
|
+
id ||= params[:asked].presence_in(asked)
|
|
119
|
+
return if id.nil?
|
|
120
|
+
|
|
121
|
+
problem = answer_problem(runner_for(run.pinned_definition).step(id.to_s), value)
|
|
122
|
+
return flash[:alert] = problem if problem
|
|
123
|
+
|
|
124
|
+
progress.record(id, value.to_s)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def answer_problem(step, answer)
|
|
128
|
+
return unless step && EasyFlow.registry.registered?(step.type)
|
|
129
|
+
|
|
130
|
+
EasyFlow.registry.fetch(step.type).answer_problem(step, answer)
|
|
112
131
|
end
|
|
113
132
|
|
|
114
133
|
def asked
|
|
@@ -40,6 +40,10 @@ module EasyFlow
|
|
|
40
40
|
@routing = routing
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
def answer_check(&check)
|
|
44
|
+
@answer_check = check
|
|
45
|
+
end
|
|
46
|
+
|
|
43
47
|
def step_name(value)
|
|
44
48
|
@step_name = value
|
|
45
49
|
end
|
|
@@ -103,7 +107,7 @@ module EasyFlow
|
|
|
103
107
|
StepType.new(id: @id, step_name: @step_name, settings: settings, awaits_input: @awaits_input,
|
|
104
108
|
ends_here: @ends_here, begins_here: @begins_here, behaviour: @behaviour, routing: @routing,
|
|
105
109
|
display: @display, drawn_by: @drawn_by, naming_field: @naming_field, naming: @naming,
|
|
106
|
-
outputs: @declared_outputs)
|
|
110
|
+
outputs: @declared_outputs, answer_check: @answer_check)
|
|
107
111
|
end
|
|
108
112
|
|
|
109
113
|
def settings
|
|
@@ -8,7 +8,7 @@ module EasyFlow
|
|
|
8
8
|
|
|
9
9
|
def initialize(id:, step_name:, settings:, awaits_input:, behaviour:, routing:,
|
|
10
10
|
ends_here: false, begins_here: false, display: nil, drawn_by: nil,
|
|
11
|
-
naming_field: nil, naming: nil, outputs: [])
|
|
11
|
+
naming_field: nil, naming: nil, outputs: [], answer_check: nil)
|
|
12
12
|
@id = id
|
|
13
13
|
@step_name = step_name
|
|
14
14
|
@settings = settings
|
|
@@ -22,6 +22,7 @@ module EasyFlow
|
|
|
22
22
|
@naming_field = naming_field
|
|
23
23
|
@naming = naming
|
|
24
24
|
@outputs = outputs
|
|
25
|
+
@answer_check = answer_check
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def display_of(node)
|
|
@@ -36,6 +37,10 @@ module EasyFlow
|
|
|
36
37
|
@routing&.call(node, state)
|
|
37
38
|
end
|
|
38
39
|
|
|
40
|
+
def answer_problem(node, value)
|
|
41
|
+
@answer_check&.call(node, value)
|
|
42
|
+
end
|
|
43
|
+
|
|
39
44
|
def name_of(node)
|
|
40
45
|
@naming&.call(node).presence || node.config[naming_field.to_s].presence
|
|
41
46
|
end
|
|
@@ -7,6 +7,7 @@ module EasyFlow
|
|
|
7
7
|
|
|
8
8
|
setting :question, type: :string
|
|
9
9
|
setting :category, type: :string
|
|
10
|
+
setting :required, type: :boolean
|
|
10
11
|
setting :answers, type: :list, required: true do
|
|
11
12
|
setting :value, type: :string
|
|
12
13
|
setting :label, type: :string
|
|
@@ -17,6 +18,7 @@ module EasyFlow
|
|
|
17
18
|
|
|
18
19
|
names_by :question
|
|
19
20
|
awaits_input
|
|
21
|
+
answer_check { |node, value| "Fill this in to go on." if node.config["required"] && value.blank? }
|
|
20
22
|
|
|
21
23
|
displays_by { |node| Asked.new(id: node.id.to_sym, text: asked(node.config), choices: choices_in(node)) }
|
|
22
24
|
|
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
<%= ui_progress(value: @answers.size, max: [ @guide.steps_on_path(@answers).size, 1 ].max, label: "Question #{@answers.size + 1}") %>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
|
+
<% if flash[:alert] %>
|
|
11
|
+
<div class="mb-6"><%= ui_alert(message: flash[:alert], type: :error) %></div>
|
|
12
|
+
<% end %>
|
|
13
|
+
|
|
10
14
|
<%= form_with url: step_form[:url], method: step_form[:method], data: { turbo: false } do %>
|
|
15
|
+
<%= hidden_field_tag :asked, @question.id %>
|
|
11
16
|
<% if carries_answers? %>
|
|
12
17
|
<% @answers.each do |key, value| %>
|
|
13
18
|
<%= hidden_field_tag "answers[#{key}]", value %>
|
data/lib/easy_flow/version.rb
CHANGED
|
@@ -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, computes a value from earlier answers, or picks the next branch), offering an admin a step setting whose options are read from the app's own records, serving a host's flows from the app's own controller and routes, acting when a visitor finishes a flow, and reading a run's recorded answers and question labels — MUST BE USED instead of hand-rolling questionnaire steps, branching logic, number comparisons, hard-coded setting options, 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, computes a value from earlier answers, or picks the next branch), 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, serving a host's flows from the app's own controller and routes, acting when a visitor finishes a flow, and reading a run's recorded answers and question labels — MUST BE USED instead of hand-rolling questionnaire steps, branching logic, number comparisons, hard-coded setting options, answer validation, 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,7 +9,7 @@ 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 six: start (`:start`), end (`:terminal`), question (`:question`, a question with a list of answers), and three that pick a branch from an earlier answer with no code — condition (`:condition`, is or is not a chosen value), switch (`:switch`, follows the connection labelled with the answer) and compare (`:compare`, reads the answer as a number and checks it by more than, less than, at least or at most against an amount). Before writing a step type to branch on an answer or a number, ask the developer whether an admin placing one of those three on the canvas is enough. 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, 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.
|
|
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 six: start (`:start`), end (`:terminal`), question (`:question`, a question with a list of answers, which the admin can mark required so a blank answer is refused), and three that pick a branch from an earlier answer with no code — condition (`:condition`, is or is not a chosen value), switch (`:switch`, follows the connection labelled with the answer) and compare (`:compare`, reads the answer as a number and checks it by more than, less than, at least or at most against an amount). Before writing a step type to branch on an answer or a number, ask the developer whether an admin placing one of those three on the canvas is enough. 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, 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
|
|
|
@@ -77,7 +77,8 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
77
77
|
- `setting :name, type:, label:, options:, required:, limit:, check:, from:, outputs_of:` — 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`, 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`. `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.
|
|
78
78
|
- `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.
|
|
79
79
|
- `names_by :setting` or `names_by { |node| ... }` — what the step is called on the canvas, from a setting or computed.
|
|
80
|
-
- `awaits_input` — the visitor is shown this step and
|
|
80
|
+
- `awaits_input` — the visitor is shown this step and submits an answer to it.
|
|
81
|
+
- `answer_check { |node, value| ... }` — checks a submitted answer before it is recorded. `value` is the submitted string, or `nil` when the input sent nothing. Return a message to refuse the answer, or `nil` to accept it. Without it every answer is accepted, including a blank one.
|
|
81
82
|
- `ends_here` / `begins_here` — marks the type as an end or a start of a flow.
|
|
82
83
|
- `displays_by { |node| ... }` — builds the object handed to the step's partial as the local `step`. Without it the partial receives the node itself.
|
|
83
84
|
- `drawn_by "<partial>"` — the partial that draws this type for a visitor. Without it the host's default drawing is used, which expects `step.id`, `step.text` and `step.choices`, so a type with its own display shape needs its own partial.
|
|
@@ -105,7 +106,16 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
105
106
|
<%= number_field_tag "answers[#{step.id}]", nil, in: 1..step.scale %>
|
|
106
107
|
```
|
|
107
108
|
|
|
109
|
+
The input must submit one value. An input that submits an array or a hash, such as checkboxes named `answers[<step id>][]`, is dropped and treated as blank.
|
|
110
|
+
|
|
108
111
|
An answer is recorded as the string the visitor submitted. A compare step still reads a typed answer such as `"12"` as the number it spells, and an answer that is missing or is not a number makes it follow its `false` connection.
|
|
112
|
+
|
|
113
|
+
A blank answer is recorded as `""` and the visitor moves on, unless the type's `answer_check` refuses it. 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:
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
setting :required, type: :boolean
|
|
117
|
+
answer_check { |node, value| "Fill this in to go on." if node.config["required"] && value.blank? }
|
|
118
|
+
```
|
|
109
119
|
7. Restart the server, open a flow on the canvas and check the type is offered, its settings show, and a preview walks through it.
|
|
110
120
|
|
|
111
121
|
### Serve a host's flows from the app's own controller
|
|
@@ -175,6 +185,7 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
|
|
|
175
185
|
- A type that routes declares the values its output takes, or the canvas offers no connections to label.
|
|
176
186
|
- 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.
|
|
177
187
|
- Always look flows and runs up through a host.
|
|
178
|
-
- A step's input field is named `answers[<step id>]
|
|
188
|
+
- A step's input field is named `answers[<step id>]` and submits one value. Any other name is ignored, and an array or hash is treated as blank.
|
|
189
|
+
- 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 one as `""`.
|
|
179
190
|
- A `FlowsController` subclass needs all four named routes for its prefix; a missing one raises when the visitor is linked or redirected to it.
|
|
180
191
|
- The engine installs, mounts and configures hosts, layouts, the default drawing and checks through `easy_flow-install`, not here.
|
|
@@ -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, hosts, runs, the built-in step types including branching on an answer or comparing a number, step types the host app registers, and the settings an admin fills in, including picks whose options come from the app's data.
|
|
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, hosts, runs, the built-in step types including questions that can require an answer and branching on an answer or comparing a number, step types the host app registers and the checks they make on an answer, and the settings an admin fills in, including picks whose options come from the app's data.
|
|
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
|
---
|
|
@@ -23,8 +23,9 @@ easy_flow declares no commands of its own for this local. Its surface is split b
|
|
|
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, use **easy_flow-install**.
|
|
26
|
-
- To add a step type, put a flow on the app's own pages, or act on what a visitor answered, use **easy_flow-develop**.
|
|
26
|
+
- To add a step type, give a step type its own rule for refusing an answer, put a flow on the app's own pages, or act on what a visitor answered, use **easy_flow-develop**.
|
|
27
27
|
- 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.
|
|
28
|
+
- To make a visitor answer a question before going on, no code is needed: an admin marks that question step as required on the canvas.
|
|
28
29
|
- If the question is only what a word below means, this page is the answer.
|
|
29
30
|
|
|
30
31
|
## Conventions
|
|
@@ -35,16 +36,20 @@ easy_flow declares no commands of its own for this local. Its surface is split b
|
|
|
35
36
|
- **Canvas** — the admin screen where steps are added, configured, moved, removed and connected, with undo and redo. Publishing happens there.
|
|
36
37
|
- **Preview** — an admin walking the current flow without starting a stored run.
|
|
37
38
|
- **Step type** — what a kind of step is: its display name, its settings (the fields an admin fills in), its outputs (the values it records), and whether it waits for the visitor or acts on its own. A step that acts on its own may record a value worked out from the answers so far, pick which connection to follow, or both.
|
|
39
|
+
- **Answer check** — a rule a step type may carry that looks at a visitor's answer and, when the answer is not acceptable, returns the message the visitor sees. A step type with no such rule accepts every answer.
|
|
38
40
|
- **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, 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.
|
|
39
41
|
- **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.
|
|
40
42
|
- **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.
|
|
41
43
|
- **Start** and **End** — the built-in step types that begin and finish a flow.
|
|
42
|
-
- **Question** — the built-in step type that asks the visitor: a question text and a list of answers, each with a value, a label and a weight.
|
|
44
|
+
- **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 and a weight.
|
|
45
|
+
- **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."
|
|
43
46
|
- **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.
|
|
44
47
|
- **Switch** — a built-in branching step that follows the connection labelled with an earlier step's answer.
|
|
45
48
|
- **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.
|
|
46
|
-
- **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.
|
|
49
|
+
- **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.
|
|
47
50
|
- **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, and how a refusal is answered. Flows are always looked up through a host, so one host never sees another's flows.
|
|
48
51
|
- **Run** — one visitor's pass through a flow. A run is pinned to the version that was live when it started, so publishing a new version does not change a run already under way. It records each answer by step id, and going back discards the last answer on the path.
|
|
49
52
|
- **Answers** — the recorded state of a run, keyed by step id. The next step is always worked out from these answers and the connections in the pinned version.
|
|
50
|
-
- **
|
|
53
|
+
- **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.
|
|
54
|
+
- **Blank answer** — a step the visitor leaves blank. On a step whose answer check does not refuse it, such as a question that is not required, the blank is recorded and the visitor moves on to the next step.
|
|
55
|
+
- **Checks** — warnings on a flow's shape: an answer value no connection routes, a path nothing follows, and a step that leads nowhere. The engine turns all three on by default. These are separate from answer checks, which look at what a visitor submits.
|