easy_flow 0.35.0 → 0.37.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c21a6edf0086eee7de013dd151cd128f0bcc5d8d412e4a6c79b2a2231278855
4
- data.tar.gz: cc47c44ecbb25b1acf1854ab1cd8d66112a49fafc8c2eaa9ceaac34b631059ce
3
+ metadata.gz: 738d119d2cab4783784532361d52c24c2b2825066ad8970a4dfa03e8e0e3c405
4
+ data.tar.gz: fc233a4ea07c339f27f62f87b83ad218e771ee19bcee08a1e795cde7d1f18c10
5
5
  SHA512:
6
- metadata.gz: 87527bde8f215b3cde4ad930936c25c2174a45b16a0d5d89737e3891e090ba4a798a2b8ca615ed186bc8422d69a62e3f5a7c48b78ad97623c8380c7f5acf1d65
7
- data.tar.gz: 10969742e56b6725db6571637dee3be07d5ae6076b5e5021e725f1f3a0108e104bf27f307b6ed1bb662f60aa70389eac594ab7b62debd12c7efb6c30089d118d
6
+ metadata.gz: 95b62e84987cf90ef6eb7061165cb3a733c0a658c91901d46920bb48fcd6364d9642571b9069be6983e9c5983eaa6a97e3c98c174d34a97f83c9e20182d3b915
7
+ data.tar.gz: a83ed3214d6b9c161b3eaeac4d72f3552ac847f2d4bf7ca30685a3dc0861a299c96b52625398cd582a20d1c0e791e0ff7d30e0f33f83d2e08eb3630f42173169
@@ -15,11 +15,11 @@ module EasyFlow
15
15
  output :answer
16
16
  end
17
17
 
18
- def process(node, state)
18
+ def process(node, state, run: nil)
19
19
  raise ArgumentError, "#{node.config["data"]} is not offered by this step" unless self.class.step_type.settings.choices[:data].include?(node.config["data"])
20
20
 
21
21
  answer = ActiveRecord::Base.transaction(requires_new: true) do
22
- EasyFlow.data_source.carry_out(node.config["data"], Inputs.values_of(node, state))
22
+ EasyFlow.data_source.carry_out(node.config["data"], Inputs.values_of(node, state, run: run))
23
23
  end.as_json
24
24
  (answer.is_a?(Hash) ? answer : { "answer" => answer }).merge("result" => "passed")
25
25
  rescue => error
@@ -52,6 +52,14 @@ module EasyFlow
52
52
  records_a_value?(named) ? declared + CompletedAt.outputs : declared
53
53
  end
54
54
 
55
+ def plain_value_is?(id, output_name)
56
+ named = step(id)
57
+ type = step_type(named)
58
+ return true if type.nil? || type.outputs_for(named).empty?
59
+
60
+ type.outputs.one? && type.outputs.first.name.to_s == output_name.to_s
61
+ end
62
+
55
63
  def values_out_of(id)
56
64
  named = step(id)
57
65
  return [] unless named
@@ -8,17 +8,32 @@ module EasyFlow
8
8
  setting :value, type: :string
9
9
  setting :step, type: :previous_step, label: "Earlier step"
10
10
  setting :output, outputs_of: :step, label: "Earlier step's output"
11
+ setting :from_run_owner, type: :boolean, label: "The run's owner"
11
12
  end
12
13
  end
13
14
 
14
- def self.values_of(node, state)
15
- Array(node.config["inputs"]).to_h { |input| [ input["name"], input["step"].present? ? latest_output(input, state) : input["value"] ] }
15
+ def self.values_of(node, state, run: nil)
16
+ Array(node.config["inputs"]).to_h { |input| [ input["name"], value_of(input, state, run) ] }
16
17
  end
17
18
 
18
- def self.latest_output(input, state)
19
+ def self.value_of(input, state, run)
20
+ return run&.owner_id if input["from_run_owner"]
21
+
22
+ input["step"].present? ? latest_output(input, state, run) : input["value"]
23
+ end
24
+ private_class_method :value_of
25
+
26
+ def self.latest_output(input, state, run)
19
27
  latest_visit = state.keys.select { |key| key.split("@").first == input["step"] }.max_by { |key| key.split("@")[1].to_i }
28
+ return if unreported?(input, state[latest_visit], run)
29
+
20
30
  CompletedAt.value_of(state, {}, latest_visit, input["output"])
21
31
  end
22
32
  private_class_method :latest_output
