caixanegra 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +18 -0
- data/Rakefile +32 -0
- data/app/assets/config/caixanegra_manifest.js +2 -0
- data/app/assets/javascripts/caixanegra/api.js +69 -0
- data/app/assets/javascripts/caixanegra/caixanegra.js +1 -0
- data/app/assets/javascripts/caixanegra/designer.js +1281 -0
- data/app/assets/javascripts/caixanegra/sabertooth.js +310 -0
- data/app/assets/stylesheets/caixanegra/application.css +15 -0
- data/app/assets/stylesheets/caixanegra/designer.scss +804 -0
- data/app/controllers/caixanegra/api/designer/flows_controller.rb +44 -0
- data/app/controllers/caixanegra/api/designer/units_controller.rb +43 -0
- data/app/controllers/caixanegra/api_controller.rb +6 -0
- data/app/controllers/caixanegra/application_controller.rb +7 -0
- data/app/controllers/caixanegra/designer_controller.rb +9 -0
- data/app/helpers/caixanegra/application_helper.rb +4 -0
- data/app/models/caixanegra/unit.rb +131 -0
- data/app/views/caixanegra/designer/index.html.erb +109 -0
- data/app/views/layouts/caixanegra/application.html.erb +16 -0
- data/app/views/shared/caixanegra/_js_dependencies.html.erb +4 -0
- data/config/routes.rb +14 -0
- data/lib/caixanegra/engine.rb +25 -0
- data/lib/caixanegra/exceptions.rb +5 -0
- data/lib/caixanegra/executor.rb +201 -0
- data/lib/caixanegra/manager.rb +32 -0
- data/lib/caixanegra/version.rb +3 -0
- data/lib/caixanegra.rb +8 -0
- data/lib/tasks/caixanegra_tasks.rake +4 -0
- metadata +100 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Caixanegra
|
4
|
+
module API
|
5
|
+
module Designer
|
6
|
+
class FlowsController < ::Caixanegra::APIController
|
7
|
+
before_action :set_flow, only: %i[show debug_run]
|
8
|
+
|
9
|
+
def show
|
10
|
+
render json: @flow
|
11
|
+
end
|
12
|
+
|
13
|
+
def update
|
14
|
+
Caixanegra::Manager.set(params[:id], JSON.parse(request.body.read))
|
15
|
+
|
16
|
+
head :ok
|
17
|
+
end
|
18
|
+
|
19
|
+
def debug_run
|
20
|
+
execution = Caixanegra::Executor.new(
|
21
|
+
initial_carryover: initial_carryover,
|
22
|
+
flow_definition: @flow,
|
23
|
+
unit_scope: params[:unit_scope],
|
24
|
+
debug_mode: true
|
25
|
+
).run
|
26
|
+
|
27
|
+
render json: { result: execution[:result], debug: execution[:debug] }
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def initial_carryover
|
33
|
+
JSON.parse(request.body.read)
|
34
|
+
rescue StandardError
|
35
|
+
{}
|
36
|
+
end
|
37
|
+
|
38
|
+
def set_flow
|
39
|
+
@flow = Caixanegra::Manager.get(params[:id])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Caixanegra
|
4
|
+
module API
|
5
|
+
module Designer
|
6
|
+
class UnitsController < ::Caixanegra::APIController
|
7
|
+
def index
|
8
|
+
render json: units
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def unit_scope
|
14
|
+
params[:scope].presence || nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def units
|
18
|
+
@units ||= begin
|
19
|
+
base_units = Caixanegra.units.reject { |_, v| v.is_a? Hash }
|
20
|
+
scope_units = Caixanegra.units[unit_scope] if unit_scope.present?
|
21
|
+
all_units = base_units.merge(scope_units || {})
|
22
|
+
|
23
|
+
all_units.map do |k, v|
|
24
|
+
base = {
|
25
|
+
title: v.name,
|
26
|
+
type: v.type,
|
27
|
+
description: v.description,
|
28
|
+
class: k,
|
29
|
+
exits: v.exits&.map { |e| { name: e } },
|
30
|
+
inputs: v.inputs,
|
31
|
+
assignments: v.assignments
|
32
|
+
}
|
33
|
+
|
34
|
+
base[:set] = v.set if base[:set]
|
35
|
+
|
36
|
+
base
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Caixanegra
|
4
|
+
class Unit
|
5
|
+
attr_reader :oid
|
6
|
+
|
7
|
+
def initialize(oid, inputs = {}, mappings = {}, carry_over = {}, storage = {})
|
8
|
+
@oid = oid
|
9
|
+
@mappings = mappings
|
10
|
+
@inputs = inputs
|
11
|
+
@carry_over = carry_over.with_indifferent_access
|
12
|
+
@storage = storage.with_indifferent_access
|
13
|
+
set_mapping_defaults
|
14
|
+
end
|
15
|
+
|
16
|
+
def current_carry_over
|
17
|
+
@carry_over.dup
|
18
|
+
end
|
19
|
+
|
20
|
+
def current_storage
|
21
|
+
@storage.dup
|
22
|
+
end
|
23
|
+
|
24
|
+
def carry_over(value)
|
25
|
+
@carry_over.merge!(value) if value.is_a? Hash
|
26
|
+
end
|
27
|
+
|
28
|
+
def persist(value)
|
29
|
+
@storage.merge!(value) if value.is_a? Hash
|
30
|
+
end
|
31
|
+
|
32
|
+
def exit_through(exit_id)
|
33
|
+
{
|
34
|
+
exit_through: exit_id,
|
35
|
+
carry_over: @carry_over
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def flow
|
40
|
+
exit_through exits.first
|
41
|
+
end
|
42
|
+
|
43
|
+
def input(id)
|
44
|
+
input_metadata = @mappings[id]
|
45
|
+
input_value = case input_metadata&.[](:type)
|
46
|
+
when 'storage'
|
47
|
+
@storage[id]
|
48
|
+
when 'carryover'
|
49
|
+
@carry_over[id]
|
50
|
+
when 'user'
|
51
|
+
input_metadata[:value]
|
52
|
+
end
|
53
|
+
|
54
|
+
if input_value.present?
|
55
|
+
result = input_value.dup
|
56
|
+
input_value.to_s.scan(Regexp.new(/%(.*?)%/)) do |match|
|
57
|
+
match = match[0]
|
58
|
+
result.gsub!("%#{match}%", @carry_over[match] || '')
|
59
|
+
end
|
60
|
+
input_value.to_s.scan(Regexp.new(/@(.*?)@/)) do |match|
|
61
|
+
match = match[0]
|
62
|
+
result.gsub!("@#{match}@", @storage[match] || '')
|
63
|
+
end
|
64
|
+
|
65
|
+
input_value = result
|
66
|
+
end
|
67
|
+
input_value.presence || @inputs[id][:default]
|
68
|
+
end
|
69
|
+
|
70
|
+
def name
|
71
|
+
self.class.name
|
72
|
+
end
|
73
|
+
|
74
|
+
def description
|
75
|
+
self.class.description
|
76
|
+
end
|
77
|
+
|
78
|
+
def inputs
|
79
|
+
self.class.inputs || []
|
80
|
+
end
|
81
|
+
|
82
|
+
def exits
|
83
|
+
self.class.exits || []
|
84
|
+
end
|
85
|
+
|
86
|
+
def set
|
87
|
+
self.class.set || []
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def set_mapping_defaults
|
93
|
+
(self.class.inputs || {}).each do |key, data|
|
94
|
+
next if @mappings.key? key
|
95
|
+
|
96
|
+
@mappings[key] = { type: 'carryover' }
|
97
|
+
@mappings[key][:value] = data[:default] if data[:default].present?
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class << self
|
102
|
+
attr_reader :name, :description, :inputs, :exits, :assignments, :type
|
103
|
+
|
104
|
+
@type = :passthrough
|
105
|
+
|
106
|
+
def configure_name(value)
|
107
|
+
@name = value
|
108
|
+
end
|
109
|
+
|
110
|
+
def configure_description(value)
|
111
|
+
@description = value
|
112
|
+
end
|
113
|
+
|
114
|
+
def configure_type(value)
|
115
|
+
@type = value
|
116
|
+
end
|
117
|
+
|
118
|
+
def configure_exits(exits)
|
119
|
+
@exits = exits
|
120
|
+
end
|
121
|
+
|
122
|
+
def configure_inputs(inputs)
|
123
|
+
@inputs = inputs
|
124
|
+
end
|
125
|
+
|
126
|
+
def configure_assignments(assignments)
|
127
|
+
@assignments = assignments
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
<consts>
|
2
|
+
<mounted_path value="<%= @mounted_path %>" />
|
3
|
+
</consts>
|
4
|
+
<canvas style="position:fixed;top:0px;left:0px;"></canvas>
|
5
|
+
<div id="execution">
|
6
|
+
<div id="reset">
|
7
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" xml:space="preserve">
|
8
|
+
<g><path d="M17.1,23.8L12.4,21l2.7-4.8l1.7,1l-1.7,3.1l3,1.8L17.1,23.8z M5.7,11L4,8L1.1,9.7L0.1,8l4.7-2.8L7.5,10L5.7,11z"/></g>
|
9
|
+
<g><polygon points="22,6 16.5,6 16.5,4 20,4 20,0.5 22,0.5"/></g>
|
10
|
+
<g><path d="M15.4,21.5l-0.4-2c4-0.9,6.9-4.5,6.9-8.6c0-0.6-0.1-1.3-0.2-1.9l2-0.4c0.2,0.8,0.3,1.6,0.3,2.3 C24,16.1,20.4,20.5,15.4,21.5z"/></g>
|
11
|
+
<g><path d="M9.8,21.3C5.3,19.9,2.2,15.8,2.2,11c0-1.3,0.2-2.6,0.7-3.8l1.9,0.7c-0.4,1-0.6,2-0.6,3.1c0,3.9,2.5,7.2,6.1,8.4L9.8,21.3z"/></g>
|
12
|
+
<g><path d="M19.6,5c-1.7-1.9-4.1-3-6.6-3c-2.1,0-4.1,0.8-5.7,2.1L6,2.6C7.9,0.9,10.4,0,13,0c3.1,0,6,1.3,8.1,3.6L19.6,5z"/></g>
|
13
|
+
</svg>
|
14
|
+
</div>
|
15
|
+
<div id="play">
|
16
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" xml:space="preserve">
|
17
|
+
<path d="M19,12L5,22.2V1.8L19,12z"/>
|
18
|
+
</svg>
|
19
|
+
</div>
|
20
|
+
<div id="configure">
|
21
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
22
|
+
<path d="M19,17h-2v-2.1c-0.4-0.1-0.7-0.2-1-0.4l-1.5,1.5l-1.4-1.4l1.5-1.5c-0.2-0.3-0.3-0.7-0.4-1H12v-2h2.1 c0.1-0.4,0.2-0.7,0.4-1l-1.5-1.5l1.4-1.4L16,7.6c0.3-0.2,0.7-0.3,1-0.4V5h2v2.1c0.4,0.1,0.7,0.2,1,0.4l1.5-1.5l1.4,1.4L21.4,9 c0.2,0.3,0.3,0.7,0.4,1H24v2h-2.1c-0.1,0.4-0.2,0.7-0.4,1l1.5,1.5l-1.4,1.4L20,14.4c-0.3,0.2-0.7,0.3-1,0.4V17z M18,9 c-1.1,0-2,0.9-2,2c0,1.1,0.9,2,2,2s2-0.9,2-2C20,9.9,19.1,9,18,9z"/>
|
23
|
+
<path d="M7,12H5V9.9C4.6,9.8,4.3,9.6,4,9.4l-1.5,1.5L1.1,9.5L2.6,8C2.4,7.7,2.2,7.4,2.1,7H0V5h2.1c0.1-0.4,0.2-0.7,0.4-1L1.1,2.5 l1.4-1.4L4,2.6c0.3-0.2,0.7-0.3,1-0.4V0h2v2.1c0.4,0.1,0.7,0.2,1,0.4l1.5-1.5l1.4,1.4L9.4,4c0.2,0.3,0.3,0.7,0.4,1H12v2H9.9 C9.8,7.4,9.6,7.7,9.4,8l1.5,1.5l-1.4,1.4L8,9.4C7.7,9.6,7.4,9.8,7,9.9V12z M6,4C4.9,4,4,4.9,4,6s0.9,2,2,2s2-0.9,2-2S7.1,4,6,4z"/>
|
24
|
+
<path d="M9,24H7v-2.1c-0.4-0.1-0.7-0.2-1-0.4l-1.5,1.5l-1.4-1.4L4.6,20c-0.2-0.3-0.3-0.7-0.4-1H2v-2h2.1c0.1-0.4,0.2-0.7,0.4-1 l-1.5-1.5l1.4-1.4L6,14.6c0.3-0.2,0.7-0.3,1-0.4V12h2v2.1c0.4,0.1,0.7,0.2,1,0.4l1.5-1.5l1.4,1.4L11.4,16c0.2,0.3,0.3,0.7,0.4,1 H14v2h-2.1c-0.1,0.4-0.2,0.7-0.4,1l1.5,1.5l-1.4,1.4L10,21.4c-0.3,0.2-0.7,0.3-1,0.4V24z M8,16c-1.1,0-2,0.9-2,2s0.9,2,2,2 s2-0.9,2-2S9.1,16,8,16z"/>
|
25
|
+
</svg>
|
26
|
+
</div>
|
27
|
+
<div id="console">
|
28
|
+
<svg version="1.1" id="XMLID_75_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" xml:space="preserve">
|
29
|
+
<path d="M24,24H0V0h24V24z M2,22h20V2H6v2h16v2H2V22z M2,4h2V2H2V4z M16,16h-6v-2h6V16z M5.7,15.7l-1.4-1.4L6.6,12L4.3,9.7 l1.4-1.4L9.4,12L5.7,15.7z"/>
|
30
|
+
</svg>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
<div id="addUnit"><span>+</span></div>
|
34
|
+
<div id="save"><span>save</span></div>
|
35
|
+
<div id="unitMenu"></div>
|
36
|
+
<div id="executionConsole"></div>
|
37
|
+
<div id="executionConfiguration">
|
38
|
+
<div class="section">
|
39
|
+
<span>initial carry-over</span>
|
40
|
+
<span id="clearCarryOverObject">clear</span>
|
41
|
+
</div>
|
42
|
+
<div id="carryOverObject"></div>
|
43
|
+
</div>
|
44
|
+
<div id="unitDebugHits">
|
45
|
+
<div class="content">
|
46
|
+
<div class="name">run hits</div>
|
47
|
+
<div id="debugData"></div>
|
48
|
+
</div>
|
49
|
+
</div>
|
50
|
+
<div id="unitDetail">
|
51
|
+
<div class="color-code"></div>
|
52
|
+
<div class="content">
|
53
|
+
<div id="unitDetailClass" class="unit-detail-headers">
|
54
|
+
<div class="name"></div>
|
55
|
+
<div class="delete">
|
56
|
+
<div id="toggler">
|
57
|
+
<svg viewBox="0 0 56 56" xmlns="http://www.w3.org/2000/svg">
|
58
|
+
<path d="M 44.5235 48.6602 L 46.1407 14.3945 L 48.4844 14.3945 C 49.4454 14.3945 50.2187 13.5976 50.2187 12.6367 C 50.2187 11.6758 49.4454 10.8555 48.4844 10.8555 L 38.2422 10.8555 L 38.2422 7.3398 C 38.2422 3.9883 35.9688 1.8086 32.3595 1.8086 L 23.5938 1.8086 C 19.9844 1.8086 17.7344 3.9883 17.7344 7.3398 L 17.7344 10.8555 L 7.5391 10.8555 C 6.6016 10.8555 5.7813 11.6758 5.7813 12.6367 C 5.7813 13.5976 6.6016 14.3945 7.5391 14.3945 L 9.8829 14.3945 L 11.5000 48.6836 C 11.6641 52.0586 13.8907 54.1914 17.2657 54.1914 L 38.7579 54.1914 C 42.1095 54.1914 44.3595 52.0351 44.5235 48.6602 Z M 21.4844 7.5742 C 21.4844 6.2383 22.4688 5.3008 23.8751 5.3008 L 32.1016 5.3008 C 33.5313 5.3008 34.5157 6.2383 34.5157 7.5742 L 34.5157 10.8555 L 21.4844 10.8555 Z M 17.6173 50.6758 C 16.2579 50.6758 15.2500 49.6445 15.1797 48.2852 L 13.5391 14.3945 L 42.3907 14.3945 L 40.8438 48.2852 C 40.7735 49.6680 39.7891 50.6758 38.4063 50.6758 Z M 34.9610 46.5508 C 35.7344 46.5508 36.3204 45.9180 36.3438 45.0273 L 37.0469 20.2773 C 37.0704 19.3867 36.4610 18.7305 35.6641 18.7305 C 34.9376 18.7305 34.3282 19.4102 34.3048 20.2539 L 33.6016 45.0273 C 33.5782 45.8711 34.1641 46.5508 34.9610 46.5508 Z M 21.0626 46.5508 C 21.8595 46.5508 22.4454 45.8711 22.4219 45.0273 L 21.7188 20.2539 C 21.6954 19.4102 21.0626 18.7305 20.3360 18.7305 C 19.5391 18.7305 18.9532 19.3867 18.9766 20.2773 L 19.7032 45.0273 C 19.7266 45.9180 20.2891 46.5508 21.0626 46.5508 Z M 29.4298 45.0273 L 29.4298 20.2539 C 29.4298 19.4102 28.7969 18.7305 28.0235 18.7305 C 27.2500 18.7305 26.5938 19.4102 26.5938 20.2539 L 26.5938 45.0273 C 26.5938 45.8711 27.2500 46.5508 28.0235 46.5508 C 28.7735 46.5508 29.4298 45.8711 29.4298 45.0273 Z"/>
|
59
|
+
</svg>
|
60
|
+
</div>
|
61
|
+
<div class="confirm-buttons">
|
62
|
+
<div id="confirmDelete">
|
63
|
+
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
64
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22ZM10.2426 14.4142L17.3137 7.34315L18.7279 8.75736L10.2426 17.2426L6 13L7.41421 11.5858L10.2426 14.4142Z"/>
|
65
|
+
</svg>
|
66
|
+
</div>
|
67
|
+
<div id="cancelDelete">
|
68
|
+
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
69
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM15.7123 16.773L12 13.0607L8.28769 16.773L7.22703 15.7123L10.9393 12L7.22703 8.28769L8.28769 7.22703L12 10.9393L15.7123 7.22703L16.773 8.28769L13.0607 12L16.773 15.7123L15.7123 16.773Z"/>
|
70
|
+
</svg>
|
71
|
+
</div>
|
72
|
+
</div>
|
73
|
+
</div>
|
74
|
+
</div>
|
75
|
+
<div id="unitDetailDescription"></div>
|
76
|
+
<div class="field">
|
77
|
+
<input type="text" id="unitDetailTitle" placeholder="title" />
|
78
|
+
</div>
|
79
|
+
<div id="dynamicContent"></div>
|
80
|
+
</div>
|
81
|
+
</div>
|
82
|
+
<div id="blocker">
|
83
|
+
<div class="information">
|
84
|
+
<div>caixa<span>negra</span></div>
|
85
|
+
<div class="message">loading your flow</div>
|
86
|
+
</div>
|
87
|
+
</div>
|
88
|
+
<div id="action_messenger">
|
89
|
+
<div class="icon icon-error">
|
90
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 51.976 51.976" xml:space="preserve">
|
91
|
+
<path d="M44.373,7.603c-10.137-10.137-26.632-10.138-36.77,0c-10.138,10.138-10.137,26.632,0,36.77s26.632,10.138,36.77,0 C54.51,34.235,54.51,17.74,44.373,7.603z M36.241,36.241c-0.781,0.781-2.047,0.781-2.828,0l-7.425-7.425l-7.778,7.778 c-0.781,0.781-2.047,0.781-2.828,0c-0.781-0.781-0.781-2.047,0-2.828l7.778-7.778l-7.425-7.425c-0.781-0.781-0.781-2.048,0-2.828 c0.781-0.781,2.047-0.781,2.828,0l7.425,7.425l7.071-7.071c0.781-0.781,2.047-0.781,2.828,0c0.781,0.781,0.781,2.047,0,2.828 l-7.071,7.071l7.425,7.425C37.022,34.194,37.022,35.46,36.241,36.241z"/>
|
92
|
+
</svg>
|
93
|
+
</div>
|
94
|
+
<div class="icon icon-working">
|
95
|
+
<svg viewBox="0 -8 72 72" xmlns="http://www.w3.org/2000/svg">
|
96
|
+
<path d="M56.74,20.89l-1-2.31c3.33-7.53,3.11-7.75,2.46-8.41L54,6l-.42-.35h-.49c-.26,0-1,0-7.51,2.93l-2.38-1C40.09,0,39.77,0,38.87,0h-6c-.9,0-1.25,0-4.1,7.66l-2.37,1C22,6.78,19.45,5.84,18.75,5.84l-.56,0-4.58,4.49c-.7.65-.94.88,2.58,8.3l-1,2.3c-7.79,3-7.79,3.3-7.79,4.23v5.89c0,.92,0,1.25,7.82,4l1,2.29c-3.33,7.53-3.11,7.76-2.46,8.41L18,50l.42.37h.5c.25,0,1,0,7.5-3l2.38,1C31.9,56,32.21,56,33.12,56h6c.92,0,1.25,0,4.11-7.66l2.39-1c4.37,1.85,6.93,2.79,7.61,2.79l.57,0,4.62-4.52c.66-.66.89-.89-2.62-8.28l1-2.3c7.81-3,7.81-3.33,7.81-4.23V24.93C64.57,24,64.57,23.68,56.74,20.89ZM36,37.8A9.8,9.8,0,1,1,46,28,9.91,9.91,0,0,1,36,37.8Z"/>
|
97
|
+
</svg>
|
98
|
+
</div>
|
99
|
+
<div class="icon icon-ok">
|
100
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 490.05 490.05" xml:space="preserve">
|
101
|
+
<g>
|
102
|
+
<path d="M418.275,418.275c95.7-95.7,95.7-250.8,0-346.5s-250.8-95.7-346.5,0s-95.7,250.8,0,346.5S322.675,513.975,418.275,418.275
|
103
|
+
z M157.175,207.575l55.1,55.1l120.7-120.6l42.7,42.7l-120.6,120.6l-42.8,42.7l-42.7-42.7l-55.1-55.1L157.175,207.575z"/>
|
104
|
+
</g>
|
105
|
+
</svg>
|
106
|
+
</div>
|
107
|
+
<div class="message"></div>
|
108
|
+
</div>
|
109
|
+
<%= render partial: "shared/caixanegra/js_dependencies" %>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Caixanegra</title>
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
6
|
+
<%= csrf_meta_tags %>
|
7
|
+
<%= csp_meta_tag %>
|
8
|
+
|
9
|
+
<%= stylesheet_link_tag "caixanegra/application", media: "all" %>
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
|
13
|
+
<%= yield %>
|
14
|
+
|
15
|
+
</body>
|
16
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Caixanegra::Engine.routes.draw do
|
2
|
+
get '/design/:id', to: 'designer#index'
|
3
|
+
|
4
|
+
namespace :api, constraints: { format: :json } do
|
5
|
+
namespace :designer do
|
6
|
+
resources :units, only: %i[index]
|
7
|
+
resources :flows, only: %i[show update] do
|
8
|
+
member do
|
9
|
+
patch :debug_run
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Caixanegra
|
4
|
+
self.mattr_accessor :units
|
5
|
+
self.mattr_accessor :redis
|
6
|
+
self.units = []
|
7
|
+
|
8
|
+
def self.setup(&block)
|
9
|
+
yield self
|
10
|
+
end
|
11
|
+
|
12
|
+
class Engine < ::Rails::Engine
|
13
|
+
isolate_namespace Caixanegra
|
14
|
+
|
15
|
+
initializer "caixanegra.assets.precompile" do |app|
|
16
|
+
app.config.assets.precompile += %w[
|
17
|
+
caixanegra/api.js
|
18
|
+
caixanegra/caixanegra.js
|
19
|
+
caixanegra/designer.js
|
20
|
+
caixanegra/sabertooth.js
|
21
|
+
caixanegra/application.css
|
22
|
+
]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Caixanegra
|
4
|
+
class Executor
|
5
|
+
def initialize(params)
|
6
|
+
@initial_carryover = params[:initial_carryover] || {}
|
7
|
+
@flow = (params[:flow_definition] || {}).deep_symbolize_keys
|
8
|
+
@unit_scope = params[:unit_scope]
|
9
|
+
@debug_mode = params[:debug_mode] == true
|
10
|
+
@execution = { history: [], steps: [] }
|
11
|
+
@storage = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
run_and_report_result
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def format_result(result)
|
21
|
+
return result unless @debug_mode
|
22
|
+
|
23
|
+
{
|
24
|
+
result: result,
|
25
|
+
debug: @execution
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def run_and_report_result
|
30
|
+
log_console_entry "Started"
|
31
|
+
set_start_unit
|
32
|
+
format_result(flow_through)
|
33
|
+
rescue Caixanegra::UnitScopedException => e
|
34
|
+
log_step_exception(e)
|
35
|
+
log_console_entry("Unit error: #{e.message}")
|
36
|
+
format_result(e.message)
|
37
|
+
rescue => e
|
38
|
+
log_console_entry("Error: #{e.message}")
|
39
|
+
format_result(e.message)
|
40
|
+
end
|
41
|
+
|
42
|
+
def log_console_entry(message)
|
43
|
+
return unless @debug_mode
|
44
|
+
|
45
|
+
@execution[:history] << {
|
46
|
+
timestamp: Time.current.to_i,
|
47
|
+
message: message
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def log_new_step(target_unit = @step_unit)
|
52
|
+
return unless @debug_mode
|
53
|
+
|
54
|
+
@execution[:steps] << {
|
55
|
+
oid: target_unit.oid,
|
56
|
+
in: {
|
57
|
+
timestamp: Time.current.to_i,
|
58
|
+
carry_over: target_unit.current_carry_over,
|
59
|
+
storage: target_unit.current_storage
|
60
|
+
}
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def log_feeder_step(result, feeder)
|
65
|
+
return unless @debug_mode
|
66
|
+
|
67
|
+
{
|
68
|
+
oid: feeder.oid,
|
69
|
+
in: {
|
70
|
+
timestamp: Time.current.to_i,
|
71
|
+
carry_over: feeder.current_carry_over,
|
72
|
+
storage: feeder.current_storage
|
73
|
+
},
|
74
|
+
out: {
|
75
|
+
timestamp: Time.current.to_i,
|
76
|
+
result: result,
|
77
|
+
target_unit: @step_unit&.oid
|
78
|
+
}
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
def log_step_exception(exception)
|
83
|
+
return unless @debug_mode
|
84
|
+
|
85
|
+
@execution[:steps].last[:exception] = {
|
86
|
+
timestamp: Time.current.to_i,
|
87
|
+
message: exception.message,
|
88
|
+
backtrace: exception.backtrace
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def log_step_result(result, next_unit)
|
93
|
+
return unless @debug_mode
|
94
|
+
|
95
|
+
@execution[:steps].last[:out] = {
|
96
|
+
timestamp: Time.current.to_i,
|
97
|
+
result: result,
|
98
|
+
target_unit: next_unit&.oid
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def flow_through
|
103
|
+
while @step_unit.exits.size.nonzero?
|
104
|
+
feeder_steps = process_feeders
|
105
|
+
result = begin
|
106
|
+
log_console_entry("Flowing #{@step_unit.oid}")
|
107
|
+
result = @step_unit.flow
|
108
|
+
@storage.merge! @step_unit.current_storage
|
109
|
+
result
|
110
|
+
rescue => e
|
111
|
+
exception = Caixanegra::UnitScopedException.new(e)
|
112
|
+
exception.set_backtrace(e.backtrace)
|
113
|
+
raise exception
|
114
|
+
end
|
115
|
+
next_unit = next_unit(result)
|
116
|
+
log_step_result(result, next_unit)
|
117
|
+
@execution[:steps] += feeder_steps
|
118
|
+
@step_unit = next_unit
|
119
|
+
log_new_step
|
120
|
+
end
|
121
|
+
|
122
|
+
log_console_entry("Reached terminator #{@step_unit.oid}")
|
123
|
+
|
124
|
+
@step_unit.flow[:carry_over]
|
125
|
+
end
|
126
|
+
|
127
|
+
def map_carry_over(result, target_step: @step_unit)
|
128
|
+
mapped_carry_over = {}
|
129
|
+
exit_name = result[:exit_through]
|
130
|
+
carry_over = result[:carry_over].deep_symbolize_keys
|
131
|
+
metadata = unit_metadata(target_step.oid)
|
132
|
+
mapped_carry_over.merge!(carry_over) if metadata[:type] == 'passthrough'
|
133
|
+
exit_metadata = metadata[:exits].find { |ex| ex[:name] == exit_name.to_s }
|
134
|
+
(exit_metadata[:mappings] || []).each do |mapping|
|
135
|
+
next if mapping[:use].blank? || mapping[:as].blank?
|
136
|
+
|
137
|
+
mapped_carry_over[mapping[:as].to_sym] = carry_over.dig(
|
138
|
+
*(mapping[:use] || '').split('.').map(&:to_sym)
|
139
|
+
)
|
140
|
+
end
|
141
|
+
|
142
|
+
mapped_carry_over
|
143
|
+
end
|
144
|
+
|
145
|
+
def next_unit(result)
|
146
|
+
exit_name = result[:exit_through]
|
147
|
+
metadata = unit_metadata(@step_unit.oid)
|
148
|
+
log_console_entry "Next unit found through '#{exit_name}': '#{@step_unit.oid}'"
|
149
|
+
exit_metadata = metadata[:exits].find { |ex| ex[:name] == exit_name.to_s }
|
150
|
+
unit(exit_metadata[:target], map_carry_over(result))
|
151
|
+
end
|
152
|
+
|
153
|
+
def process_feeders
|
154
|
+
feeder_hits = []
|
155
|
+
carry_over = {}
|
156
|
+
@flow[:units].filter do |unit|
|
157
|
+
unit[:type] == 'feeder' && (unit[:exits] || []).any? { |ex| ex[:target] == @step_unit.oid }
|
158
|
+
end.each do |feeder|
|
159
|
+
feeder_instance = unit_instance(feeder)
|
160
|
+
log_console_entry "Flowing feeder '#{feeder_instance.oid}'"
|
161
|
+
result = feeder_instance.flow
|
162
|
+
carry_over.merge! map_carry_over(result, target_step: feeder_instance)
|
163
|
+
feeder_hits << log_feeder_step(result, feeder_instance)
|
164
|
+
end
|
165
|
+
@step_unit.carry_over(carry_over)
|
166
|
+
|
167
|
+
feeder_hits
|
168
|
+
end
|
169
|
+
|
170
|
+
def unit_metadata(oid)
|
171
|
+
@flow[:units].find { |u| u[:oid] == oid }
|
172
|
+
end
|
173
|
+
|
174
|
+
def set_start_unit
|
175
|
+
@step_unit = unit(@flow[:entrypoint])
|
176
|
+
log_console_entry "Start unit set: #{@step_unit&.oid || 'none'}"
|
177
|
+
@step_unit.carry_over(@initial_carryover)
|
178
|
+
log_new_step
|
179
|
+
end
|
180
|
+
|
181
|
+
def unit(oid, carry_over = {})
|
182
|
+
unit_instance(unit_metadata(oid), carry_over)
|
183
|
+
end
|
184
|
+
|
185
|
+
def unit_instance(unit_data, carry_over = {})
|
186
|
+
mappings = unit_data[:mappings] || {}
|
187
|
+
unit_class = scoped_units[unit_data[:class].to_sym]
|
188
|
+
inputs = unit_class.inputs
|
189
|
+
unit_class.new(unit_data[:oid], inputs, mappings, carry_over, @storage)
|
190
|
+
end
|
191
|
+
|
192
|
+
def scoped_units
|
193
|
+
@scoped_units ||= begin
|
194
|
+
base_units = Caixanegra.units.reject { |_, v| v.is_a? Hash }
|
195
|
+
scope_units = Caixanegra.units[@unit_scope] if @unit_scope.present?
|
196
|
+
|
197
|
+
base_units.merge(scope_units || {})
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Caixanegra
|
4
|
+
class Manager
|
5
|
+
class << self
|
6
|
+
def handler(flow_definition = {})
|
7
|
+
uid = SecureRandom.uuid.gsub("-", "")
|
8
|
+
|
9
|
+
Caixanegra.redis.multi do |pipeline|
|
10
|
+
pipeline.hset(:caixanegra, uid, JSON.dump(flow_definition))
|
11
|
+
pipeline.expire(:caixanegra, 1.hour)
|
12
|
+
end
|
13
|
+
|
14
|
+
uid
|
15
|
+
end
|
16
|
+
|
17
|
+
def destroy
|
18
|
+
Caixanegra.redis.hdel(:caixanegra, key)
|
19
|
+
end
|
20
|
+
|
21
|
+
def get(uid)
|
22
|
+
value = Caixanegra.redis.hget(:caixanegra, uid)
|
23
|
+
|
24
|
+
JSON.parse(value) if value.present?
|
25
|
+
end
|
26
|
+
|
27
|
+
def set(uid, flow_definition)
|
28
|
+
Caixanegra.redis.hset(:caixanegra, uid, JSON.dump(flow_definition))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|