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,34 +1,14 @@
1
1
  module Alembic
2
- class FlowsController < ApplicationController
3
- helper_method :flow_start_path, :flow_step_path, :previewing?, :step_form, :carries_answers?
2
+ class FlowsController < EasyFlow::FlowsController
3
+ hosted_by FLOW_HOST
4
+ include Summarizes
4
5
 
5
- def show
6
- @diagnostic = flow
7
- end
8
-
9
- def start
10
- redirect_to alembic.run_path(Flow::Run.start(flow))
11
- end
12
-
13
- def step
14
- @guide = Runner.new(running_definition)
15
- @progress = progress
16
- @guide.run(@progress)
17
- @answers = @progress.recorded
18
- @question = @guide.next_step(@answers)
19
- @drawing = @guide.drawing_at(@answers)
20
- return render :step if @question
21
-
22
- render_completion
23
- end
6
+ private
24
7
 
25
- def update
26
- params[:back] ? progress.discard_last : record_submitted
27
- redirect_to alembic.run_path(run)
8
+ def run_location(run)
9
+ alembic.run_path(run)
28
10
  end
29
11
 
30
- private
31
-
32
12
  def flow_start_path(slug)
33
13
  alembic.flow_path(slug)
34
14
  end
@@ -36,69 +16,5 @@ module Alembic
36
16
  def flow_step_path(slug)
37
17
  alembic.flow_step_path(slug)
38
18
  end
39
-
40
- def previewing?
41
- false
42
- end
43
-
44
- def step_form
45
- return { url: alembic.run_path(run), method: :patch } if run
46
-
47
- { url: flow_step_path(@guide.slug), method: :get }
48
- end
49
-
50
- def carries_answers?
51
- run.nil?
52
- end
53
-
54
- def render_completion
55
- @answered = @guide.state_on_path(@answers)
56
- @progress.finish(@answered)
57
- @outputs = @progress.summary_of(@answered.transform_keys(&:to_s))
58
- render :complete
59
- end
60
-
61
- def progress
62
- return @progress ||= Flow::Progress.for(run.flow, run: run) if run
63
-
64
- @progress ||= Flow::Progress.for(flow, answers: submitted_answers, definition: running_definition)
65
- end
66
-
67
- def running_definition
68
- @running_definition ||= run ? run.pinned_definition : flowing_definition(flow)
69
- end
70
-
71
- def run
72
- return unless params[:id]
73
-
74
- @run ||= admitted_run
75
- end
76
-
77
- def admitted_run
78
- found = Flow::Run.find(params[:id])
79
-
80
- Flow::Admission.of_run(found, permitted: permitted?(found.flow))
81
- end
82
-
83
- def flow
84
- @stored_flow ||= admit(Flow::Definition.find_by(slug: params[:slug]))
85
- end
86
-
87
- def flowing_definition(diagnostic)
88
- diagnostic.live_definition
89
- end
90
-
91
- def submitted_answers
92
- params.fetch(:answers, {}).permit(*@guide.steps.map(&:id)).to_h.symbolize_keys
93
- end
94
-
95
- def record_submitted
96
- id, value = params.fetch(:answers, {}).permit(*asked).to_h.first
97
- progress.record(id, value)
98
- end
99
-
100
- def asked
101
- Runner.new(run.pinned_definition).steps.map(&:id)
102
- end
103
19
  end
104
20
  end
@@ -11,6 +11,8 @@ module Alembic
11
11
  helper KeystoneUiHelper
12
12
  helper KeystoneUi::React::MountHelper
13
13
  helper KsBlocks::ContentHelper
14
+ helper Alembic::PagesHelper
15
+ helper Alembic::ResultBlocksHelper
14
16
  end
15
17
  end
16
18
  end
@@ -1,54 +1,22 @@
1
1
  module Alembic
2
2
  module Manage
