spot_flow 0.0.1
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +187 -0
- data/Rakefile +11 -0
- data/lib/spot_flow/bpmn/definitions.rb +55 -0
- data/lib/spot_flow/bpmn/element.rb +44 -0
- data/lib/spot_flow/bpmn/event.rb +195 -0
- data/lib/spot_flow/bpmn/event_definition.rb +135 -0
- data/lib/spot_flow/bpmn/extension_elements.rb +29 -0
- data/lib/spot_flow/bpmn/extensions.rb +77 -0
- data/lib/spot_flow/bpmn/flow.rb +47 -0
- data/lib/spot_flow/bpmn/gateway.rb +85 -0
- data/lib/spot_flow/bpmn/process.rb +179 -0
- data/lib/spot_flow/bpmn/step.rb +58 -0
- data/lib/spot_flow/bpmn/task.rb +128 -0
- data/lib/spot_flow/bpmn.rb +18 -0
- data/lib/spot_flow/context.rb +108 -0
- data/lib/spot_flow/execution.rb +360 -0
- data/lib/spot_flow/version.rb +5 -0
- data/lib/spot_flow.rb +49 -0
- metadata +233 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpotFlow
|
4
|
+
module Bpmn
|
5
|
+
class Extension
|
6
|
+
include ActiveModel::Model
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Zeebe
|
11
|
+
class AssignmentDefinition < Bpmn::Extension
|
12
|
+
attr_accessor :assignee, :candidate_groups, :candidate_users
|
13
|
+
end
|
14
|
+
|
15
|
+
class CalledElement < Bpmn::Extension
|
16
|
+
attr_accessor :process_id, :propagate_all_child_variables, :propagate_all_parent_variables
|
17
|
+
|
18
|
+
def initialize(attributes = {})
|
19
|
+
super(attributes.except(:propagate_all_child_variables))
|
20
|
+
|
21
|
+
@propagate_all_parent_variables = true
|
22
|
+
@propagate_all_parent_variables = attributes[:propagate_all_parent_variables] == "true" if attributes[:propagate_all_parent_variables].present?
|
23
|
+
@propagate_all_child_variables = attributes[:propagate_all_child_variables] == "true"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class CalledDecision < Bpmn::Extension
|
28
|
+
attr_accessor :decision_id, :result_variable
|
29
|
+
end
|
30
|
+
|
31
|
+
class FormDefinition < Bpmn::Extension
|
32
|
+
attr_accessor :form_key
|
33
|
+
end
|
34
|
+
|
35
|
+
class IoMapping < Bpmn::Extension
|
36
|
+
attr_reader :inputs, :outputs
|
37
|
+
|
38
|
+
def initialize(attributes = {})
|
39
|
+
super(attributes.except(:input, :output))
|
40
|
+
|
41
|
+
@inputs = Array.wrap(attributes[:input]).map { |atts| Parameter.new(atts) } if attributes[:input].present?
|
42
|
+
@outputs = Array.wrap(attributes[:output]).map { |atts| Parameter.new(atts) } if attributes[:output].present?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Parameter < Bpmn::Extension
|
47
|
+
attr_accessor :source, :target
|
48
|
+
end
|
49
|
+
|
50
|
+
class Script < Bpmn::Extension
|
51
|
+
attr_accessor :expression, :result_variable
|
52
|
+
end
|
53
|
+
|
54
|
+
class Subscription < Bpmn::Extension
|
55
|
+
attr_accessor :correlation_key
|
56
|
+
end
|
57
|
+
|
58
|
+
class TaskDefinition < Bpmn::Extension
|
59
|
+
attr_accessor :type, :retries
|
60
|
+
end
|
61
|
+
|
62
|
+
class TaskHeaders < Bpmn::Extension
|
63
|
+
attr_accessor :headers
|
64
|
+
|
65
|
+
def initialize(attributes = {})
|
66
|
+
super(attributes.except(:header))
|
67
|
+
|
68
|
+
@headers = HashWithIndifferentAccess.new
|
69
|
+
Array.wrap(attributes[:header]).each { |header| @headers[header[:key]] = header[:value] }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class TaskSchedule < Bpmn::Extension
|
74
|
+
attr_accessor :due_date, :follow_up_date
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpotFlow
|
4
|
+
module Bpmn
|
5
|
+
class Flow < Element
|
6
|
+
attr_accessor :source_ref, :target_ref
|
7
|
+
attr_accessor :source, :target
|
8
|
+
|
9
|
+
def initialize(attributes = {})
|
10
|
+
super(attributes.except(:source_ref, :target_ref))
|
11
|
+
|
12
|
+
@source_ref = attributes[:source_ref]
|
13
|
+
@target_ref = attributes[:target_ref]
|
14
|
+
@source = nil
|
15
|
+
@target = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def inspect
|
19
|
+
parts = ["#<#{self.class.name.gsub(/SpotFlow::/, "")} @id=#{id.inspect}"]
|
20
|
+
parts << "@source_ref=#{source_ref.inspect}" if source_ref
|
21
|
+
parts << "@target_ref=#{target_ref.inspect}" if target_ref
|
22
|
+
parts.join(" ") + ">"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Association < Flow
|
27
|
+
end
|
28
|
+
|
29
|
+
class SequenceFlow < Flow
|
30
|
+
attr_accessor :condition
|
31
|
+
|
32
|
+
def initialize(attributes = {})
|
33
|
+
super(attributes.except(:condition))
|
34
|
+
|
35
|
+
@condition = attributes[:condition_expression]
|
36
|
+
end
|
37
|
+
|
38
|
+
def evaluate(execution)
|
39
|
+
return true unless condition
|
40
|
+
execution.evaluate_condition(condition)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class TextAnnotation < Flow
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpotFlow
|
4
|
+
module Bpmn
|
5
|
+
class Gateway < Step
|
6
|
+
|
7
|
+
def execute(execution)
|
8
|
+
if converging?
|
9
|
+
if is_enabled?(execution)
|
10
|
+
return leave(execution)
|
11
|
+
else
|
12
|
+
execution.wait
|
13
|
+
end
|
14
|
+
else
|
15
|
+
return leave(execution)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Algorithm from https://researcher.watson.ibm.com/researcher/files/zurich-hvo/bpm2010-1.pdf
|
21
|
+
#
|
22
|
+
def is_enabled?(execution)
|
23
|
+
filled = []
|
24
|
+
empty = []
|
25
|
+
|
26
|
+
incoming.each { |flow| execution.tokens_in.include?(flow.id) ? filled.push(flow) : empty.push(flow) }
|
27
|
+
|
28
|
+
# Filled slots don't need to be searched for tokens
|
29
|
+
index = 0
|
30
|
+
while (index < filled.length)
|
31
|
+
current_flow = filled[index]
|
32
|
+
current_flow.source.incoming.each do |incoming_flow|
|
33
|
+
filled.push(incoming_flow) unless filled.include?(incoming_flow) || incoming_flow.target == self
|
34
|
+
end
|
35
|
+
index = index + 1
|
36
|
+
end
|
37
|
+
|
38
|
+
# Empty slots need to be searched for tokens
|
39
|
+
index = 0
|
40
|
+
while (index < empty.length)
|
41
|
+
current_flow = empty[index]
|
42
|
+
current_flow.source.incoming.each do |incoming_flow|
|
43
|
+
empty.push(incoming_flow) unless filled.include?(incoming_flow) || empty.include?(incoming_flow) || incoming_flow.target == self
|
44
|
+
end
|
45
|
+
index = index + 1
|
46
|
+
end
|
47
|
+
|
48
|
+
empty_ids = empty.map { |g| g.id }
|
49
|
+
|
50
|
+
# If there are empty slots with tokens we need to wait
|
51
|
+
return false if (empty_ids & execution.parent.tokens).length > 0
|
52
|
+
return true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class ExclusiveGateway < Gateway
|
57
|
+
# RULE: Only one flow is taken
|
58
|
+
def outgoing_flows(step_execution)
|
59
|
+
flows = super
|
60
|
+
return [flows.first]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class ParallelGateway < Gateway
|
65
|
+
# RULE: All flows are taken
|
66
|
+
end
|
67
|
+
|
68
|
+
class InclusiveGateway < Gateway
|
69
|
+
# RULE: All valid flows are taken
|
70
|
+
# NOTE: Camunda 8 only support diverging but not converging inclusive gateways
|
71
|
+
end
|
72
|
+
|
73
|
+
class EventBasedGateway < Gateway
|
74
|
+
#
|
75
|
+
# RULE: when an event created from an event gateway is caught,
|
76
|
+
# all other waiting events must be canceled.
|
77
|
+
#
|
78
|
+
def cancel_waiting_events(execution)
|
79
|
+
execution.targets.each do |target_execution|
|
80
|
+
target_execution.terminate unless target_execution.ended?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpotFlow
|
4
|
+
module Bpmn
|
5
|
+
class Process < Step
|
6
|
+
attr_accessor :is_executable
|
7
|
+
|
8
|
+
attr_accessor :start_events, :end_events, :intermediate_catch_events, :intermediate_throw_events, :boundary_events
|
9
|
+
attr_accessor :tasks, :user_tasks, :service_tasks, :script_tasks, :send_tasks, :receive_tasks, :manual_tasks, :business_rule_tasks, :call_activities
|
10
|
+
attr_accessor :sub_processes, :ad_hoc_sub_processes
|
11
|
+
attr_accessor :sequence_flows, :message_flows, :associations, :data_associations, :data_inputs, :data_outputs
|
12
|
+
attr_accessor :data_objects, :data_stores, :data_stores_references
|
13
|
+
attr_accessor :gateways, :exclusive_gateways, :parallel_gateways, :inclusive_gateways, :event_based_gateways, :complex_gateways
|
14
|
+
|
15
|
+
attr_accessor :parent
|
16
|
+
|
17
|
+
def initialize(attributes = {})
|
18
|
+
super(attributes.slice(:id, :name, :extension_elements, :incoming, :outgoing, :default))
|
19
|
+
|
20
|
+
@is_executable = attributes[:is_executable] == ("true" || true)
|
21
|
+
|
22
|
+
@start_events = Array.wrap(attributes[:start_event]).map { |se| StartEvent.new(se) }
|
23
|
+
@end_events = Array.wrap(attributes[:end_event]).map { |ee| EndEvent.new(ee) }
|
24
|
+
@intermediate_catch_events = Array.wrap(attributes[:intermediate_catch_event]).map { |ice| IntermediateCatchEvent.new(ice) }
|
25
|
+
@intermediate_throw_events = Array.wrap(attributes[:intermediate_throw_event]).map { |ite| IntermediateThrowEvent.new(ite) }
|
26
|
+
@boundary_events = Array.wrap(attributes[:boundary_event]).map { |be| BoundaryEvent.new(be) }
|
27
|
+
@tasks = Array.wrap(attributes[:task]).map { |t| Task.new(t) }
|
28
|
+
@user_tasks = Array.wrap(attributes[:user_task]).map { |ut| UserTask.new(ut) }
|
29
|
+
@service_tasks = Array.wrap(attributes[:service_task]).map { |st| ServiceTask.new(st) }
|
30
|
+
@script_tasks = Array.wrap(attributes[:script_task]).map { |st| ScriptTask.new(st) }
|
31
|
+
@business_rule_tasks = Array.wrap(attributes[:business_rule_task]).map { |brt| BusinessRuleTask.new(brt) }
|
32
|
+
@call_activities = Array.wrap(attributes[:call_activity]).map { |ca| CallActivity.new(ca) }
|
33
|
+
@sub_processes = Array.wrap(attributes[:sub_process]).map { |sp| SubProcess.new(sp) }
|
34
|
+
@ad_hoc_sub_processes = Array.wrap(attributes[:ad_hoc_sub_processe]).map { |ahsp| AdHocSubProcess.new(ahsp) }
|
35
|
+
@exclusive_gateways = Array.wrap(attributes[:exclusive_gateway]).map { |eg| ExclusiveGateway.new(eg) }
|
36
|
+
@parallel_gateways = Array.wrap(attributes[:parallel_gateway]).map { |pg| ParallelGateway.new(pg) }
|
37
|
+
@inclusive_gateways = Array.wrap(attributes[:inclusive_gateway]).map { |ig| InclusiveGateway.new(ig) }
|
38
|
+
@event_based_gateways = Array.wrap(attributes[:event_based_gateway]).map { |ebg| EventBasedGateway.new(ebg) }
|
39
|
+
@sequence_flows = Array.wrap(attributes[:sequence_flow]).map { |sf| SequenceFlow.new(sf) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def elements
|
43
|
+
@elements ||= {}.tap do |elements|
|
44
|
+
elements.merge!(start_events.index_by(&:id))
|
45
|
+
elements.merge!(end_events.index_by(&:id))
|
46
|
+
elements.merge!(intermediate_catch_events.index_by(&:id))
|
47
|
+
elements.merge!(intermediate_throw_events.index_by(&:id))
|
48
|
+
elements.merge!(boundary_events.index_by(&:id))
|
49
|
+
elements.merge!(tasks.index_by(&:id))
|
50
|
+
elements.merge!(user_tasks.index_by(&:id))
|
51
|
+
elements.merge!(service_tasks.index_by(&:id))
|
52
|
+
elements.merge!(script_tasks.index_by(&:id))
|
53
|
+
elements.merge!(business_rule_tasks.index_by(&:id))
|
54
|
+
elements.merge!(call_activities.index_by(&:id))
|
55
|
+
elements.merge!(sub_processes.index_by(&:id))
|
56
|
+
elements.merge!(ad_hoc_sub_processes.index_by(&:id))
|
57
|
+
elements.merge!(exclusive_gateways.index_by(&:id))
|
58
|
+
elements.merge!(parallel_gateways.index_by(&:id))
|
59
|
+
elements.merge!(inclusive_gateways.index_by(&:id))
|
60
|
+
elements.merge!(event_based_gateways.index_by(&:id))
|
61
|
+
elements.merge!(sequence_flows.index_by(&:id))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def wire_references(definitions)
|
66
|
+
elements.values.each do |element|
|
67
|
+
if element.is_a?(Step)
|
68
|
+
element.incoming = element.incoming.map { |id| element_by_id(id) }
|
69
|
+
element.outgoing = element.outgoing.map { |id| element_by_id(id) }
|
70
|
+
|
71
|
+
if element.is_a?(Event)
|
72
|
+
|
73
|
+
element.event_definitions.each do |event_definition|
|
74
|
+
if event_definition.is_a?(MessageEventDefinition)
|
75
|
+
event_definition.message = definitions.message_by_id(event_definition.message_ref)
|
76
|
+
elsif event_definition.is_a?(SignalEventDefinition)
|
77
|
+
event_definition.signal = definitions.signal_by_id(event_definition.signal_ref)
|
78
|
+
elsif event_definition.is_a?(ErrorEventDefinition)
|
79
|
+
event_definition.error = definitions.error_by_id(event_definition.error_ref)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
if element.is_a?(BoundaryEvent)
|
84
|
+
host_element = element_by_id(element.attached_to_ref)
|
85
|
+
host_element.attachments << element
|
86
|
+
element.attached_to = host_element
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
if element.is_a?(Gateway)
|
92
|
+
element.default = element_by_id(element.default_ref)
|
93
|
+
end
|
94
|
+
|
95
|
+
if element.is_a?(SubProcess)
|
96
|
+
element.wire_references(definitions)
|
97
|
+
end
|
98
|
+
|
99
|
+
elsif element.is_a?(SequenceFlow)
|
100
|
+
element.source = element_by_id(element.source_ref)
|
101
|
+
element.target = element_by_id(element.target_ref)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Not handled participant process ref
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def element_by_id(id)
|
109
|
+
elements[id]
|
110
|
+
end
|
111
|
+
|
112
|
+
def elements_by_type(type)
|
113
|
+
elements.select { |e| e.class == type }
|
114
|
+
end
|
115
|
+
|
116
|
+
def default_start_event
|
117
|
+
start_events.find { |se| se.event_definitions.empty? }
|
118
|
+
end
|
119
|
+
|
120
|
+
def execute(execution)
|
121
|
+
start_event = execution.start_event_id ? element_by_id(execution.start_event_id) : default_start_event
|
122
|
+
raise ExecutionErrorNew.new("Process must have at least one start event.") if start_event.blank?
|
123
|
+
execution.execute_step(start_event)
|
124
|
+
end
|
125
|
+
|
126
|
+
def inspect
|
127
|
+
parts = ["#<#{self.class.name.gsub(/SpotFlow::/, "")} @id=#{id.inspect} @name=#{name.inspect} @is_executable=#{is_executable.inspect}"]
|
128
|
+
parts << "@parent=#{parent.inspect}" if parent
|
129
|
+
event_attrs = (start_events + intermediate_catch_events + intermediate_throw_events + boundary_events + end_events).compact
|
130
|
+
parts << "@events=#{event_attrs.inspect}" unless event_attrs.blank?
|
131
|
+
activity_attrs = (tasks + user_tasks + service_tasks + script_tasks + business_rule_tasks + call_activities).compact
|
132
|
+
parts << "@activities=#{activity_attrs.inspect}" unless activity_attrs.blank?
|
133
|
+
gateway_attrs = (exclusive_gateways + parallel_gateways + inclusive_gateways + event_based_gateways).compact
|
134
|
+
parts << "@gateways=#{gateway_attrs.inspect}" unless gateway_attrs.blank?
|
135
|
+
sub_process_attrs = (sub_processes + ad_hoc_sub_processes).compact
|
136
|
+
parts << "@sub_processes=#{sub_process_attrs.inspect}" unless sub_process_attrs.blank?
|
137
|
+
parts << "@sequence_flows=#{sequence_flows.inspect}" unless sequence_flows.blank?
|
138
|
+
parts.join(" ") + ">"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class SubProcess < Process
|
143
|
+
attr_accessor :triggered_by_event
|
144
|
+
|
145
|
+
def initialize(attributes = {})
|
146
|
+
super(attributes.except(:triggered_by_event))
|
147
|
+
|
148
|
+
@is_executable = false
|
149
|
+
@sub_processes = []
|
150
|
+
@triggered_by_event = attributes[:triggered_by_event]
|
151
|
+
end
|
152
|
+
|
153
|
+
def execution_ended(execution)
|
154
|
+
leave(execution)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class AdHocSubProcess < SubProcess
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
class CallActivity < Activity
|
163
|
+
attr_reader :process_id
|
164
|
+
|
165
|
+
def execute(execution)
|
166
|
+
if extension_elements&.called_element&.process_id&.start_with?("=")
|
167
|
+
@process_id = SpotFeel.evaluate(extension_elements&.called_element&.process_id, variables: execution.variables)
|
168
|
+
else
|
169
|
+
@process_id = extension_elements&.called_element&.process_id
|
170
|
+
end
|
171
|
+
|
172
|
+
execution.wait
|
173
|
+
|
174
|
+
process = execution.context.process_by_id(@process_id) if @process_id
|
175
|
+
execution.execute_step(process.default_start_event) if process
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpotFlow
|
4
|
+
module Bpmn
|
5
|
+
class Step < Element
|
6
|
+
attr_accessor :incoming, :outgoing, :default, :default_ref
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
super(attributes.except(:incoming, :outgoing, :default))
|
10
|
+
|
11
|
+
@incoming = Array.wrap(attributes[:incoming]) || []
|
12
|
+
@outgoing = Array.wrap(attributes[:outgoing]) || []
|
13
|
+
@default_ref = attributes[:default]
|
14
|
+
end
|
15
|
+
|
16
|
+
def diverging?
|
17
|
+
outgoing.length > 1
|
18
|
+
end
|
19
|
+
|
20
|
+
def converging?
|
21
|
+
incoming.length > 1
|
22
|
+
end
|
23
|
+
|
24
|
+
def leave(execution)
|
25
|
+
execution.end(false)
|
26
|
+
execution.take_all(outgoing_flows(execution))
|
27
|
+
end
|
28
|
+
|
29
|
+
def outgoing_flows(execution)
|
30
|
+
flows = []
|
31
|
+
outgoing.each do |flow|
|
32
|
+
result = flow.evaluate(execution) unless default&.id == flow.id
|
33
|
+
flows.push flow if result
|
34
|
+
end
|
35
|
+
flows = [default] if flows.empty? && default
|
36
|
+
return flows
|
37
|
+
end
|
38
|
+
|
39
|
+
def input_mappings
|
40
|
+
extension_elements&.io_mapping&.inputs || []
|
41
|
+
end
|
42
|
+
|
43
|
+
def output_mappings
|
44
|
+
extension_elements&.io_mapping&.outputs || []
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Activity < Step
|
49
|
+
attr_accessor :attachments
|
50
|
+
|
51
|
+
def initialize(attributes = {})
|
52
|
+
super(attributes.except(:attachments))
|
53
|
+
|
54
|
+
@attachments = []
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpotFlow
|
4
|
+
module Bpmn
|
5
|
+
class Task < Activity
|
6
|
+
|
7
|
+
def is_automated?
|
8
|
+
false
|
9
|
+
end
|
10
|
+
|
11
|
+
def is_manual?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute(execution)
|
16
|
+
execution.wait
|
17
|
+
end
|
18
|
+
|
19
|
+
def signal(execution)
|
20
|
+
leave(execution)
|
21
|
+
end
|
22
|
+
|
23
|
+
def result_to_variables(result)
|
24
|
+
if result_variable
|
25
|
+
return { "#{result_variable}": result }
|
26
|
+
else
|
27
|
+
if result.is_a? Hash
|
28
|
+
result
|
29
|
+
else
|
30
|
+
{}.tap { |h| h[id.underscore] = result }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class UserTask < Task
|
37
|
+
|
38
|
+
def form_key
|
39
|
+
extension_elements&.form_definition&.form_key
|
40
|
+
end
|
41
|
+
|
42
|
+
def assignee
|
43
|
+
extension_elements&.assignment_definition&.assignee
|
44
|
+
end
|
45
|
+
|
46
|
+
def candidate_groups
|
47
|
+
extension_elements&.assignment_definition&.candidate_groups
|
48
|
+
end
|
49
|
+
|
50
|
+
def candidate_users
|
51
|
+
extension_elements&.assignment_definition&.candidate_users
|
52
|
+
end
|
53
|
+
|
54
|
+
def due_date
|
55
|
+
extension_elements&.task_schedule&.due_date
|
56
|
+
end
|
57
|
+
|
58
|
+
def follow_up_date
|
59
|
+
extension_elements&.task_schedule&.follow_up_date
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class ServiceTask < Task
|
64
|
+
attr_accessor :service
|
65
|
+
|
66
|
+
def is_automated?
|
67
|
+
true
|
68
|
+
end
|
69
|
+
|
70
|
+
def is_manual?
|
71
|
+
false
|
72
|
+
end
|
73
|
+
|
74
|
+
def execute(execution)
|
75
|
+
execution.wait
|
76
|
+
end
|
77
|
+
|
78
|
+
def task_type
|
79
|
+
extension_elements&.task_definition&.type
|
80
|
+
end
|
81
|
+
|
82
|
+
def task_retries
|
83
|
+
extension_elements&.task_definition&.retries
|
84
|
+
end
|
85
|
+
|
86
|
+
def headers
|
87
|
+
extension_elements&.task_headers&.headers
|
88
|
+
end
|
89
|
+
|
90
|
+
def run(execution)
|
91
|
+
if defined?(task_type)
|
92
|
+
klass = task_type.constantize
|
93
|
+
klass.new.call(execution.parent.variables, headers || {})
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class ScriptTask < ServiceTask
|
99
|
+
|
100
|
+
def script
|
101
|
+
extension_elements&.script&.expression
|
102
|
+
end
|
103
|
+
|
104
|
+
def result_variable
|
105
|
+
extension_elements&.script&.result_variable
|
106
|
+
end
|
107
|
+
|
108
|
+
def run(execution)
|
109
|
+
SpotFeel.evaluate(script.delete_prefix("="), variables: execution.parent.variables)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class BusinessRuleTask < ServiceTask
|
114
|
+
|
115
|
+
def decision_id
|
116
|
+
extension_elements&.called_decision&.decision_id
|
117
|
+
end
|
118
|
+
|
119
|
+
def result_variable
|
120
|
+
extension_elements&.called_decision&.result_variable
|
121
|
+
end
|
122
|
+
|
123
|
+
def run(execution)
|
124
|
+
SpotFeel.decide(decision_id, definitions: execution.context.dmn_definitions_by_decision_id(decision_id), variables: execution.parent.variables)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "bpmn/element"
|
4
|
+
require_relative "bpmn/extensions"
|
5
|
+
require_relative "bpmn/extension_elements"
|
6
|
+
require_relative "bpmn/step"
|
7
|
+
require_relative "bpmn/flow"
|
8
|
+
require_relative "bpmn/event_definition"
|
9
|
+
require_relative "bpmn/event"
|
10
|
+
require_relative "bpmn/gateway"
|
11
|
+
require_relative "bpmn/task"
|
12
|
+
require_relative "bpmn/process"
|
13
|
+
require_relative "bpmn/definitions"
|
14
|
+
|
15
|
+
module SpotFlow
|
16
|
+
module Bpmn
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpotFlow
|
4
|
+
class Context
|
5
|
+
attr_reader :sources, :processes, :decisions, :services, :executions
|
6
|
+
attr_reader :bpmn_definitions, :dmn_definitions
|
7
|
+
|
8
|
+
def initialize(sources = [], processes:[], decisions:[], services: {})
|
9
|
+
@sources = Array.wrap(sources)
|
10
|
+
@processes = Array.wrap(processes)
|
11
|
+
@bpmn_definitions = []
|
12
|
+
@dmn_definitions = []
|
13
|
+
@decisions = Array.wrap(decisions)
|
14
|
+
@services = HashWithIndifferentAccess.new((SpotFlow.config.services || {}).merge(services))
|
15
|
+
|
16
|
+
@sources.each do |source|
|
17
|
+
if source.include?("http://www.omg.org/spec/DMN/20180521/DC/")
|
18
|
+
definitions = SpotFeel.definitions_from_xml(source)
|
19
|
+
@dmn_definitions << definitions
|
20
|
+
@decisions += definitions.decisions
|
21
|
+
else
|
22
|
+
@processes += SpotFlow.processes_from_xml(source)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
@executions = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def start(process_id: nil, start_event_id: nil, variables: {})
|
30
|
+
process = process_id ? process_by_id(process_id) : default_process
|
31
|
+
raise ExecutionError.new(process_id ? "Process #{process_id} not found." : "No default process found.") if process.blank?
|
32
|
+
execution = Execution.start(context: self, process: process, start_event_id: start_event_id, variables: variables)
|
33
|
+
executions << execution
|
34
|
+
execution
|
35
|
+
end
|
36
|
+
|
37
|
+
def start_with_message(message_name:, variables: {})
|
38
|
+
[].tap do |executions|
|
39
|
+
processes.map do |process|
|
40
|
+
process.start_events.map do |start_event|
|
41
|
+
start_event.message_event_definitions.map do |message_event_definition|
|
42
|
+
if message_name == message_event_definition.message_name
|
43
|
+
Execution.start(context: self, process: process, variables: variables, start_event_id: start_event.id).tap { |execution| executions.push execution }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def restore(execution_state)
|
52
|
+
Execution.deserialize(execution_state, context: self).tap do |execution|
|
53
|
+
executions << execution
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def notify_listener(*args)
|
58
|
+
SpotFlow.config.listener&.call(args)
|
59
|
+
end
|
60
|
+
|
61
|
+
def default_process
|
62
|
+
raise "Multiple processes defined, must identify process" if processes.size > 1
|
63
|
+
processes.first
|
64
|
+
end
|
65
|
+
|
66
|
+
def process_by_id(id)
|
67
|
+
processes.each do |process|
|
68
|
+
return process if process.id == id
|
69
|
+
process.sub_processes.each do |sub_process|
|
70
|
+
return sub_process if sub_process.id == id
|
71
|
+
end
|
72
|
+
end
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def element_by_id(id)
|
77
|
+
processes.each do |process|
|
78
|
+
element = process.element_by_id(id)
|
79
|
+
return element if element
|
80
|
+
end
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
|
84
|
+
def execution_by_id(id)
|
85
|
+
executions.find { |e| e.id == id }
|
86
|
+
end
|
87
|
+
|
88
|
+
def execution_by_step_id(step_id)
|
89
|
+
executions.find { |e| e.step.id == step_id }
|
90
|
+
end
|
91
|
+
|
92
|
+
def decision_by_id(id)
|
93
|
+
decisions.find { |d| d.id == id }
|
94
|
+
end
|
95
|
+
|
96
|
+
def dmn_definitions_by_decision_id(decision_id)
|
97
|
+
dmn_definitions.find { |definitions| definitions.decisions.find { |decision| decision.id == decision_id } }
|
98
|
+
end
|
99
|
+
|
100
|
+
def inspect
|
101
|
+
parts = ["#<Context"]
|
102
|
+
parts << "@processes=#{processes.inspect}" if processes.present?
|
103
|
+
parts << "@decisions=#{decisions.inspect}" if decisions.present?
|
104
|
+
parts << "@executions=#{executions.inspect}" if executions.present?
|
105
|
+
parts.join(" ") + ">"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|