bpmn 0.0.1
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 +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
|