alembic 0.1.0 → 0.3.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.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +36 -22
  3. data/app/assets/builds/alembic/page_builder.css +1 -1
  4. data/app/assets/builds/alembic/page_builder.js +3 -3
  5. data/app/controllers/alembic/application_controller.rb +1 -16
  6. data/app/controllers/alembic/flows_controller.rb +6 -90
  7. data/app/controllers/alembic/manage/base_controller.rb +2 -0
  8. data/app/controllers/alembic/manage/flows_controller.rb +12 -44
  9. data/app/controllers/alembic/manage/pages_controller.rb +8 -1
  10. data/app/controllers/alembic/manage/previews_controller.rb +4 -19
  11. data/app/controllers/alembic/pages_controller.rb +19 -0
  12. data/app/controllers/concerns/alembic/summarizes.rb +59 -0
  13. data/app/helpers/alembic/application_helper.rb +2 -2
  14. data/app/helpers/alembic/pages_helper.rb +40 -0
  15. data/app/helpers/alembic/result_blocks_helper.rb +45 -0
  16. data/app/models/alembic/flow/definition_summary.rb +10 -0
  17. data/app/models/alembic/flow/run_summary.rb +10 -0
  18. data/app/models/alembic/flow/summaries.rb +44 -5
  19. data/app/models/alembic/flow/summary_version.rb +1 -1
  20. data/app/models/alembic/outputs/band.rb +1 -1
  21. data/app/models/alembic/outputs/grouped.rb +1 -1
  22. data/app/models/alembic/outputs/lowest.rb +5 -3
  23. data/app/models/alembic/outputs/percentage.rb +1 -1
  24. data/app/models/alembic/outputs/weighted_sum.rb +1 -1
  25. data/app/models/alembic/page/drawing.rb +17 -0
  26. data/app/models/alembic/page/options.rb +53 -0
  27. data/app/models/alembic/page/version.rb +13 -0
  28. data/app/models/alembic/page.rb +32 -0
  29. data/app/models/alembic/result_blocks.rb +29 -0
  30. data/app/models/alembic/summary/report.rb +1 -1
  31. data/app/models/alembic/summary/result.rb +1 -1
  32. data/app/views/alembic/blocks/_answers.html.erb +11 -0
  33. data/app/views/alembic/blocks/_band.html.erb +6 -0
  34. data/app/views/alembic/blocks/_categories.html.erb +11 -0
  35. data/app/views/alembic/blocks/_lead.html.erb +15 -0
  36. data/app/views/alembic/blocks/_score.html.erb +7 -0
  37. data/app/views/alembic/blocks/_weakest.html.erb +12 -0
  38. data/app/views/alembic/flows/complete.html.erb +12 -6
  39. data/app/views/alembic/flows/show.html.erb +6 -6
  40. data/app/views/alembic/flows/summary_page.html.erb +5 -0
  41. data/app/views/alembic/manage/flows/edit.html.erb +16 -10
  42. data/app/views/alembic/manage/pages/_block_content.html.erb +1 -1
  43. data/app/views/alembic/manage/pages/index.html.erb +4 -1
  44. data/app/views/alembic/manage/pages/show.html.erb +7 -1
  45. data/app/views/alembic/pages/show.html.erb +5 -0
  46. data/config/routes.rb +8 -19
  47. data/db/migrate/20260825224538_remove_published_version_from_alembic_diagnostics.rb +21 -1
  48. data/db/migrate/20260922120000_add_slug_to_alembic_pages.rb +6 -0
  49. data/db/migrate/20260922130000_create_alembic_page_versions.rb +16 -0
  50. data/db/migrate/20260924220000_move_flows_onto_easy_flow.rb +166 -0
  51. data/db/migrate/20260930120000_add_summary_page_to_alembic_flow_definition_summaries.rb +6 -0
  52. data/lib/alembic/engine.rb +6 -11
  53. data/lib/alembic/version.rb +1 -1
  54. data/lib/alembic.rb +34 -6
  55. metadata +45 -71
  56. data/app/assets/builds/alembic/canvas.js +0 -1
  57. data/app/controllers/alembic/manage/canvas_controller.rb +0 -183
  58. data/app/controllers/alembic/manage/definitions_controller.rb +0 -15
  59. data/app/controllers/alembic/manage/versions_controller.rb +0 -22
  60. data/app/controllers/concerns/alembic/manage/draws_canvas.rb +0 -35
  61. data/app/javascript/alembic/canvas/Canvas.jsx +0 -197
  62. data/app/javascript/alembic/canvas/Connector.jsx +0 -30
  63. data/app/javascript/alembic/canvas/ConnectorLayer.jsx +0 -31
  64. data/app/javascript/alembic/canvas/Control.jsx +0 -42
  65. data/app/javascript/alembic/canvas/Inspector.jsx +0 -44
  66. data/app/javascript/alembic/canvas/Panel.jsx +0 -67
  67. data/app/javascript/alembic/canvas/Placeholder.jsx +0 -24
  68. data/app/javascript/alembic/canvas/Port.jsx +0 -16
  69. data/app/javascript/alembic/canvas/Records.jsx +0 -34
  70. data/app/javascript/alembic/canvas/StepCard.jsx +0 -57
  71. data/app/javascript/alembic/canvas/Toolbar.jsx +0 -19
  72. data/app/javascript/alembic/canvas/TypePicker.jsx +0 -21
  73. data/app/javascript/alembic/canvas/changes.js +0 -5
  74. data/app/javascript/alembic/canvas/choices.js +0 -4
  75. data/app/javascript/alembic/canvas/flow.js +0 -7
  76. data/app/javascript/alembic/canvas/flowSender.js +0 -22
  77. data/app/javascript/alembic/canvas/ids.js +0 -7
  78. data/app/javascript/alembic/canvas/rows.js +0 -2
  79. data/app/javascript/alembic/canvas/styles.js +0 -3
  80. data/app/javascript/alembic/canvas/useConnectors.js +0 -75
  81. data/app/javascript/alembic/canvas/useFlow.js +0 -17
  82. data/app/javascript/alembic/canvas.jsx +0 -13
  83. data/app/models/alembic/asked.rb +0 -3
  84. data/app/models/alembic/choice.rb +0 -7
  85. data/app/models/alembic/flow/admission.rb +0 -19
  86. data/app/models/alembic/flow/canvas.rb +0 -167
  87. data/app/models/alembic/flow/change.rb +0 -18
  88. data/app/models/alembic/flow/condition.rb +0 -24
  89. data/app/models/alembic/flow/definition.rb +0 -132
  90. data/app/models/alembic/flow/digest.rb +0 -116
  91. data/app/models/alembic/flow/document.rb +0 -131
  92. data/app/models/alembic/flow/drawing.rb +0 -15
  93. data/app/models/alembic/flow/edge.rb +0 -9
  94. data/app/models/alembic/flow/edit_history.rb +0 -75
  95. data/app/models/alembic/flow/layout.rb +0 -88
  96. data/app/models/alembic/flow/name.rb +0 -15
  97. data/app/models/alembic/flow/node.rb +0 -5
  98. data/app/models/alembic/flow/output.rb +0 -23
  99. data/app/models/alembic/flow/progress/kept.rb +0 -38
  100. data/app/models/alembic/flow/progress/loose.rb +0 -43
  101. data/app/models/alembic/flow/progress.rb +0 -11
  102. data/app/models/alembic/flow/registry.rb +0 -25
  103. data/app/models/alembic/flow/run.rb +0 -54
  104. data/app/models/alembic/flow/runner.rb +0 -65
  105. data/app/models/alembic/flow/settings.rb +0 -80
  106. data/app/models/alembic/flow/start.rb +0 -13
  107. data/app/models/alembic/flow/step.rb +0 -42
  108. data/app/models/alembic/flow/step_type/declaration.rb +0 -118
  109. data/app/models/alembic/flow/step_type.rb +0 -69
  110. data/app/models/alembic/flow/switch.rb +0 -17
  111. data/app/models/alembic/flow/terminal.rb +0 -13
  112. data/app/models/alembic/flow/validator.rb +0 -171
  113. data/app/models/alembic/flow/version.rb +0 -26
  114. data/app/models/alembic/flow/violation.rb +0 -9
  115. data/app/models/alembic/flow.rb +0 -44
  116. data/app/models/alembic/runner.rb +0 -13
  117. data/app/models/alembic/steps/question.rb +0 -52
  118. data/app/views/alembic/flow/steps/_choosing.html.erb +0 -9
  119. data/app/views/alembic/flows/step.html.erb +0 -27
  120. data/app/views/alembic/manage/definitions/edit.html.erb +0 -13
  121. data/app/views/alembic/manage/flows/index.html.erb +0 -32
  122. data/app/views/alembic/manage/flows/show.html.erb +0 -26
  123. data/app/views/alembic/manage/versions/index.html.erb +0 -41
