alembic 0.1.0 → 0.2.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 +0 -16
- data/app/controllers/alembic/flows_controller.rb +6 -90
- data/app/controllers/alembic/manage/base_controller.rb +1 -0
- data/app/controllers/alembic/manage/flows_controller.rb +7 -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 +38 -0
- data/app/helpers/alembic/pages_helper.rb +39 -0
- data/app/models/alembic/flow/definition_summary.rb +9 -0
- data/app/models/alembic/flow/run_summary.rb +10 -0
- data/app/models/alembic/flow/summaries.rb +36 -5
- data/app/models/alembic/flow/summary_version.rb +1 -1
- data/app/models/alembic/outputs/grouped.rb +1 -1
- 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 +46 -0
- data/app/models/alembic/page/version.rb +13 -0
- data/app/models/alembic/page.rb +32 -0
- data/app/views/alembic/flows/complete.html.erb +3 -3
- data/app/views/alembic/flows/show.html.erb +6 -6
- data/app/views/alembic/manage/flows/edit.html.erb +10 -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/lib/alembic/engine.rb +2 -11
- data/lib/alembic/version.rb +1 -1
- data/lib/alembic.rb +30 -6
- metadata +35 -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,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
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
class Document
|
|
4
|
-
def initialize(document, registry: Flow.registry)
|
|
5
|
-
@document = document
|
|
6
|
-
@registry = registry
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def nodes
|
|
10
|
-
Array(@document["nodes"]).map { |node| Node.new(id: node["id"], type: node["type"], config: node.except("id", "type")) }
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def edges
|
|
14
|
-
Array(@document["edges"]).map { |edge| Edge.new(from: edge["from"], to: edge["to"], on: edge["on"]) }
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def entry
|
|
18
|
-
beginnings.first
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def beginnings
|
|
22
|
-
nodes.select { |node| begins_here?(node) }.map(&:id)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def begins_here?(node)
|
|
26
|
-
@registry.registered?(node.type) && @registry.fetch(node.type).begins_here?
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def node(id)
|
|
30
|
-
nodes.find { |node| node.id == id }
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def edges_from(id)
|
|
34
|
-
edges.select { |edge| edge.from == id }
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def reachable(without: nil)
|
|
38
|
-
found = []
|
|
39
|
-
frontier = [ entry ]
|
|
40
|
-
|
|
41
|
-
while (id = frontier.shift)
|
|
42
|
-
next if found.include?(id) || id == without
|
|
43
|
-
|
|
44
|
-
found << id
|
|
45
|
-
frontier.concat(edges_from(id).map(&:to))
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
found
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def to_h
|
|
52
|
-
@document
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def insert(node, on:, leaving: nil)
|
|
56
|
-
source, target = on
|
|
57
|
-
replaced = raw_edges.find { |edge| edge["from"] == source && edge["to"] == target }
|
|
58
|
-
bridged = [ { "from" => source, "to" => node["id"], "on" => replaced&.fetch("on", nil) }.compact,
|
|
59
|
-
{ "from" => node["id"], "to" => target, "on" => leaving }.compact ]
|
|
60
|
-
|
|
61
|
-
with(nodes: raw_nodes + [ node ], edges: (raw_edges - [ replaced ]) + bridged)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def add(node)
|
|
65
|
-
with(nodes: raw_nodes + [ node ], edges: raw_edges)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def connect(from:, to:, on: nil)
|
|
69
|
-
with(nodes: raw_nodes, edges: raw_edges + [ { "from" => from, "to" => to, "on" => on }.compact ])
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def disconnect(from:, to:)
|
|
73
|
-
with(nodes: raw_nodes, edges: raw_edges.reject { |edge| edge["from"] == from && edge["to"] == to })
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def move(id, on:, leaving: nil)
|
|
77
|
-
step = raw_nodes.find { |node| node["id"] == id }
|
|
78
|
-
return self unless step
|
|
79
|
-
|
|
80
|
-
remove(id).insert(step, on: on, leaving: leaving)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def configure(id, config)
|
|
84
|
-
reconfigured = raw_nodes.map do |node|
|
|
85
|
-
node["id"] == id ? node.slice("id", "type").merge(config) : node
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
with(nodes: reconfigured, edges: raw_edges)
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def rewire(from:, to:, target:)
|
|
92
|
-
rewritten = raw_edges.map do |edge|
|
|
93
|
-
edge["from"] == from && edge["to"] == to ? edge.merge("to" => target) : edge
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
with(nodes: raw_nodes, edges: rewritten)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def remove(id)
|
|
100
|
-
incoming = raw_edges.select { |edge| edge["to"] == id }
|
|
101
|
-
outgoing = raw_edges.select { |edge| edge["from"] == id }
|
|
102
|
-
|
|
103
|
-
with(nodes: raw_nodes.reject { |node| node["id"] == id },
|
|
104
|
-
edges: (raw_edges - incoming - outgoing) + bridges(incoming, outgoing))
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
private
|
|
108
|
-
|
|
109
|
-
def bridges(incoming, outgoing)
|
|
110
|
-
incoming.product(outgoing).map do |arriving, leaving|
|
|
111
|
-
{ "from" => arriving["from"], "to" => leaving["to"], "on" => arriving["on"] }.compact
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def raw_nodes
|
|
116
|
-
Array(@document["nodes"])
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def raw_edges
|
|
120
|
-
Array(@document["edges"])
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def with(nodes:, edges:)
|
|
124
|
-
Document.new(@document.merge("nodes" => nodes, "edges" => edges), registry: @registry).tap do |edited|
|
|
125
|
-
broken = Validator.new(edited, registry: @registry).malformations
|
|
126
|
-
raise InvalidEdit, broken.map { |violation| "#{violation.node}: #{violation.problem}" }.join(", ") if broken.any?
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
end
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
module Drawing
|
|
4
|
-
def self.of(node, registry = Flow.registry)
|
|
5
|
-
named(node, registry).presence || Flow.drawing
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def self.named(node, registry)
|
|
9
|
-
return unless node && registry.registered?(node.type)
|
|
10
|
-
|
|
11
|
-
registry.fetch(node.type).drawn_by
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
class EditHistory
|
|
4
|
-
def initialize(flow)
|
|
5
|
-
@flow = flow
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def undoable?
|
|
9
|
-
undoable.any?
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def redoable?
|
|
13
|
-
undone.any?
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def undoable
|
|
17
|
-
@flow.undo_history.to_a + @flow.changes_since_version.to_a
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def undo_change
|
|
21
|
-
undone_change = undoable.last
|
|
22
|
-
return unless undone_change
|
|
23
|
-
|
|
24
|
-
@flow.update!(document: undone_change["before"],
|
|
25
|
-
undone_changes: undone + [ undone_change.merge("after" => @flow.document) ],
|
|
26
|
-
undo_history: @flow.undo_history.to_a[0...-1],
|
|
27
|
-
changes_since_version: @flow.changes_since_version.to_a[0...-1])
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def redo_change
|
|
31
|
-
redone = undone.last
|
|
32
|
-
return unless redone
|
|
33
|
-
|
|
34
|
-
@flow.update!(document: redone["after"], undone_changes: undone[0...-1],
|
|
35
|
-
changes_since_version: @flow.changes_since_version.to_a + [ redone.except("after") ])
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def record(edited, action, steps, before)
|
|
39
|
-
@flow.update!(document: edited.to_h, undone_changes: [],
|
|
40
|
-
changes_since_version: with(edited, action, steps, before))
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def edit_document(payload)
|
|
44
|
-
@flow.update!(document: payload, undone_changes: [],
|
|
45
|
-
changes_since_version: @flow.changes_since_version.to_a + [ edited_by_hand ])
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
private
|
|
49
|
-
|
|
50
|
-
def with(edited, action, steps, before)
|
|
51
|
-
return @flow.changes_since_version.to_a unless action
|
|
52
|
-
|
|
53
|
-
@flow.changes_since_version.to_a +
|
|
54
|
-
[ { "action" => action.to_s, "steps" => steps.map(&:to_s),
|
|
55
|
-
"named" => steps.map { |id| named(edited, id) }, "before" => before } ]
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def named(edited, id)
|
|
59
|
-
Name.of(edited.node(id.to_s) || document.node(id.to_s))
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def document
|
|
63
|
-
Document.new(@flow.document || @flow.definition || {})
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def undone
|
|
67
|
-
@flow.undone_changes.to_a
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def edited_by_hand
|
|
71
|
-
{ "action" => "edited", "steps" => [], "named" => [ "the definition" ], "before" => @flow.document }
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
class Layout
|
|
4
|
-
SPREAD = 2
|
|
5
|
-
|
|
6
|
-
def initialize(document)
|
|
7
|
-
@document = document
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def positions
|
|
11
|
-
rows = depths
|
|
12
|
-
columns = columns_for(rows)
|
|
13
|
-
|
|
14
|
-
rows.to_h { |id, row| [ id, { "row" => row, "column" => columns[id] } ] }
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
private
|
|
18
|
-
|
|
19
|
-
def depths
|
|
20
|
-
walked = walk_from_entry
|
|
21
|
-
|
|
22
|
-
walked.merge(stranded(walked).index_with(next_row(walked)))
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def walk_from_entry
|
|
26
|
-
seen = {}
|
|
27
|
-
frontier = starting_points.map { |id| [ id, 0 ] }
|
|
28
|
-
|
|
29
|
-
until frontier.empty?
|
|
30
|
-
id, depth = frontier.shift
|
|
31
|
-
next if seen.key?(id)
|
|
32
|
-
|
|
33
|
-
seen[id] = depth
|
|
34
|
-
@document.edges_from(id).each { |edge| frontier << [ edge.to, depth + 1 ] if known?(edge.to) }
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
seen
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def starting_points
|
|
41
|
-
return [ @document.entry ] if known?(@document.entry)
|
|
42
|
-
|
|
43
|
-
arrived = @document.edges.map(&:to)
|
|
44
|
-
@document.nodes.map(&:id).reject { |id| arrived.include?(id) }
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def columns_for(rows)
|
|
48
|
-
@columns = {}
|
|
49
|
-
@next_leaf = 0
|
|
50
|
-
rows.keys.each { |id| place(id, branches(rows)) }
|
|
51
|
-
@columns
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def branches(rows)
|
|
55
|
-
claimed = {}
|
|
56
|
-
|
|
57
|
-
rows.keys.to_h do |id|
|
|
58
|
-
mine = @document.edges_from(id).map(&:to).uniq
|
|
59
|
-
.select { |child| rows[child] == rows[id] + 1 && !claimed.key?(child) }
|
|
60
|
-
mine.each { |child| claimed[child] = true }
|
|
61
|
-
[ id, mine ]
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def place(id, branches)
|
|
66
|
-
return @columns[id] if @columns.key?(id)
|
|
67
|
-
|
|
68
|
-
mine = branches[id].to_a
|
|
69
|
-
return @columns[id] = (@next_leaf += SPREAD) - SPREAD if mine.empty?
|
|
70
|
-
|
|
71
|
-
spans = mine.map { |child| place(child, branches) }
|
|
72
|
-
@columns[id] = (spans.min + spans.max) / 2
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def stranded(walked)
|
|
76
|
-
@document.nodes.map(&:id).uniq - walked.keys
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def next_row(walked)
|
|
80
|
-
(walked.values.max || -1) + 1
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def known?(id)
|
|
84
|
-
@document.node(id).present?
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
module Name
|
|
4
|
-
def self.of(node, registry = Flow.registry)
|
|
5
|
-
return "" unless node
|
|
6
|
-
|
|
7
|
-
named(node, registry) || node.id
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def self.named(node, registry)
|
|
11
|
-
registry.fetch(node.type).name_of(node) if registry.registered?(node.type)
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
Output = Data.define(:name, :label, :type, :values, :from) do
|
|
4
|
-
def initialize(name:, label: nil, type: :string, values: nil, from: nil)
|
|
5
|
-
raise UnknownOutputType, "#{type} is not one of #{OUTPUT_TYPES.join(', ')}" unless OUTPUT_TYPES.include?(type)
|
|
6
|
-
|
|
7
|
-
super(name: name, label: label.presence || name.to_s.humanize, type: type, values: values, from: from)
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def values_for(node)
|
|
11
|
-
Array(values.respond_to?(:call) ? values.call(node) : values).map { |value| taken(value) }
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
private
|
|
15
|
-
|
|
16
|
-
def taken(value)
|
|
17
|
-
return value if value.is_a?(Hash)
|
|
18
|
-
|
|
19
|
-
{ "value" => value, "label" => value.to_s }
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
module Progress
|
|
4
|
-
class Kept
|
|
5
|
-
def initialize(run)
|
|
6
|
-
@run = run
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def definition
|
|
10
|
-
@run.pinned_definition
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def summary_of(state)
|
|
14
|
-
return [] if @run.pinned_summary.blank?
|
|
15
|
-
|
|
16
|
-
Summary::Report.new(@run.pinned_summary)
|
|
17
|
-
.results(Summary::Run.new(state: state, steps: @run.pinned_steps))
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def recorded
|
|
21
|
-
@run.recorded
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def record(id, value)
|
|
25
|
-
@run.record(id.to_sym, value)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def finish(_state)
|
|
29
|
-
@run
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def discard_last
|
|
33
|
-
@run.discard_last
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|