3
- class FlowsController < BaseController
4
- include DrawsCanvas
5
- def index
6
- @diagnostics = ordered_diagnostics
7
- end
8
-
9
- def create
10
- @diagnostic = Flow::Definition.new(create_params)
11
-
12
- if @diagnostic.save
13
- redirect_to manage_flow_path(@diagnostic), notice: "Flow created."
14
- else
15
- @diagnostics = ordered_diagnostics
16
- render :index, status: :unprocessable_entity
17
- end
18
- end
19
-
20
- def show
21
- @diagnostic = Flow::Definition.find(params[:id])
22
- @canvas = canvas_payload(@diagnostic)
23
- end
24
-
3
+ class FlowsController < EasyFlow::Manage::FlowsController
4
+ hosted_by FLOW_HOST
25
5
  def edit
26
- @diagnostic = Flow::Definition.find(params[:id])
6
+ super
7
+ summaries = Flow::Summaries.new(@flow)
8
+ @summary = summaries.text
9
+ @summary_page = summaries.summary_page
10
+ @published_pages = Page.where(id: Page::Version.live.select(:page_id)).order(:name)
27
11
  end
28
12
 
29
13
  def update
30
- @diagnostic = Flow::Definition.find(params[:id])
31
- @diagnostic.update!(diagnostic_params)
32
- redirect_to manage_flow_path(@diagnostic), notice: "Saved."
33
- end
34
-
35
- def destroy
36
- Flow::Definition.find(params[:id]).destroy!
37
- redirect_to manage_flows_path, notice: "Flow removed."
38
- end
39
-
40
- private
41
-
42
- def ordered_diagnostics
43
- Flow::Definition.order(:slug)
44
- end
45
-
46
- def create_params
47
- params.require(:flow).permit(:slug, :kind)
48
- end
14
+ details = params.require(:flow)
15
+ summaries = Flow::Summaries.new(flow_host.flows.find(params[:id]))
16
+ summaries.describe(details[:summary]) unless details[:summary].nil?
17
+ summaries.finish_on(Page.find_by(id: details[:summary_page_id])) unless details[:summary_page_id].nil?
49
18
 
50
- def diagnostic_params
51
- params.require(:flow).permit(:title, :summary, :start_label, :persists)
19
+ super
52
20
  end
53
21
  end
54
22
  end
@@ -20,6 +20,13 @@ module Alembic
20
20
  redirect_to manage_page_path(page)
21
21
  end
22
22
 
23
+ def publish
24
+ page = Page.find(params[:id])
25
+ page.publish
26
+
27
+ redirect_to manage_page_path(page)
28
+ end
29
+
23
30
  private
24
31
 
25
32
  def payload(page)
@@ -31,7 +38,7 @@ module Alembic
31
38
  end
32
39
 
33
40
  def page_params
34
- params.require(:page).permit(:name)
41
+ params.require(:page).permit(:name, :slug)
35
42
  end
36
43
  end
37
44
  end
@@ -1,10 +1,11 @@
1
1
  module Alembic
2
2
  module Manage
3
- class PreviewsController < ::Alembic::FlowsController
4
- include AuthenticatesAdmin
3
+ class PreviewsController < EasyFlow::Manage::PreviewsController
4
+ hosted_by FLOW_HOST
5
+ include Summarizes
5
6
 
6
7
  def show
7
- @diagnostic = previewed
8
+ @flow = previewed
8
9
  render template: "alembic/flows/show"
9
10
  end
10
11
 
@@ -17,22 +18,6 @@ module Alembic
17
18
  def flow_step_path(_slug)
18
19
  alembic.step_manage_flow_preview_path(previewed)
19
20
  end
20
-
21
- def previewing?
22
- true
23
- end
24
-
25
- def flowing_definition(diagnostic)
26
- diagnostic.document
27
- end
28
-
29
- def previewed
30
- @previewed ||= Flow::Definition.find(params[:flow_id])
31
- end
32
-
33
- def admit(_diagnostic)
34
- previewed
35
- end
36
21
  end
37
22
  end
38
23
  end
