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,42 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
module Step
|
|
4
|
-
extend ActiveSupport::Concern
|
|
5
|
-
|
|
6
|
-
WORDS = (StepType::Declaration.public_instance_methods(false) - [ :to_step_type ]).freeze
|
|
7
|
-
BEHAVIOURS = %i[route process].freeze
|
|
8
|
-
|
|
9
|
-
included do
|
|
10
|
-
class_attribute :declarations, default: []
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
class_methods do
|
|
14
|
-
WORDS.each do |word|
|
|
15
|
-
define_method(word) do |*arguments, **options, &block|
|
|
16
|
-
self.declarations += [ [ word, arguments, options, block ] ]
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def step_type
|
|
21
|
-
spoken = declarations + behaviours
|
|
22
|
-
StepType.define(step_type_id) do
|
|
23
|
-
spoken.each { |word, arguments, options, block| public_send(word, *arguments, **options, &block) }
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def behaviours
|
|
28
|
-
BEHAVIOURS.select { |word| method_defined?(word) }
|
|
29
|
-
.map { |word| [ word, [], {}, ->(node, state) { new.public_send(word, node, state) } ] }
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def register(registry = Flow.registry)
|
|
33
|
-
registry.register(step_type)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def step_type_id
|
|
37
|
-
name.demodulize.underscore.to_sym
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
class StepType
|
|
4
|
-
class Declaration
|
|
5
|
-
def initialize(id)
|
|
6
|
-
@id = id
|
|
7
|
-
@step_name = id.to_s
|
|
8
|
-
@fields = {}
|
|
9
|
-
@labels = {}
|
|
10
|
-
@record_fields = {}
|
|
11
|
-
@record_labels = {}
|
|
12
|
-
@choices = {}
|
|
13
|
-
@limits = {}
|
|
14
|
-
@checks = {}
|
|
15
|
-
@drawn_from = {}
|
|
16
|
-
@outputs_of = {}
|
|
17
|
-
@declared_outputs = []
|
|
18
|
-
@required = []
|
|
19
|
-
@awaits_input = false
|
|
20
|
-
@ends_here = false
|
|
21
|
-
@begins_here = false
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def output(name, type: :string, label: nil, values: nil, from: nil)
|
|
25
|
-
@declared_outputs += [ Output.new(name: name, type: type, label: label, values: values, from: from) ]
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def displays_by(&display)
|
|
29
|
-
@display = display
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def drawn_by(template)
|
|
33
|
-
@drawn_by = template
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def process(&behaviour)
|
|
37
|
-
@behaviour = behaviour
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def route(&routing)
|
|
41
|
-
@routing = routing
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def step_name(value)
|
|
45
|
-
@step_name = value
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def awaits_input
|
|
49
|
-
@awaits_input = true
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def ends_here
|
|
53
|
-
@ends_here = true
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def begins_here
|
|
57
|
-
@begins_here = true
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def names_by(field = nil, &naming)
|
|
61
|
-
@naming_field = field
|
|
62
|
-
@naming = naming
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def setting(name, type: nil, from: nil, outputs_of: nil, label: nil, options: nil, limit: nil, check: nil, required: false, &entries)
|
|
66
|
-
type ||= :from_step if from
|
|
67
|
-
type ||= :from_step if outputs_of
|
|
68
|
-
raise UnknownFieldType, "#{type} is not one of #{FIELD_TYPES.join(', ')}" unless FIELD_TYPES.include?(type)
|
|
69
|
-
|
|
70
|
-
@drawn_from[name] = from if from
|
|
71
|
-
@outputs_of[name] = outputs_of if outputs_of
|
|
72
|
-
@required += [ name ] if required
|
|
73
|
-
|
|
74
|
-
declare_entries(name, entries) if type == :list
|
|
75
|
-
declare_choices(name, type, options)
|
|
76
|
-
@limits[name] = limit if limit
|
|
77
|
-
@checks[name] = check if check
|
|
78
|
-
@labels[name] = label.presence || name.to_s.humanize
|
|
79
|
-
@fields[name] = type
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
protected
|
|
83
|
-
|
|
84
|
-
attr_reader :fields, :labels
|
|
85
|
-
|
|
86
|
-
private
|
|
87
|
-
|
|
88
|
-
def declare_choices(name, type, options)
|
|
89
|
-
return @choices[name] = options if options.present?
|
|
90
|
-
raise UnknownFieldType, "a #{type} must offer options" if %i[select multi_select].include?(type)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def declare_entries(name, entries)
|
|
94
|
-
raise UnknownFieldType, "a list must say what an entry holds" if entries.nil?
|
|
95
|
-
|
|
96
|
-
declared = Declaration.new(:entry).tap { |entry| entry.instance_eval(&entries) }
|
|
97
|
-
@record_fields[name] = declared.fields
|
|
98
|
-
@record_labels[name] = declared.labels
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
public
|
|
102
|
-
|
|
103
|
-
def to_step_type
|
|
104
|
-
StepType.new(id: @id, step_name: @step_name, settings: settings, awaits_input: @awaits_input,
|
|
105
|
-
ends_here: @ends_here, begins_here: @begins_here, behaviour: @behaviour, routing: @routing,
|
|
106
|
-
display: @display, drawn_by: @drawn_by, naming_field: @naming_field, naming: @naming,
|
|
107
|
-
outputs: @declared_outputs)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def settings
|
|
111
|
-
Settings.new(fields: @fields, labels: @labels, record_fields: @record_fields,
|
|
112
|
-
record_labels: @record_labels, choices: @choices, limits: @limits, checks: @checks,
|
|
113
|
-
required: @required, drawn_from: @drawn_from, outputs_of: @outputs_of)
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
end
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
class StepType
|
|
4
|
-
def self.define(id, &declaration)
|
|
5
|
-
Declaration.new(id).tap { |decl| decl.instance_eval(&declaration) }.to_step_type
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
attr_reader :id, :step_name, :settings, :naming_field, :naming, :outputs, :drawn_by
|
|
9
|
-
|
|
10
|
-
def initialize(id:, step_name:, settings:, awaits_input:, behaviour:, routing:,
|
|
11
|
-
ends_here: false, begins_here: false, display: nil, drawn_by: nil,
|
|
12
|
-
naming_field: nil, naming: nil, outputs: [])
|
|
13
|
-
@id = id
|
|
14
|
-
@step_name = step_name
|
|
15
|
-
@settings = settings
|
|
16
|
-
@awaits_input = awaits_input
|
|
17
|
-
@ends_here = ends_here
|
|
18
|
-
@begins_here = begins_here
|
|
19
|
-
@behaviour = behaviour
|
|
20
|
-
@routing = routing
|
|
21
|
-
@display = display
|
|
22
|
-
@drawn_by = drawn_by
|
|
23
|
-
@naming_field = naming_field
|
|
24
|
-
@naming = naming
|
|
25
|
-
@outputs = outputs
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def display_of(node)
|
|
29
|
-
@display&.call(node)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def process(node, state)
|
|
33
|
-
@behaviour&.call(node, state)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def route(node, state)
|
|
37
|
-
@routing&.call(node, state)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def name_of(node)
|
|
41
|
-
@naming&.call(node).presence || node.config[naming_field.to_s].presence
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def acts?
|
|
45
|
-
@behaviour.present?
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def routes?
|
|
49
|
-
@routing.present?
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def values_of(name, node)
|
|
53
|
-
outputs.find { |output| output.name.to_s == name.to_s }&.values_for(node).to_a
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def awaits_input?
|
|
57
|
-
@awaits_input
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def ends_here?
|
|
61
|
-
@ends_here
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def begins_here?
|
|
65
|
-
@begins_here
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
class Switch
|
|
4
|
-
include Step
|
|
5
|
-
|
|
6
|
-
step_name "Switch"
|
|
7
|
-
|
|
8
|
-
setting :step, type: :previous_step
|
|
9
|
-
|
|
10
|
-
output :choice, type: :string, label: "Choice", from: :step
|
|
11
|
-
|
|
12
|
-
def route(node, state)
|
|
13
|
-
state[node.config["step"]]
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
class Validator
|
|
4
|
-
OPTIONAL = { unrouted_value: :unrouted_values, unfollowed_path: :unfollowed_paths, dead_end: :dead_ends }.freeze
|
|
5
|
-
|
|
6
|
-
def initialize(document, registry: Flow.registry, checks: Flow.checks)
|
|
7
|
-
@document = document
|
|
8
|
-
@registry = registry
|
|
9
|
-
@checks = checks
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def violations
|
|
13
|
-
structural_violations + unmet_requirements + missing_settings + missing_values + beginnings + asked_for
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def structural_violations
|
|
17
|
-
malformations + unreachable
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def malformations
|
|
21
|
-
missing_edge_targets + missing_edge_sources + duplicate_ids + no_beginning
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
private
|
|
25
|
-
|
|
26
|
-
def asked_for
|
|
27
|
-
@checks.flat_map { |name| send(OPTIONAL.fetch(name)) }
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def missing_edge_targets
|
|
31
|
-
@document.edges.reject { |edge| known?(edge.to) }
|
|
32
|
-
.map { |edge| Violation.new(node: edge.from, problem: :missing_edge_target, detail: edge.to) }
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def missing_edge_sources
|
|
36
|
-
@document.edges.reject { |edge| known?(edge.from) }
|
|
37
|
-
.map { |edge| Violation.new(node: edge.to, problem: :missing_edge_source, detail: edge.from) }
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def duplicate_ids
|
|
41
|
-
@document.nodes.map(&:id).tally.select { |_id, count| count > 1 }
|
|
42
|
-
.map { |id, _count| Violation.new(node: id, problem: :duplicate_id) }
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def no_beginning
|
|
46
|
-
return [] if known?(@document.entry)
|
|
47
|
-
|
|
48
|
-
[ Violation.new(node: nil, problem: :no_beginning) ]
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def unmet_requirements
|
|
52
|
-
@document.nodes.flat_map do |node|
|
|
53
|
-
requirements_for(node).reject { |required| precedes_every_path?(required, node.id) }
|
|
54
|
-
.map { |required| Violation.new(node: node.id, problem: :unmet_requirement, detail: required) }
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def dead_ends
|
|
59
|
-
@document.nodes.filter_map { |node| ending_problem(node) }
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def ending_problem(node)
|
|
63
|
-
leaving = @document.edges_from(node.id).any?
|
|
64
|
-
return Violation.new(node: node.id, problem: :past_the_end) if ends_here?(node) && leaving
|
|
65
|
-
return if ends_here?(node) || leaving
|
|
66
|
-
|
|
67
|
-
Violation.new(node: node.id, problem: :dead_end)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def ends_here?(node)
|
|
71
|
-
@registry.registered?(node.type) && @registry.fetch(node.type).ends_here?
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def unfollowed_paths
|
|
75
|
-
@document.nodes.select { |node| leads_on?(node) }.flat_map { |node| unfollowed_paths_from(node) }
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def leads_on?(node)
|
|
79
|
-
@registry.registered?(node.type) && !@registry.fetch(node.type).routes?
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def unfollowed_paths_from(node)
|
|
83
|
-
@document.edges_from(node.id).drop(1)
|
|
84
|
-
.map { |edge| Violation.new(node: node.id, problem: :unfollowed_path, detail: edge.to) }
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def unrouted_values
|
|
88
|
-
@document.nodes.flat_map { |node| unrouted_values_in(node) }
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def unrouted_values_in(node)
|
|
92
|
-
wired = @document.edges_from(node.id).map { |edge| edge.on.to_s }
|
|
93
|
-
|
|
94
|
-
digest.routing_values(node.id).reject { |value| wired.include?(value) }
|
|
95
|
-
.map { |value| Violation.new(node: node.id, problem: :unrouted_value, detail: value) }
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def digest
|
|
99
|
-
Digest.new(@document, registry: @registry)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def beginnings
|
|
103
|
-
@document.beginnings.drop(1).map { |id| Violation.new(node: id, problem: :many_beginnings) } + led_into
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def led_into
|
|
107
|
-
@document.beginnings.select { |id| @document.edges.any? { |edge| edge.to == id } }
|
|
108
|
-
.map { |id| Violation.new(node: id, problem: :before_the_beginning) }
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def missing_values
|
|
112
|
-
@document.nodes.flat_map { |node| missing_values_in(node) }
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def missing_values_in(node)
|
|
116
|
-
drawn_of(node).filter_map do |name, source|
|
|
117
|
-
chosen = node.config[name.to_s]
|
|
118
|
-
next if chosen.blank? || node.config[source.to_s].blank?
|
|
119
|
-
next if offered_to(node, source).include?(chosen.to_s)
|
|
120
|
-
|
|
121
|
-
Violation.new(node: node.id, problem: :missing_value, detail: chosen.to_s)
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def offered_to(node, source)
|
|
126
|
-
naming = @registry.fetch(node.type).settings.outputs_of[source]
|
|
127
|
-
|
|
128
|
-
digest.values_of(node.config[naming.to_s], node.config[source.to_s]).map { |value| value["value"].to_s }
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def drawn_of(node)
|
|
132
|
-
return {} unless @registry.registered?(node.type)
|
|
133
|
-
|
|
134
|
-
@registry.fetch(node.type).settings.drawn_from
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def missing_settings
|
|
138
|
-
@document.nodes.flat_map do |node|
|
|
139
|
-
required_of(node).reject { |name| node.config[name.to_s].present? }
|
|
140
|
-
.map { |name| Violation.new(node: node.id, problem: :missing_setting, detail: name.to_s) }
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def required_of(node)
|
|
145
|
-
return [] unless @registry.registered?(node.type)
|
|
146
|
-
|
|
147
|
-
@registry.fetch(node.type).settings.required
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def requirements_for(node)
|
|
151
|
-
return [] unless @registry.registered?(node.type)
|
|
152
|
-
|
|
153
|
-
@registry.fetch(node.type).settings.requirements_for(node.config)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def precedes_every_path?(required, id)
|
|
157
|
-
known?(required) && !@document.reachable(without: required).include?(id)
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def unreachable
|
|
161
|
-
return [] unless known?(@document.entry)
|
|
162
|
-
|
|
163
|
-
(@document.nodes.map(&:id).uniq - @document.reachable).map { |id| Violation.new(node: id, problem: :unreachable) }
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def known?(id)
|
|
167
|
-
@document.node(id).present?
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
end
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
class Version < ApplicationRecord
|
|
4
|
-
self.table_name = "alembic_flow_versions"
|
|
5
|
-
|
|
6
|
-
enum :status, { draft: "draft", live: "live", superseded: "superseded",
|
|
7
|
-
retired: "retired", withdrawn: "withdrawn" }
|
|
8
|
-
|
|
9
|
-
belongs_to :flow, class_name: "Alembic::Flow::Definition"
|
|
10
|
-
|
|
11
|
-
validates :number, uniqueness: { scope: :flow_id }
|
|
12
|
-
|
|
13
|
-
def out_of_service?
|
|
14
|
-
retired? || withdrawn?
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def changes
|
|
18
|
-
changes_captured.to_a
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
FROZEN = %w[definition number flow_id changes_captured].freeze
|
|
22
|
-
|
|
23
|
-
before_update { raise ActiveRecord::ReadOnlyRecord if changed.intersect?(FROZEN) }
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
data/app/models/alembic/flow.rb
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Flow
|
|
3
|
-
FIELD_TYPES = %i[string integer float boolean select multi_select previous_step from_step list].freeze
|
|
4
|
-
|
|
5
|
-
OUTPUT_TYPES = %i[string integer float boolean].freeze
|
|
6
|
-
OPTIONAL_CHECKS = %i[unrouted_value unfollowed_path dead_end].freeze
|
|
7
|
-
|
|
8
|
-
class UnknownFieldType < ArgumentError; end
|
|
9
|
-
class UnknownOutputType < ArgumentError; end
|
|
10
|
-
class UnknownStepType < KeyError; end
|
|
11
|
-
class UnknownCheck < ArgumentError; end
|
|
12
|
-
class InvalidEdit < StandardError; end
|
|
13
|
-
|
|
14
|
-
DRAWING = "alembic/flow/steps/choosing".freeze
|
|
15
|
-
|
|
16
|
-
class << self
|
|
17
|
-
def draws_with(template)
|
|
18
|
-
@drawing = template
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def drawing
|
|
22
|
-
@drawing.presence || DRAWING
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def step(id, &declaration)
|
|
26
|
-
registry.register(StepType.define(id, &declaration))
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def registry
|
|
30
|
-
@registry ||= Registry.new
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def check(name)
|
|
34
|
-
raise UnknownCheck, "#{name} is not one of #{OPTIONAL_CHECKS.join(', ')}" unless OPTIONAL_CHECKS.include?(name)
|
|
35
|
-
|
|
36
|
-
checks << name unless checks.include?(name)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def checks
|
|
40
|
-
@checks ||= []
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
class Runner < Flow::Runner
|
|
3
|
-
def question_text(id)
|
|
4
|
-
Steps::Question.asked(step(id)&.config)
|
|
5
|
-
end
|
|
6
|
-
|
|
7
|
-
def choice_label(id, value)
|
|
8
|
-
chosen = Steps::Question.choices_in(step(id)).find { |choice| choice.value == value }
|
|
9
|
-
|
|
10
|
-
chosen&.label.presence || value
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
module Alembic
|
|
2
|
-
module Steps
|
|
3
|
-
class Question
|
|
4
|
-
include Flow::Step
|
|
5
|
-
|
|
6
|
-
step_name "Question"
|
|
7
|
-
|
|
8
|
-
setting :question, type: :string
|
|
9
|
-
setting :category, type: :string
|
|
10
|
-
setting :answers, type: :list, required: true do
|
|
11
|
-
setting :value, type: :string
|
|
12
|
-
setting :label, type: :string
|
|
13
|
-
setting :weight, type: :integer
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
output :answer, type: :string, label: "Answer", values: ->(node) { Question.offered(node.config) }
|
|
17
|
-
|
|
18
|
-
names_by :question
|
|
19
|
-
awaits_input
|
|
20
|
-
|
|
21
|
-
displays_by { |node| Asked.new(id: node.id.to_sym, text: asked(node.config), choices: choices_in(node)) }
|
|
22
|
-
|
|
23
|
-
def self.asked(step)
|
|
24
|
-
step.to_h["question"] || step.to_h["text"]
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def self.answers_of(step)
|
|
28
|
-
Array(step.to_h["answers"] || step.to_h["options"])
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def self.offered(step)
|
|
32
|
-
answers_of(step).map do |answer|
|
|
33
|
-
{ "value" => answer["value"], "label" => answer["label"].presence || answer["value"] }
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def self.choices_in(node)
|
|
38
|
-
answers_of(node.config).map { |answer| choice_from(answer) }
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def self.choice_from(answer)
|
|
42
|
-
return Choice.new(value: answer) unless answer.is_a?(Hash)
|
|
43
|
-
|
|
44
|
-
Choice.new(value: answer["value"], label: answer["label"], hint: answer["hint"])
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def self.category_of(step)
|
|
48
|
-
step.to_h["category"] || step.to_h["tag"]
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
<fieldset>
|
|
2
|
-
<legend class="mb-5 text-xl font-semibold leading-snug text-gray-900"><%= step.text %></legend>
|
|
3
|
-
|
|
4
|
-
<div class="space-y-3">
|
|
5
|
-
<% step.choices.each do |choice| %>
|
|
6
|
-
<%= ui_radio_card(name: "answers[#{step.id}]", value: choice.value, label: choice.label.presence || choice.value, hint: choice.hint) %>
|
|
7
|
-
<% end %>
|
|
8
|
-
</div>
|
|
9
|
-
</fieldset>
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
<% content_for(:title, @guide.headline) %>
|
|
2
|
-
|
|
3
|
-
<%= ui_page(max_width: :md) do %>
|
|
4
|
-
<%= link_to "← Start over", flow_start_path(@guide.slug), class: "text-sm text-accent-600 hover:text-accent-700" %>
|
|
5
|
-
|
|
6
|
-
<div class="mt-6 mb-6">
|
|
7
|
-
<%= ui_progress(value: @answers.size, max: [ @guide.steps_on_path(@answers).size, 1 ].max, label: "Question #{@answers.size + 1}") %>
|
|
8
|
-
</div>
|
|
9
|
-
|
|
10
|
-
<%= form_with url: step_form[:url], method: step_form[:method], data: { turbo: false } do %>
|
|
11
|
-
<% if carries_answers? %>
|
|
12
|
-
<% @answers.each do |key, value| %>
|
|
13
|
-
<%= hidden_field_tag "answers[#{key}]", value %>
|
|
14
|
-
<% end %>
|
|
15
|
-
<% end %>
|
|
16
|
-
|
|
17
|
-
<%= render @drawing, step: @question %>
|
|
18
|
-
|
|
19
|
-
<div class="mt-8 flex items-center gap-4">
|
|
20
|
-
<%= ui_button(label: "Next", type: :submit) %>
|
|
21
|
-
|
|
22
|
-
<% if @answers.any? %>
|
|
23
|
-
<button type="submit" name="back" value="1" formnovalidate class="cursor-pointer text-sm text-gray-500 underline underline-offset-2 hover:text-gray-900">← Back</button>
|
|
24
|
-
<% end %>
|
|
25
|
-
</div>
|
|
26
|
-
<% end %>
|
|
27
|
-
<% end %>
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<%= ui_page(max_width: :lg) do %>
|
|
2
|
-
<p class="mb-4">
|
|
3
|
-
<%= link_to "← Back to the flow", alembic.manage_flow_path(@diagnostic), class: "text-sm text-accent-600 hover:text-accent-700" %>
|
|
4
|
-
</p>
|
|
5
|
-
|
|
6
|
-
<%= ui_form_page(title: "Definition", back_url: alembic.manage_flow_path(@diagnostic), subtitle: @diagnostic.slug) %>
|
|
7
|
-
|
|
8
|
-
<%= ui_form(action: alembic.manage_flow_definition_path(@diagnostic), method: :patch) do %>
|
|
9
|
-
<%= ui_textarea(name: "definition", value: JSON.pretty_generate(@diagnostic.document), rows: 30) %>
|
|
10
|
-
|
|
11
|
-
<%= ui_button(label: "Save", type: :submit) %>
|
|
12
|
-
<% end %>
|
|
13
|
-
<% end %>
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
<%= ui_page(max_width: :xl) do %>
|
|
2
|
-
<%= ui_page_header(title: "Flows", subtitle: "Build and edit your flows") %>
|
|
3
|
-
|
|
4
|
-
<%= ui_data_table(
|
|
5
|
-
items: @diagnostics,
|
|
6
|
-
columns: [ { title: "Title" }, { slug: "Slug" }, { kind: "Kind" }, { status: "Status" } ],
|
|
7
|
-
empty_message: "No diagnostics yet."
|
|
8
|
-
) do |table| %>
|
|
9
|
-
<% table.link(:title) { |diagnostic| alembic.manage_flow_path(diagnostic) } %>
|
|
10
|
-
<% table.actions do |diagnostic| %>
|
|
11
|
-
<%= ui_form(action: alembic.manage_flow_path(diagnostic), method: :delete) do %>
|
|
12
|
-
<%= ui_button(label: "Remove", type: :submit, variant: :secondary) %>
|
|
13
|
-
<% end %>
|
|
14
|
-
<% end %>
|
|
15
|
-
<% end %>
|
|
16
|
-
|
|
17
|
-
<%= ui_section(title: "Create a diagnostic") do %>
|
|
18
|
-
<% if @diagnostic&.errors&.any? %>
|
|
19
|
-
<%= ui_alert(message: @diagnostic.errors.full_messages.to_sentence, type: :error) %>
|
|
20
|
-
<% end %>
|
|
21
|
-
|
|
22
|
-
<%= ui_form(action: alembic.manage_flows_path, method: :post) do %>
|
|
23
|
-
<label class="block text-sm font-medium mb-1">Slug</label>
|
|
24
|
-
<%= ui_input(name: "flow[slug]", placeholder: "e.g. business-scorecard") %>
|
|
25
|
-
|
|
26
|
-
<label class="block text-sm font-medium mb-1 mt-4">Kind</label>
|
|
27
|
-
<%= ui_select(name: "flow[kind]", options: [ [ "Scored", "scored" ], [ "Guide", "guide" ] ]) %>
|
|
28
|
-
|
|
29
|
-
<%= ui_button(label: "Create", type: :submit, variant: :secondary) %>
|
|
30
|
-
<% end %>
|
|
31
|
-
<% end %>
|
|
32
|
-
<% end %>
|