33
+
34
+ def self.unreported?(input, recorded, run)
35
+ run && input["output"].present? && !recorded.is_a?(Hash) && !run.digest.plain_value_is?(input["step"], input["output"])
36
+ end
37
+ private_class_method :unreported?
23
38
  end
24
39
  end
@@ -1,6 +1,8 @@
1
1
  module EasyFlow
2
2
  module Progress
3
3
  class Kept
4
+ attr_reader :run
5
+
4
6
  def initialize(run)
5
7
  @run = run
6
8
  end
@@ -14,6 +14,8 @@ module EasyFlow
14
14
  false
15
15
  end
16
16
 
17
+ def run; end
18
+
17
19
  def definition
18
20
  @definition || @flow.live_definition
19
21
  end
@@ -30,7 +30,7 @@ module EasyFlow
30
30
 
31
31
  def run(progress)
32
32
  while (node = @digest.next_step(named(progress.recorded), named(progress.completed_at))) && goes_on?(node, named(progress.recorded), CompletedAt.latest(named(progress.completed_at))) && (progress.saved? || refusal_of(node).nil?)
33
- progress.record(node.id, result_of(@digest.step(node.id), named(progress.recorded)))
33
+ progress.record(node.id, result_of(@digest.step(node.id), named(progress.recorded), progress.run))
34
34
  end
35
35
  progress.start_inner(node) if node && starts_a_flow?(node)
36
36
  progress.ended unless node
@@ -99,10 +99,10 @@ module EasyFlow
99
99
  @registry.registered?(node.type) && @registry.fetch(node.type).waits? && @registry.fetch(node.type).ready?(node, state, times)
100
100
  end
101
101
 
102
- def result_of(node, state)
102
+ def result_of(node, state, run)
103
103
  return allowed(node, state).first.to_s if decided?(node, state)
104
104
 
105
- acts?(node) ? @registry.fetch(node.type).process(node, state) : true
105
+ acts?(node) ? @registry.fetch(node.type).process(node, state, run: run) : true
106
106
  end
107
107
 
108
108
  def shown(node, run = nil)
@@ -28,8 +28,12 @@ module EasyFlow
28
28
  end
29
29
 
30
30
  def behaviour(word)
31
- asks_for_times = instance_method(word).arity.abs >= 3
32
- ->(node, state, times = {}) { asks_for_times ? new.public_send(word, node, state, times) : new.public_send(word, node, state) }
31
+ asks_for_run = StepType.asks_for_run?(instance_method(word))
32
+ asks_for_times = instance_method(word).arity.abs - (asks_for_run ? 1 : 0) >= 3
33
+ lambda do |node, state, times = {}, run: nil|
34
+ arguments = asks_for_times ? [ node, state, times ] : [ node, state ]
35
+ new.public_send(word, *arguments, **(asks_for_run ? { run: run } : {}))
36
+ end
33
37
  end
34
38
 
35
39
  def register(registry = EasyFlow.registry)
@@ -4,6 +4,10 @@ module EasyFlow
4
4
  Declaration.new(id).tap { |decl| decl.instance_eval(&declaration) }.to_step_type
5
5
  end
6
6
 
7
+ def self.asks_for_run?(behaviour)
8
+ behaviour.present? && behaviour.parameters.include?([ :key, :run ])
9
+ end
10
+
7
11
  attr_reader :id, :step_name, :settings, :naming_field, :naming, :outputs, :drawn_by, :unsaved_refusal
8
12
 