@@ -0,0 +1,19 @@
1
+ module Alembic
2
+ class PagesController < ApplicationController
3
+ helper PagesHelper
4
+ helper KsBlocks::LayoutHelper
5
+
6
+ def show
7
+ @page = shown_page
8
+ end
9
+
10
+ private
11
+
12
+ def shown_page
13
+ page = Page.find_by(slug: params[:slug])
14
+ raise NotPublished if page.nil? || page.live_version.nil?
15
+
16
+ page
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,59 @@
1
+ module Alembic
2
+ module Summarizes
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ layout -> { Alembic.layout }
7
+ helper ApplicationHelper
8
+ helper PagesHelper
9
+ helper ResultBlocksHelper
10
+ helper KsBlocks::LayoutHelper
11
+ helper_method :flow_summary, :first_step_path, :summary_values
12
+ end
13
+
14
+ private
15
+
16
+ def flow_summary(flow)
17
+ Flow::Summaries.new(flow).text
18
+ end
19
+
20
+ def first_step_path(slug)
21
+ flow_step_path(slug)
22
+ end
23
+
24
+ def start_run(flow)
25
+ super.tap { |run| Flow::Summaries.new(flow).pin(run) }
26
+ end
27
+
28
+ def finished(answers, finished_run)
29
+ @outputs = outputs_of(answers.transform_keys(&:to_s))
30
+ Flow::Summaries.new(flow).pin(finished_run) if finished_run && run.nil?
31
+ @summary_page = Flow::Summaries.new(run&.flow || flow).summary_page
32
+ render template: @summary_page&.live_version ? "alembic/flows/summary_page" : "alembic/flows/complete"
33
+ end
34
+
35
+ def summary_values
36
+ @outputs.to_h { |output| [ output.id, output.value ] }
37
+ .merge("slug" => @guide.slug, "note" => result_note, "answers" => answers_given)
38
+ end
39
+
40
+ def answers_given
41
+ @answered.map { |step, value| [ @guide.question_text(step), @guide.choice_label(step, value) ] }
42
+ end
43
+
44
+ def result_note
45
+ finished_flow = run&.flow || flow
46
+ return Alembic.lead_note.call(finished_flow.title, @outputs.to_h { |output| [ output.id, output.value ] }) if Alembic.lead_note
47
+
48
+ [ finished_flow.title, *@outputs.map { |output| "#{output.label}: #{helpers.alembic_output_lines(output.value).join(', ')}" } ]
49
+ .compact_blank.join(". ")
50
+ end
51
+
52
+ def outputs_of(state)
53
+ return Flow::Summaries.new(run.flow).of_run(run, state) if run
54
+
55
+ summaries = Flow::Summaries.new(flow)
56
+ summaries.any? ? summaries.of(state) : []
57
+ end
58
+ end
59
+ end
@@ -2,8 +2,8 @@ module Alembic
2
2
  module ApplicationHelper
3
3
  def alembic_output_lines(value)
4
4
  case value
5
- when Hash then value.map { |name, share| "#{name}: #{share}" }
6
- when Array then value
5
+ when Hash then value.key?("name") ? [ value["name"] ] : value.map { |name, share| "#{name}: #{share}" }
6
+ when Array then value.map { |item| item.is_a?(Hash) ? item["name"] : item }
7
7
  else [ value ]
8
8
  end
9
9
  end
