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.
- checksums.yaml +4 -4
- data/README.md +36 -22
- data/app/assets/builds/alembic/page_builder.css +1 -1
- data/app/assets/builds/alembic/page_builder.js +3 -3
- data/app/controllers/alembic/application_controller.rb +1 -16
- data/app/controllers/alembic/flows_controller.rb +6 -90
- data/app/controllers/alembic/manage/base_controller.rb +2 -0
- data/app/controllers/alembic/manage/flows_controller.rb +12 -44
- data/app/controllers/alembic/manage/pages_controller.rb +8 -1
- data/app/controllers/alembic/manage/previews_controller.rb +4 -19
- data/app/controllers/alembic/pages_controller.rb +19 -0
- data/app/controllers/concerns/alembic/summarizes.rb +59 -0
- data/app/helpers/alembic/application_helper.rb +2 -2
- data/app/helpers/alembic/pages_helper.rb +40 -0
- data/app/helpers/alembic/result_blocks_helper.rb +45 -0
- data/app/models/alembic/flow/definition_summary.rb +10 -0
- data/app/models/alembic/flow/run_summary.rb +10 -0
- data/app/models/alembic/flow/summaries.rb +44 -5
- data/app/models/alembic/flow/summary_version.rb +1 -1
- data/app/models/alembic/outputs/band.rb +1 -1
- data/app/models/alembic/outputs/grouped.rb +1 -1
- data/app/models/alembic/outputs/lowest.rb +5 -3
- data/app/models/alembic/outputs/percentage.rb +1 -1
- data/app/models/alembic/outputs/weighted_sum.rb +1 -1
- data/app/models/alembic/page/drawing.rb +17 -0
- data/app/models/alembic/page/options.rb +53 -0
- data/app/models/alembic/page/version.rb +13 -0
- data/app/models/alembic/page.rb +32 -0
- data/app/models/alembic/result_blocks.rb +29 -0
- data/app/models/alembic/summary/report.rb +1 -1
- data/app/models/alembic/summary/result.rb +1 -1
- data/app/views/alembic/blocks/_answers.html.erb +11 -0
- data/app/views/alembic/blocks/_band.html.erb +6 -0
- data/app/views/alembic/blocks/_categories.html.erb +11 -0
- data/app/views/alembic/blocks/_lead.html.erb +15 -0
- data/app/views/alembic/blocks/_score.html.erb +7 -0
- data/app/views/alembic/blocks/_weakest.html.erb +12 -0
- data/app/views/alembic/flows/complete.html.erb +12 -6
- data/app/views/alembic/flows/show.html.erb +6 -6
- data/app/views/alembic/flows/summary_page.html.erb +5 -0
- data/app/views/alembic/manage/flows/edit.html.erb +16 -10
- data/app/views/alembic/manage/pages/_block_content.html.erb +1 -1
- data/app/views/alembic/manage/pages/index.html.erb +4 -1
- data/app/views/alembic/manage/pages/show.html.erb +7 -1
- data/app/views/alembic/pages/show.html.erb +5 -0
- data/config/routes.rb +8 -19
- data/db/migrate/20260825224538_remove_published_version_from_alembic_diagnostics.rb +21 -1
- data/db/migrate/20260922120000_add_slug_to_alembic_pages.rb +6 -0
- data/db/migrate/20260922130000_create_alembic_page_versions.rb +16 -0
- data/db/migrate/20260924220000_move_flows_onto_easy_flow.rb +166 -0
- data/db/migrate/20260930120000_add_summary_page_to_alembic_flow_definition_summaries.rb +6 -0
- data/lib/alembic/engine.rb +6 -11
- data/lib/alembic/version.rb +1 -1
- data/lib/alembic.rb +34 -6
- metadata +45 -71
- data/app/assets/builds/alembic/canvas.js +0 -1
- data/app/controllers/alembic/manage/canvas_controller.rb +0 -183
- data/app/controllers/alembic/manage/definitions_controller.rb +0 -15
- data/app/controllers/alembic/manage/versions_controller.rb +0 -22
- data/app/controllers/concerns/alembic/manage/draws_canvas.rb +0 -35
- data/app/javascript/alembic/canvas/Canvas.jsx +0 -197
- data/app/javascript/alembic/canvas/Connector.jsx +0 -30
- data/app/javascript/alembic/canvas/ConnectorLayer.jsx +0 -31
- data/app/javascript/alembic/canvas/Control.jsx +0 -42
- data/app/javascript/alembic/canvas/Inspector.jsx +0 -44
- data/app/javascript/alembic/canvas/Panel.jsx +0 -67
- data/app/javascript/alembic/canvas/Placeholder.jsx +0 -24
- data/app/javascript/alembic/canvas/Port.jsx +0 -16
- data/app/javascript/alembic/canvas/Records.jsx +0 -34
- data/app/javascript/alembic/canvas/StepCard.jsx +0 -57
- data/app/javascript/alembic/canvas/Toolbar.jsx +0 -19
- data/app/javascript/alembic/canvas/TypePicker.jsx +0 -21
- data/app/javascript/alembic/canvas/changes.js +0 -5
- data/app/javascript/alembic/canvas/choices.js +0 -4
- data/app/javascript/alembic/canvas/flow.js +0 -7
- data/app/javascript/alembic/canvas/flowSender.js +0 -22
- data/app/javascript/alembic/canvas/ids.js +0 -7
- data/app/javascript/alembic/canvas/rows.js +0 -2
- data/app/javascript/alembic/canvas/styles.js +0 -3
- data/app/javascript/alembic/canvas/useConnectors.js +0 -75
- data/app/javascript/alembic/canvas/useFlow.js +0 -17
- data/app/javascript/alembic/canvas.jsx +0 -13
- data/app/models/alembic/asked.rb +0 -3
- data/app/models/alembic/choice.rb +0 -7
- data/app/models/alembic/flow/admission.rb +0 -19
- data/app/models/alembic/flow/canvas.rb +0 -167
- data/app/models/alembic/flow/change.rb +0 -18
- data/app/models/alembic/flow/condition.rb +0 -24
- data/app/models/alembic/flow/definition.rb +0 -132
- data/app/models/alembic/flow/digest.rb +0 -116
- data/app/models/alembic/flow/document.rb +0 -131
- data/app/models/alembic/flow/drawing.rb +0 -15
- data/app/models/alembic/flow/edge.rb +0 -9
- data/app/models/alembic/flow/edit_history.rb +0 -75
- data/app/models/alembic/flow/layout.rb +0 -88
- data/app/models/alembic/flow/name.rb +0 -15
- data/app/models/alembic/flow/node.rb +0 -5
- data/app/models/alembic/flow/output.rb +0 -23
- data/app/models/alembic/flow/progress/kept.rb +0 -38
- data/app/models/alembic/flow/progress/loose.rb +0 -43
- data/app/models/alembic/flow/progress.rb +0 -11
- data/app/models/alembic/flow/registry.rb +0 -25
- data/app/models/alembic/flow/run.rb +0 -54
- data/app/models/alembic/flow/runner.rb +0 -65
- data/app/models/alembic/flow/settings.rb +0 -80
- data/app/models/alembic/flow/start.rb +0 -13
- data/app/models/alembic/flow/step.rb +0 -42
- data/app/models/alembic/flow/step_type/declaration.rb +0 -118
- data/app/models/alembic/flow/step_type.rb +0 -69
- data/app/models/alembic/flow/switch.rb +0 -17
- data/app/models/alembic/flow/terminal.rb +0 -13
- data/app/models/alembic/flow/validator.rb +0 -171
- data/app/models/alembic/flow/version.rb +0 -26
- data/app/models/alembic/flow/violation.rb +0 -9
- data/app/models/alembic/flow.rb +0 -44
- data/app/models/alembic/runner.rb +0 -13
- data/app/models/alembic/steps/question.rb +0 -52
- data/app/views/alembic/flow/steps/_choosing.html.erb +0 -9
- data/app/views/alembic/flows/step.html.erb +0 -27
- data/app/views/alembic/manage/definitions/edit.html.erb +0 -13
- data/app/views/alembic/manage/flows/index.html.erb +0 -32
- data/app/views/alembic/manage/flows/show.html.erb +0 -26
- data/app/views/alembic/manage/versions/index.html.erb +0 -41
|
@@ -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)
|
data/app/models/alembic/asked.rb
DELETED
|
@@ -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
|