9
13
  def initialize(id:, step_name:, settings:, awaits_input:, behaviour:, routing:,
@@ -40,8 +44,10 @@ module EasyFlow
40
44
  @display.arity == 1 ? @display.call(node) : @display.call(node, run)
41
45
  end
42
46
 
43
- def process(node, state)
44
- @behaviour&.call(node, state)
47
+ def process(node, state, run: nil)
48
+ return @behaviour&.call(node, state) unless StepType.asks_for_run?(@behaviour)
49
+
50
+ @behaviour.call(node, state, run: run)
45
51
  end
46
52
 
47
53
  def route(node, state, times = {})
@@ -1,3 +1,3 @@
1
1
  module EasyFlow
2
- VERSION = "0.35.0"
2
+ VERSION = "0.37.0"
3
3
  end
@@ -17,7 +17,7 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
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
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.
20
- - `EasyFlow::Inputs` — a module a step type's class includes, after `EasyFlow::Step`, to declare the list of named inputs an admin fills in, each a fixed value or an output of an earlier step. `EasyFlow::Inputs.values_of(node, state)` returns those values as a hash with string keys, reading a step reached more than once at its latest visit.
20
+ - `EasyFlow::Inputs` — a module a step type's class includes, after `EasyFlow::Step`, to declare the list of named inputs an admin fills in, each a fixed value, an output of an earlier step, or the id of the run's owner. `EasyFlow::Inputs.values_of(node, state, run: run)` returns those values as a hash with string keys, reading a step reached more than once at its latest visit.
21
21
  - `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.
22
22
  - `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.
23
23
  - `hosted_by` — class method on a `FlowsController` subclass naming the host whose flows it serves.
@@ -291,19 +291,22 @@ A Rails engine for flows an admin draws on a canvas and a visitor runs one step
291
291
 
292
292
  step_name "Script"
293
293
 
294
- def process(node, state)
295
- ScriptRunner.call(EasyFlow::Inputs.values_of(node, state))
294
+ def process(node, state, run: nil)
295
+ ScriptRunner.call(EasyFlow::Inputs.values_of(node, state, run: run))
296
296
  end
297
297
  end
298
298
  ```
299
299
 
300
300
  2. What then holds:
301
- - The step's settings on the canvas hold an `inputs` list, and each entry takes a name, a fixed value, an earlier step and that step's output.
301
+ - The step's settings on the canvas hold an `inputs` list, and each entry takes a name, a fixed value, an earlier step, that step's output and a tick box, `The run's owner`.
302
+ - An entry with `The run's owner` ticked gives the owner's id, whatever else it holds. It is stored as `"from_run_owner" => true`. It gives nothing in a flow that saves no runs or a run with no owner, and raises nothing.
303
+ - `run: nil` in `process` is optional and must be written with its default. With it the step is given the stored run, or `nil` in a flow that saves no runs. A `process` without it keeps working as `process(node, state)`.
304
+ - `EasyFlow::Inputs.values_of(node, state)` keeps working with those two arguments, and an entry with `The run's owner` ticked then gives nothing. Code outside `process` that reads a stored run's inputs passes the run itself, as `EasyFlow::Inputs.values_of(step, run.recorded.stringify_keys, run: run)`.
302
305
  - An entry that names an earlier step reads that step's output, at its latest visit when a loop reached it more than once. Any other entry gives its fixed value.
303
306
  - `state` in `process` still holds every visit under its own key, so a step type that adds up every visit keeps working.
304
307
  - An earlier step that recorded a plain value gives that value for whichever output the entry names.
305
308
  - `process` is not given completion times, so an entry naming "Hour completed", "Weekday completed" or "Minutes since 1970 when completed" gives nothing.
306
- 3. Publish a flow set to save each step with a loop back to a question before the step, answer it twice, and check the step reads the second answer.
309
+ 3. Publish a flow set to save each step with a loop back to a question before the step, answer it twice, and check the step reads the second answer. Give a stored run an owner, tick `The run's owner` on an input, and check the step reads the owner's id.
307
310
 
308
311
  ### Keep a setting on one of the app's records
309
312
 
@@ -77,7 +77,7 @@ easy_flow declares no commands of its own for this local. Its surface is split b
77
77
  - **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 the admin picks which of that step's outputs to read, as described under chosen output. 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. The second step may be left empty, and the first answer is then checked against the amount alone, or against 0 when the amount is blank too. When either answer is missing or is not a number, the step decides false.
78
78
  - **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 which of its outputs to read, 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.
79
79
  - **Read data and Write data** — built-in steps that ask the app to carry out one thing from its list of what a flow can read or write, in a nested transaction, then follow the `passed` or the `failed` connection. The step records `result`, `reason` on a failure, and the app's answer: a hash as named outputs, anything else as `answer`. The admin lists the answer's names under "Values it reports" so later steps can pick them. Both need 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 "Data can only be read or written in a flow that saves its runs." when the run reaches the step and cannot go on, and the app is not called.
80
- - **Input** — a named value a Read data or Write data step passes to the app, either a fixed value or an output of an earlier step. A step reached more than once gives its most recent output.
80
+ - **Input** — a named value a Read data or Write data step passes to the app, either a fixed value, an output of an earlier step, or the id of the run's owner, which an admin chooses by ticking `The run's owner`. A step reached more than once gives its most recent output. A flow that saves no runs, or a run with no owner, gives no value for the run's owner, and a run started by a Flow step has its parent's owner.
81
81
  - **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.
82
82
  - **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.
83
83
  - **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.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: easy_flow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.35.0
4
+ version: 0.37.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - tylercschneider