@@ -0,0 +1,40 @@
1
+ require "ks_blocks/layout_helper"
2
+
3
+ module Alembic
4
+ module PagesHelper
5
+ UNDRAWABLE = "This block cannot be drawn until its fields are filled in.".freeze
6
+
7
+ def drawn_page(page, being_edited: false, values: {})
8
+ blocks = being_edited ? page.blocks : page.live_version&.blocks.to_a
9
+ drawn = blocks.to_h { |block| [ block["id"], drawn_block(block, values: values) ] }.compact
10
+
11
+ block_layout(blocks.select { |block| drawn.key?(block["id"]) }, kind: :pages) do |block|
12
+ drawn.fetch(block["id"])
13
+ end
14
+ end
15
+
16
+ def drawn_block(block, values: {})
17
+ component = Page::Drawing.of(block["type"])
18
+ return unless component
19
+
20
+ options = Page::Options.for(block, values: values)
21
+ body = Page::Options.body_for(block)
22
+ body.nil? ? send(component, **options) : send(component, **options) { body }
23
+ rescue StandardError => undrawable
24
+ Rails.logger.error("Alembic could not draw the #{block['type']} block: #{undrawable.message}")
25
+ nil
26
+ end
27
+
28
+ def shown_block(block)
29
+ drawn_block(block) || undrawn_block(block)
30
+ end
31
+
32
+ private
33
+
34
+ def undrawn_block(block)
35
+ return UNDRAWABLE if Page::Drawing.of(block["type"])
36
+
37
+ KsBlocks.registry.block_types(kind: :pages).find { |type| type.key.to_s == block["type"] }&.name
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,45 @@
1
+ module Alembic
2
+ module ResultBlocksHelper
3
+ def alembic_score_block(value: nil, caption: nil, kicker: "Your result")
4
+ render "alembic/blocks/score", value: value, caption: caption, kicker: kicker
5
+ end
6
+
7
+ def alembic_band_block(value: nil, score: nil)
8
+ render "alembic/blocks/band", band: value.to_h, tone: alembic_tone(score)
9
+ end
10
+
11
+ def alembic_categories_block(value: nil, heading: "Where you stand, area by area")
12
+ render "alembic/blocks/categories", shares: value.to_h, heading: heading
13
+ end
14
+
15
+ def alembic_weakest_block(value: nil, heading: "Your biggest blind spots", cost_label: "What it likely costs you:")
16
+ render "alembic/blocks/weakest", categories: Array(value), heading: heading, cost_label: cost_label
17
+ end
18
+
19
+ def alembic_answers_block(value: nil, heading: "What you told us")
20
+ render "alembic/blocks/answers", answers: Array(value), heading: heading
21
+ end
22
+
23
+ def alembic_lead_block(slug:, note: nil, heading: "The next step", blurb: nil, button: "Send", placeholder: "you@example.com")
24
+ render "alembic/blocks/lead", address: Alembic.lead_address.call(slug), slug: slug, note: note,
25
+ heading: heading, blurb: blurb, button: button, placeholder: placeholder
26
+ end
27
+
28
+ def alembic_default_result(output, outputs = [])
29
+ case output.type
30
+ when "percentage" then alembic_score_block(value: output.value, caption: output.label)
31
+ when "band" then alembic_band_block(value: output.value, score: outputs.find { |other| other.type == "percentage" }&.value)
32
+ when "grouped" then alembic_categories_block(value: output.value)
33
+ when "lowest" then alembic_weakest_block(value: output.value)
34
+ else ui_stat_card(label: output.label, value: alembic_output_lines(output.value).join(" · "))
35
+ end
36
+ end
37
+
38
+ def alembic_tone(percentage)
39
+ return "bg-red-500" if percentage.to_i < 40
40
+ return "bg-amber-500" if percentage.to_i < 70
41
+
42
+ "bg-emerald-500"
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,10 @@
1
+ module Alembic
2
+ module Flow
3
+ class DefinitionSummary < ApplicationRecord
4
+ self.table_name = "alembic_flow_definition_summaries"
5
+
6
+ belongs_to :flow, class_name: "EasyFlow::Definition"
7
+ belongs_to :summary_page, class_name: "Alembic::Page", optional: true
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Alembic
2
+ module Flow
3
+ class RunSummary < ApplicationRecord
4
+ self.table_name = "alembic_flow_run_summaries"
5
+
6
+ belongs_to :run, class_name: "EasyFlow::Run"
7
+ belongs_to :summary_version
8
+ end
9
+ end
10
+ end
@@ -10,12 +10,12 @@ module Alembic
10
10
  end
11
11
 
12
12
  def current_version