@@ -1,2 +0,0 @@
1
- export const amended = (rows, index, name, next) =>
2
- rows.map((row, at) => (at === index ? { ...row, [name]: next } : row))
@@ -1,3 +0,0 @@
1
- export const CARD = 240
2
- export const GAP_X = 40
3
- export const GAP_Y = 64
@@ -1,75 +0,0 @@
1
- import { useLayoutEffect, useState } from "react"
2
-
3
- export const anchor = (box, side, frame, surface) => {
4
- const left = box.left - frame.left + surface.scrollLeft
5
- const top = box.top - frame.top + surface.scrollTop
6
-
7
- if (side === "left") return { x: left, y: top + box.height / 2 }
8
- if (side === "right") return { x: left + box.width, y: top + box.height / 2 }
9
- if (side === "top") return { x: left + box.width / 2, y: top }
10
- return { x: left + box.width / 2, y: top + box.height }
11
- }
12
-
13
- export const shy = (point, side) => {
14
- if (side === "top") return { x: point.x, y: point.y - 3 }
15
- if (side === "left") return { x: point.x - 3, y: point.y }
16
- if (side === "right") return { x: point.x + 3, y: point.y }
17
- return { x: point.x, y: point.y + 3 }
18
- }
19
-
20
- export const drawn = (edge, a, b, lane, detour) => {
21
- const path = {
22
- straight: `M ${a.x} ${a.y} L ${b.x} ${b.y}`,
23
- turn: `M ${a.x} ${a.y} H ${b.x} V ${b.y}`,
24
- lane: `M ${a.x} ${a.y} V ${lane} H ${b.x} V ${b.y}`,
25
- detour: `M ${a.x} ${a.y} H ${detour} V ${b.y} H ${b.x}`
26
- }[edge.route]
27
-
28
- return {
29
- ...edge, path,
30
- midX: edge.route === "detour" ? detour : (a.x + b.x) / 2,
31
- midY: edge.route === "lane" ? lane : (a.y + b.y) / 2
32
- }
33
- }
34
-
35
- const useConnectors = (flow, surface, cards, watching) => {
36
- const [ links, setLinks ] = useState([])
37
- const [ extent, setExtent ] = useState({ width: "100%", height: "100%" })
38
-
39
- useLayoutEffect(() => {
40
- const measure = () => {
41
- const frame = surface.current?.getBoundingClientRect()
42
- if (!frame) return
43
-
44
- setExtent({ width: surface.current.scrollWidth, height: surface.current.scrollHeight })
45
-
46
- setLinks(flow.edges.flatMap((edge) => {
47
- const fromBox = cards.current[edge.source]?.getBoundingClientRect()
48
- const toBox = cards.current[edge.target]?.getBoundingClientRect()
49
- if (!fromBox || !toBox) return []
50
-
51
- const a = anchor(fromBox, edge.leaves, frame, surface.current)
52
- const b = shy(anchor(toBox, edge.enters, frame, surface.current), edge.enters)
53
- const lane = (anchor(fromBox, "bottom", frame, surface.current).y + anchor(toBox, "top", frame, surface.current).y) / 2
54
- const detour = edge.leaves === "left" ? Math.min(a.x, b.x) - 28 : Math.max(a.x, b.x) + 28
55
-
56
- return [ drawn(edge, a, b, lane, detour) ]
57
- }))
58
- }
59
-
60
- measure()
61
- window.addEventListener("resize", measure)
62
-
63
- const watcher = new ResizeObserver(measure)
64
- if (surface.current) watcher.observe(surface.current)
65
-
66
- return () => {
67
- window.removeEventListener("resize", measure)
68
- watcher.disconnect()
69
- }
70
- }, watching)
71
-
72
- return { links, extent }
73
- }
74
-
75
- export default useConnectors
@@ -1,17 +0,0 @@
1
- import { useMemo, useState } from "react"
2
- import { createFlowSender } from "./flowSender"
3
-
4
- const useFlow = (base, token, initial) => {
5
- const [ flow, setFlow ] = useState(initial)
6
- const [ error, setError ] = useState(null)
7
- const [ notice, setNotice ] = useState(null)
8
-
9
- const send = useMemo(
10
- () => createFlowSender({ base, token, fetch: (...request) => window.fetch(...request), onFlow: setFlow, onError: setError, onNotice: setNotice }),
11
- [ base, token ]
12
- )
13
-
14
- return { flow, error, notice, send }
15
- }
16
-
17
- export default useFlow
@@ -1,13 +0,0 @@
1
- import { createRoot } from "react-dom/client"
2
- import { register } from "keystone_ui-react/src/registry.js"
3
- import { startMounting } from "keystone_ui-react/src/mounting.js"
4
- import Canvas from "./canvas/Canvas"
5
-
6
- register("alembic/flow-editor", Canvas)
7
-
8
- document.addEventListener("alembic:flow-named", (event) => {
9
- const heading = document.querySelector("[data-flow-heading]")
10
- if (heading) heading.textContent = event.detail
11
- })
12
-
13
- startMounting(document, createRoot)
@@ -1,3 +0,0 @@
1
- module Alembic
2
- Asked = Data.define(:id, :text, :choices)
3
- end
@@ -1,7 +0,0 @@
1
- module Alembic
2
- Choice = Data.define(:value, :label, :hint) do
3
- def initialize(value:, label: nil, hint: nil)
4
- super(value: value, label: label.presence || value, hint: hint)
5
- end
6
- end
7
- end
@@ -1,19 +0,0 @@
1
- module Alembic
2
- module Flow
3
- module Admission
4
- def self.of(diagnostic, permitted:)
5
- raise NotPublished if diagnostic.nil? || diagnostic.live_definition.blank?
6
- raise NotPermitted unless permitted && diagnostic.available?
7
-
8
- diagnostic
9
- end
10
-
11
- def self.of_run(run, permitted: true)
12
- raise Withdrawn if run.definition_version.withdrawn?
13
- raise NotPermitted unless permitted && run.flow.available?
14
-
15
- run
16
- end
17
- end
18
- end
19
- end
@@ -1,167 +0,0 @@
1
- module Alembic
2
- module Flow
3
- class Canvas
4
- def initialize(document, registry: Flow.registry)
5
- @document = document
6
- @registry = registry
7
- end
8
-
9
- def to_h
10
- { "nodes" => nodes, "edges" => edges, "palette" => palette, "violations" => violations }
11
- end
12
-
13
- private
14
-
15
- def nodes
16
- @document.nodes.map { |node| drawn_step(node) } + waiting.map { |gap| drawn_placeholder(gap) }
17
- end
18
-
19
- def drawn_step(node)
20
- { "id" => node.id, "type" => node.type, "label" => label_for(node),
21
- "config" => node.config, "ports" => [], "ends_here" => ends_here?(node), "begins_here" => begins_here?(node),
22
- "loose" => loose?(node), "choices" => choices_for(node), "placeholder" => false, **placed[node.id] }
23
- end
24
-
25
- def drawn_placeholder(gap)
26
- { "id" => gap[:id], "type" => "placeholder", "label" => gap[:on], "config" => {}, "ports" => [],
27
- "ends_here" => false, "begins_here" => false, "loose" => false, "choices" => {},
28
- "placeholder" => true, "from" => gap[:from], "on" => gap[:on], **placed[gap[:id]] }
29
- end
30
-
31
- def edges
32
- drawn.edges.each_with_index.map do |edge, index|
33
- { "id" => "#{edge.from}-#{edge.to}-#{index}", "source" => edge.from, "target" => edge.to, "label" => edge.on&.to_s,
34
- "placeholder" => waiting.any? { |gap| gap[:id] == edge.to } }.merge(routing(edge, placed))
35
- end
36
- end
37
-
38
- def placed
39
- @placed ||= Layout.new(drawn).positions
40
- end
41
-
42
- def drawn
43
- @drawn ||= Document.new(@document.to_h.merge(
44
- "nodes" => Array(@document.to_h["nodes"]) + waiting.map { |gap| { "id" => gap[:id], "type" => "placeholder" } },
45
- "edges" => Array(@document.to_h["edges"]) + waiting.map { |gap| { "from" => gap[:from], "to" => gap[:id], "on" => gap[:on] } }
46
- ), registry: @registry)
47
- end
48
-
49
- def waiting
50
- @waiting ||= @document.nodes.flat_map { |node| gaps_after(node) }
51
- end
52
-
53
- def gaps_after(node)
54
- wired = @document.edges_from(node.id).map { |edge| edge.on.to_s }
55
- results = digest.routing_values(node.id)
56
- return [ { id: "#{node.id}--", from: node.id, on: nil } ] if results.empty? && leads_nowhere?(node)
57
-
58
- results.reject { |value| wired.include?(value) }
59
- .map { |value| { id: "#{node.id}--#{value}", from: node.id, on: value } }
60
- end
61
-
62
- def leads_nowhere?(node)
63
- !ends_here?(node) && !loose?(node) && @document.edges_from(node.id).none?
64
- end
65
-
66
- def routing(edge, placed)
67
- from = placed[edge.from]
68
- to = placed[edge.to]
69
- return { "leaves" => "bottom", "enters" => "top", "route" => "straight" } unless from && to
70
-
71
- return alongside_routing(from, to) unless to["row"] > from["row"]
72
-
73
- { "leaves" => "bottom", "enters" => "top",
74
- "route" => to["column"] == from["column"] ? "straight" : "lane" }
75
- end
76
-
77
- def alongside_routing(from, to)
78
- leftward = to["column"] < from["column"]
79
-
80
- { "leaves" => leftward ? "left" : "right", "enters" => leftward ? "right" : "left",
81
- "route" => to["row"] == from["row"] ? "straight" : "detour" }
82
- end
83
-
84
- def palette
85
- @registry.step_types.reject(&:begins_here?).map do |step_type|
86
- { "type" => step_type.id.to_s, "label" => step_type.step_name,
87
- "fields" => step_type.settings.fields.transform_keys(&:to_s).transform_values(&:to_s),
88
- "labels" => step_type.settings.labels.transform_keys(&:to_s),
89
- "choices" => step_type.settings.choices.transform_keys(&:to_s),
90
- "records" => holdings_of(step_type),
91
- "record_labels" => step_type.settings.record_labels.to_h { |name, held| [ name.to_s, held.transform_keys(&:to_s) ] },
92
- "awaits_input" => step_type.awaits_input? }
93
- end
94
- end
95
-
96
- def holdings_of(step_type)
97
- step_type.settings.record_fields.to_h do |name, holds|
98
- [ name.to_s, holds.transform_keys(&:to_s).transform_values(&:to_s) ]
99
- end
100
- end
101
-
102
- def violations
103
- Validator.new(@document, registry: @registry).violations.map do |violation|
104
- { "node" => violation.node, "problem" => violation.problem.to_s, "detail" => violation.detail }
105
- end
106
- end
107
-
108
- def label_for(node)
109
- Name.of(node, @registry)
110
- end
111
-
112
- def choices_for(node)
113
- naming_steps(node).index_with { earlier_than(node) }.merge(drawn_by(node)).merge(named_outputs_by(node))
114
- end
115
-
116
- def named_outputs_by(node)
117
- step_type_for(node)&.settings&.outputs_of.to_h.to_h do |name, source|
118
- [ name.to_s, digest.outputs_of(node.config[source.to_s]) ]
119
- end
120
- end
121
-
122
- def drawn_by(node)
123
- step_type_for(node)&.settings&.drawn_from.to_h.to_h do |name, source|
124
- [ name.to_s, digest.values_of(step_named_by(node, source), node.config[source.to_s]) ]
125
- end
126
- end
127
-
128
- def step_named_by(node, source)
129
- node.config[step_type_for(node).settings.outputs_of[source].to_s]
130
- end
131
-
132
- def digest
133
- Digest.new(@document, registry: @registry)
134
- end
135
-
136
- def naming_steps(node)
137
- step_type_for(node)&.settings&.naming_steps.to_a.map(&:to_s)
138
- end
139
-
140
- def earlier_than(node)
141
- digest.preceding(node.id).map do |id|
142
- { "value" => id, "label" => label_for(@document.node(id)) }
143
- end
144
- end
145
-
146
- def ends_here?(node)
147
- step_type_for(node)&.ends_here? || false
148
- end
149
-
150
- def loose?(node)
151
- @document.entry.present? && @document.reachable.exclude?(node.id)
152
- end
153
-
154
- def begins_here?(node)
155
- step_type_for(node)&.begins_here? || false
156
- end
157
-
158
- def ports_for(node)
159
- digest.routing_values(node.id)
160
- end
161
-
162
- def step_type_for(node)
163
- @registry.fetch(node.type) if @registry.registered?(node.type)
164
- end
165
- end
166
- end
167
- end
@@ -1,18 +0,0 @@
1
- module Alembic
2
- module Flow
3
- module Change
4
- PHRASING = { "added" => "Added", "updated" => "Updated", "removed" => "Removed",
5
- "moved" => "Moved", "connected" => "Connected", "disconnected" => "Disconnected", "returned" => "Returned to" }.freeze
6
-
7
- def self.phrase(change)
8
- named = Array(change["named"]).compact_blank.map { |name| "“#{name}”" }
9
- verb = PHRASING.fetch(change["action"], change["action"])
10
-
11
- return "#{verb} #{named.join(' → ')}" if named.any?
12
- return "#{verb} #{change['detail']}" if change["detail"].present?
13
-
14
- verb
15
- end
16
- end
17
- end
18
- end
@@ -1,24 +0,0 @@
1
- module Alembic
2
- module Flow
3
- class Condition
4
- include Step
5
-
6
- step_name "Condition"
7
-
8
- setting :step, type: :previous_step
9
- setting :output, outputs_of: :step, required: true
10
- setting :comparison, type: :select, options: [ "is", "is not" ], required: true
11
- setting :answer, from: :output, required: true
12
-
13
- output :result, type: :boolean, values: [ true, false ]
14
-
15
- names_by { |node| node.config.values_at("step", "comparison", "answer").compact_blank.join(" ").presence }
16
-
17
- def route(node, state)
18
- matched = state[node.config["step"]] == node.config["answer"]
19
-
20
- node.config["comparison"] == "is not" ? !matched : matched
21
- end
22
- end
23
- end
24
- end
@@ -1,132 +0,0 @@
1
- module Alembic
2
- module Flow
3
- class Definition < ApplicationRecord
4
- self.table_name = "alembic_flows"
5
-
6
- enum :status, { active: "active", hidden: "hidden", inactive: "inactive" }
7
- enum :kind, { scored: "scored", guide: "guide" }
8
- enum :persists, { unsaved: "unsaved", each_step: "each_step", on_finish: "on_finish" }
9
-
10
- validates :slug, presence: true
11
-
12
- after_initialize :begin_the_flow, if: :new_record?
13
-
14
- scope :listable, -> { active }
15
-
16
- # Declared before :definition_versions so responses clear first, otherwise
17
- # destroying a diagnostic trips the responses -> definition_versions FK.
18
- has_many :runs, class_name: "Alembic::Flow::Run", foreign_key: :flow_id, dependent: :destroy
19
- has_many :definition_versions, class_name: "Alembic::Flow::Version", foreign_key: :flow_id, dependent: :destroy
20
- has_many :summary_versions, class_name: "Alembic::Flow::SummaryVersion", foreign_key: :flow_id, dependent: :destroy
21
-
22
- def self.upsert_definition(definition)
23
- find_or_initialize_by(slug: definition["slug"]).tap do |diagnostic|
24
- diagnostic.save!
25
- diagnostic.record_definition(definition) unless diagnostic.definition == definition
26
- end
27
- end
28
-
29
- def definition
30
- current_definition_version&.definition
31
- end
32
-
33
- def current_definition_version
34
- definition_versions.find_by(number: cursor)
35
- end
36
-
37
- def edit_history
38
- @edit_history ||= EditHistory.new(self)
39
- end
40
-
41
- def summaries
42
- @summaries ||= Summaries.new(self)
43
- end
44
-
45
- def publish
46
- create_version
47
-
48
- publish_version(current_definition_version)
49
- end
50
-
51
- def return_to(version)
52
- raise ActiveRecord::RecordNotFound unless definition_versions.exists?(version.id)
53
- raise OutOfService if version.out_of_service?
54
-
55
- update!(document: version.definition, undone_changes: [],
56
- changes_since_version: changes_since_version.to_a + [ returning_to(version) ])
57
- end
58
-
59
- def create_version
60
- record_definition(document, changes_since_version.to_a) unless versioned?
61
-
62
- update!(undo_history: edit_history.undoable, changes_since_version: [], undone_changes: [])
63
- end
64
-
65
- def returning_to(version)
66
- { "action" => "returned", "steps" => [], "named" => [],
67
- "detail" => "version #{version.number}", "before" => document }
68
- end
69
-
70
- def versioned?
71
- document == definition
72
- end
73
-
74
- def record_definition(payload, captured = [])
75
- definition_versions.create!(number: next_definition_number, definition: payload,
76
- changes_captured: captured.map { |change| change.except("before") })
77
- .tap { |version| update!(definition_cursor: version.number, document: payload) }
78
- end
79
-
80
- def publish_version(version)
81
- raise OutOfService if version.out_of_service?
82
- return if live_version == version
83
-
84
- live_version&.update!(status: :superseded)
85
- version.update!(status: :live)
86
- end
87
-
88
- def available?
89
- !inactive?
90
- end
91
-
92
- def retire_version(version)
93
- version.update!(status: :retired)
94
- end
95
-
96
- def withdraw_version(version)
97
- version.update!(status: :withdrawn)
98
- end
99
-
100
- def live_version
101
- definition_versions.find_by(status: :live)
102
- end
103
-
104
- def live_definition
105
- live_version&.definition
106
- end
107
-
108
- def runner
109
- Runner.new(live_definition)
110
- end
111
-
112
- private
113
-
114
- def begin_the_flow
115
- self.document ||= { "nodes" => [ { "id" => "start", "type" => "start" }, { "id" => "end", "type" => "terminal" } ],
116
- "edges" => [ { "from" => "start", "to" => "end" } ] }
117
- end
118
-
119
- def cursor
120
- definition_cursor || recorded_numbers.max
121
- end
122
-
123
- def recorded_numbers
124
- definition_versions.pluck(:number)
125
- end
126
-
127
- def next_definition_number
128
- (definition_versions.maximum(:number) || 0) + 1
129
- end
130
- end
131
- end
132
- end
@@ -1,116 +0,0 @@
1
- module Alembic
2
- module Flow
3
- class Digest
4
- def initialize(document, registry: Flow.registry)
5
- @document = document
6
- @registry = registry
7
- end
8
-
9
- def entry
10
- @document.node(@document.entry)
11
- end
12
-
13
- def step(id)
14
- @document.node(id)
15
- end
16
-
17
- def steps
18
- @document.nodes
19
- end
20
-
21
- def preceding(id)
22
- return [] unless @document.reachable.include?(id)
23
-
24
- @document.nodes.map(&:id).uniq
25
- .reject { |other| other == id || @document.reachable(without: other).include?(id) }
26
- end
27
-
28
- def routing_values(id)
29
- named = step(id)
30
- return [] unless named && step_type(named)&.routes?
31
-
32
- step_type(named).outputs.flat_map { |output| values_taken(output, named) }.map { |value| value["value"].to_s }
33
- end
34
-
35
- def values_taken(output, node)
36
- return output.values_for(node) unless output.from
37
-
38
- values_out_of(node.config[output.from.to_s])
39
- end
40
-
41
- def values_of(id, output_name)
42
- named = step(id)
43
- return [] unless named && output_name.present?
44
-
45
- step_type(named)&.values_of(output_name, named).to_a
46
- end
47
-
48
- def outputs_of(id)
49
- named = step(id)
50
- return [] unless named
51
-
52
- step_type(named)&.outputs.to_a.map { |output| { "value" => output.name.to_s, "label" => output.label } }
53
- end
54
-
55
- def values_out_of(id)
56
- named = step(id)
57
- return [] unless named
58
-
59
- step_type(named)&.outputs.to_a.flat_map { |output| output.values_for(named) }
60
- end
61
-
62
- def requirements(id)
63
- node = step(id)
64
- step_type(node)&.settings&.requirements_for(node.config).to_a
65
- end
66
-
67
- def next_step(state)
68
- walk(state).last
69
- end
70
-
71
- def state_on_path(state)
72
- state.slice(*walk(state).first)
73
- end
74
-
75
- private
76
-
77
- def walk(state)
78
- recorded = []
79
- visited = []
80
- cursor = entry
81
-
82
- while cursor && !visited.include?(cursor.id)
83
- return [ recorded, cursor ] if pending?(cursor, state)
84
-
85
- recorded << cursor.id if state.key?(cursor.id)
86
- visited << cursor.id
87
- cursor = successor(cursor, state)
88
- end
89
-
90
- [ recorded, nil ]
91
- end
92
-
93
- def pending?(node, state)
94
- return false if state.key?(node.id)
95
-
96
- step_type(node)&.awaits_input? || acts?(node)
97
- end
98
-
99
- def acts?(node)
100
- step_type(node)&.acts? || false
101
- end
102
-
103
- def successor(node, state)
104
- leaving = @document.edges_from(node.id)
105
- port = step_type(node)&.route(node, state)
106
- taken = port.nil? ? leaving.first : leaving.find { |edge| edge.on.to_s == port.to_s }
107
-
108
- taken && @document.node(taken.to)
109
- end
110
-
111
- def step_type(node)
112
- @registry.fetch(node.type) if node && @registry.registered?(node.type)
113
- end
114
- end
115
- end
116
- end