13
- @flow.summary_versions.find_by(number: cursor)
13
+ versions.find_by(number: cursor)
14
14
  end
15
15
 
16
16
  def record(payload)
17
- @flow.summary_versions.create!(number: next_number, summary: payload)
18
- .tap { |version| @flow.update!(summary_cursor: version.number) }
17
+ versions.create!(number: next_number, summary: payload)
18
+ .tap { |version| details.update!(summary_cursor: version.number) }
19
19
  end
20
20
 
21
21
  def any?
@@ -26,18 +26,57 @@ module Alembic
26
26
  Summary::Report.new(document).results(Summary::Run.new(state: state, steps: steps_by_id))
27
27
  end
28
28
 
29
+ def text
30
+ details.summary
31
+ end
32
+
33
+ def describe(text)
34
+ details.update!(summary: text)
35
+ end
36
+
37
+ def summary_page
38
+ details.summary_page
39
+ end
40
+
41
+ def finish_on(page)
42
+ details.update!(summary_page: page)
43
+ end
44
+
45
+ def pin(run)
46
+ RunSummary.create!(run: run, summary_version: current_version) if current_version
47
+ end
48
+
49
+ def pinned_to(run)
50
+ RunSummary.find_by(run: run)&.summary_version
51
+ end
52
+
53
+ def of_run(run, state)
54
+ pinned = pinned_to(run)&.summary
55
+ return [] if pinned.blank?
56
+
57
+ Summary::Report.new(pinned).results(Summary::Run.new(state: state, steps: run.pinned_steps))
58
+ end
59
+
29
60
  private
30
61
 
62
+ def versions
63
+ SummaryVersion.where(flow: @flow)
64
+ end
65
+
66
+ def details
67
+ @details ||= DefinitionSummary.find_or_initialize_by(flow: @flow)
68
+ end
69
+
31
70
  def steps_by_id
32
71
  Array(@flow.definition.to_h["nodes"]).index_by { |node| node["id"] }
33
72
  end
34
73
 
35
74
  def cursor
36
- @flow.summary_cursor || @flow.summary_versions.pluck(:number).max
75
+ details.summary_cursor || versions.maximum(:number)
37
76
  end
38
77
 
39
78
  def next_number
40
- (@flow.summary_versions.maximum(:number) || 0) + 1
79
+ (versions.maximum(:number) || 0) + 1
41
80
  end
42
81
  end
43
82
  end
@@ -3,7 +3,7 @@ module Alembic
3
3
  class SummaryVersion < ApplicationRecord
4
4
  self.table_name = "alembic_flow_summaries"
5
5
 
6
- belongs_to :flow, class_name: "Alembic::Flow::Definition"
6
+ belongs_to :flow, class_name: "EasyFlow::Definition"
7
7
 
8
8
  validates :number, uniqueness: { scope: :flow_id }
9
9
 
@@ -15,7 +15,7 @@ module Alembic
15
15
  def self.covering(config, number)
16
16
  ordered = Array(config["bands"]).sort_by { |band| band["ceiling"] || Float::INFINITY }
17
17
 
18
- ordered.find { |band| band["ceiling"].nil? || number < band["ceiling"] }&.fetch("name", nil)
18
+ ordered.find { |band| band["ceiling"].nil? || number < band["ceiling"] }&.slice("name", "description")
19
19
  end
20
20
  end
21
21
  end
@@ -13,7 +13,7 @@ module Alembic
13
13
  end
14
14
 
15
15
  def self.bucket(step, marker)
16
- marker ? step[marker] : Steps::Question.category_of(step)
16
+ marker ? step[marker] : EasyFlow::Steps::Question.category_of(step)
17
17
  end
18
18
 
19
19
  def self.shares(run, marker)
@@ -4,7 +4,7 @@ module Alembic
4
4
  def self.output_type
5
5
  Summary::OutputType.define(:lowest) do
6
6
  label "Weakest"
7
- compute { |config, _run, so_far| Lowest.of(so_far[config["of"]], config["count"]) }
7
+ compute { |config, _run, so_far| Lowest.of(so_far[config["of"]], config["count"], config["copy"]) }
8
8
  end
9
9
  end
10
10
 
@@ -12,8 +12,10 @@ module Alembic
12
12
  registry.register(output_type)
13
13
  end
14
14
 
15
- def self.of(shares, count)
16
- shares.to_h.sort_by { |_name, share| share }.first(count.presence&.to_i || 1).map(&:first)
15
+ def self.of(shares, count, copy = nil)
16
+ weakest = shares.to_h.sort_by { |_name, share| share }.first(count.presence&.to_i || 1).map(&:first)
17
+
18
+ weakest.map { |name| { "name" => name, **copy.to_h.fetch(name, {}).slice("miss", "cost") } }
17
19
  end
18
20
  end
19
21
  end
@@ -24,7 +24,7 @@ module Alembic
24
24
  end
25
25
 
26
26
  def self.most(step)
27
- Steps::Question.answers_of(step).filter_map { |option| option["weight"].to_i if option.is_a?(Hash) }.max.to_i
27
+ EasyFlow::Steps::Question.answers_of(step).filter_map { |option| option["weight"].to_i if option.is_a?(Hash) }.max.to_i
28
28
  end
29
29
  end
30
30
  end
@@ -17,7 +17,7 @@ module Alembic
17
17
  end
18
18
 
19
19
  def self.weight_of(step, value)
20
- chosen = Steps::Question.answers_of(step).find { |option| option.is_a?(Hash) && option["value"] == value }
20
+ chosen = EasyFlow::Steps::Question.answers_of(step).find { |option| option.is_a?(Hash) && option["value"] == value }
21
21
 
22
22
  chosen&.fetch("weight", nil).to_i
23
23
  end
@@ -0,0 +1,17 @@
1
+ module Alembic
2
+ class Page < ApplicationRecord
3
+ module Drawing
4
+ def self.record(key, component)
5
+ components[key.to_sym] = component
6
+ end
7
+
8
+ def self.of(key)
9
+ components[key.to_sym]
10
+ end
11
+
12
+ def self.components
13
+ @components ||= {}
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,53 @@
1
+ module Alembic
2
+ class Page < ApplicationRecord
3
+ module Options
4
+ def self.declare(key, options, body: nil)
5
+ declared[key.to_sym] = options
6
+ bodies[key.to_sym] = body
7
+ end
8
+
9
+ def self.for(block, values: {})
10
+ named(filled(block).except(bodies[block["type"].to_sym]), declared.fetch(block["type"].to_sym, {}), values)
11
+ end
12
+
13
+ def self.body_for(block)
14
+ field = bodies[block["type"].to_sym]
15
+
16
+ filled(block)[field] if field
17
+ end
18
+
19
+ def self.bodies
20
+ @bodies ||= {}
21
+ end
22
+
23
+ def self.declared
24
+ @declared ||= {}
25
+ end
26
+
27
+ def self.filled(block)
28
+ block.fetch("content", {}).reject { |_key, value| value == "" }.symbolize_keys
29
+ end
30
+
31
+ def self.named(filled, options, values)
32
+ options.each_with_object(filled) do |(option, spec), built|
33
+ spec.is_a?(Hash) ? fill(built, option, spec, values) : built[option] = spec
34
+ end
35
+ end
36
+
37
+ def self.fill(built, option, spec, values)
38
+ value = handed(built, option, spec, values)
39
+ built[option] = value.nil? ? spec[:default] : value
40
+ built.delete(option) if built[option].nil?
41
+ end
42
+
43
+ def self.handed(built, option, spec, values)
44
+ return values[spec[:value]] if spec.key?(:value)
45
+ return values[built.delete(spec[:value_of]).to_s] if spec.key?(:value_of)
46
+
47
+ built.delete(spec.fetch(:from, option))
48
+ end
49
+
50
+ private_class_method :filled, :named, :fill, :handed
51
+ end
52
+ end
53
+ end