Class: SpotFlow::Execution

Inherits:
Object
  • Object
show all
Defined in:
lib/spot_flow/execution.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = {}) ⇒ Execution

Returns a new instance of Execution.



42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/spot_flow/execution.rb', line 42

def initialize(attributes={})
  attributes.each do |k, v|
    send("#{k}=", v)
  end
  @id ||= gen_uid
  @status ||= "activated"
  @variables = @variables&.with_indifferent_access || {}.with_indifferent_access
  @tokens_in ||= []
  @tokens_out ||= []
  @message_names ||= []
  @error_names ||= []
  @children ||= []
end

Instance Attribute Details

#attached_to_idObject

Returns the value of attribute attached_to_id.



6
7
8
# File 'lib/spot_flow/execution.rb', line 6

def attached_to_id
  @attached_to_id
end

#childrenObject

Returns the value of attribute children.



6
7
8
# File 'lib/spot_flow/execution.rb', line 6

def children
  @children
end

#conditionObject

Returns the value of attribute condition.



5
6
7
# File 'lib/spot_flow/execution.rb', line 5

def condition
  @condition
end

#contextObject

Returns the value of attribute context.



6
7
8
# File 'lib/spot_flow/execution.rb', line 6

def context
  @context
end

#ended_atObject

Returns the value of attribute ended_at.



5
6
7
# File 'lib/spot_flow/execution.rb', line 5

def ended_at
  @ended_at
end

#error_namesObject

Returns the value of attribute error_names.



5
6
7
# File 'lib/spot_flow/execution.rb', line 5

def error_names
  @error_names
end

#idObject

Returns the value of attribute id.



5
6
7
# File 'lib/spot_flow/execution.rb', line 5

def id
  @id
end

#message_namesObject

Returns the value of attribute message_names.



5
6
7
# File 'lib/spot_flow/execution.rb', line 5

def message_names
  @message_names
end

#parentObject

Returns the value of attribute parent.



6
7
8
# File 'lib/spot_flow/execution.rb', line 6

def parent
  @parent
end

#start_event_idObject

Returns the value of attribute start_event_id.



5
6
7
# File 'lib/spot_flow/execution.rb', line 5

def start_event_id
  @start_event_id
end

#started_atObject

Returns the value of attribute started_at.



5
6
7
# File 'lib/spot_flow/execution.rb', line 5

def started_at
  @started_at
end

#statusObject

Returns the value of attribute status.



5
6
7
# File 'lib/spot_flow/execution.rb', line 5

def status
  @status
end

#stepObject

Returns the value of attribute step.



6
7
8
# File 'lib/spot_flow/execution.rb', line 6

def step
  @step
end

#timer_expires_atObject

Returns the value of attribute timer_expires_at.



5
6
7
# File 'lib/spot_flow/execution.rb', line 5

def timer_expires_at
  @timer_expires_at
end

#tokens_inObject

Returns the value of attribute tokens_in.



5
6
7
# File 'lib/spot_flow/execution.rb', line 5

def tokens_in
  @tokens_in
end

#tokens_outObject

Returns the value of attribute tokens_out.



5
6
7
# File 'lib/spot_flow/execution.rb', line 5

def tokens_out
  @tokens_out
end

#variablesObject

Returns the value of attribute variables.



5
6
7
# File 'lib/spot_flow/execution.rb', line 5

def variables
  @variables
end

Class Method Details

.deserialize(json, context:) ⇒ Object



17
18
19
20
21
22
23
24
# File 'lib/spot_flow/execution.rb', line 17

def self.deserialize(json, context:)
  if json.is_a?(String)
    attributes = JSON.parse(json)
  else
    attributes = json
  end
  Execution.from_json(attributes, context: context)
end

.from_json(attributes, context:) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
# File 'lib/spot_flow/execution.rb', line 26

def self.from_json(attributes, context:)
  step_id = attributes.delete("step_id")
  step_type = attributes.delete("step_type")
  step = step_type == "Process" ? context.process_by_id(step_id) : context.element_by_id(step_id)
  child_attributes = attributes.delete("children")
  Execution.new(attributes.merge(step: step, context:)).tap do |execution|
    execution.children = child_attributes.map do |ca| 
      Execution.from_json(ca, context:).tap { |child| child.parent = execution }
    end if child_attributes
  end
end

.start(context:, process:, variables: {}, start_event_id: nil, parent: nil) ⇒ Object



10
11
12
13
14
15
# File 'lib/spot_flow/execution.rb', line 10

def self.start(context:, process:, variables: {}, start_event_id: nil, parent: nil)
  Execution.new(context: context, step: process, variables: variables, start_event_id: start_event_id, parent: parent).tap do |execution|
    context.executions.push execution
    execution.start
  end
end

Instance Method Details

#activated?Boolean

Returns:

  • (Boolean)


64
65
66
# File 'lib/spot_flow/execution.rb', line 64

def activated?
  status == "activated"
end

#as_json(_options = {}) ⇒ Object



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/spot_flow/execution.rb', line 242

def as_json(_options = {})
  {
    id: id,
    step_id: step&.id,
    step_type: step&.class&.name&.demodulize,
    attached_to_id: attached_to_id,
    status: status,
    started_at: started_at,
    ended_at: ended_at,
    variables: variables.as_json,
    tokens_in: tokens_in,
    tokens_out: tokens_out,
    message_names: message_names,
    error_names: error_names,
    timer_expires_at: timer_expires_at,
    condition: condition,
    children: children.map { |child| child.as_json },
  }.transform_values(&:presence).compact
end

#attached_toObject



209
210
211
# File 'lib/spot_flow/execution.rb', line 209

def attached_to
  @attached_to ||= parent.children.find { |child| child.id == attached_to_id } if parent
end

#call(process) ⇒ Object



197
198
199
# File 'lib/spot_flow/execution.rb', line 197

def call(process)
  execute_step(process, attached_to: self)
end

#check_expired_timersObject



169
170
171
# File 'lib/spot_flow/execution.rb', line 169

def check_expired_timers
  waiting_children.each { |child| child.signal if child.timer_expires_at.present? && Time.zone.now > child.timer_expires_at }
end

#child_by_step_id(id) ⇒ Object



213
214
215
# File 'lib/spot_flow/execution.rb', line 213

def child_by_step_id(id)
  children.find { |child| child.step.id == id }
end

#completed?Boolean

Returns:

  • (Boolean)


72
73
74
# File 'lib/spot_flow/execution.rb', line 72

def completed?
  status == "completed"
end

#continueObject



104
105
106
# File 'lib/spot_flow/execution.rb', line 104

def continue
  step.execute(self)
end

#end(notify_parent = false) ⇒ Object



118
119
120
121
122
123
124
125
126
127
# File 'lib/spot_flow/execution.rb', line 118

def end(notify_parent = false)
  @status = "completed" unless status == "terminated"
  map_output_variables if step&.output_mappings&.present?
  parent.variables.merge!(variables) if parent && variables.present?
  @ended_at = Time.zone.now
  context.notify_listener(:execution_ended, execution: self)
  children.each { |child| child.terminate unless child.ended? }
  parent.children.each { |child| child.terminate if child.attached_to == self && child.waiting? } if parent
  parent.has_ended(self) if parent && notify_parent
end

#ended?Boolean

Returns:

  • (Boolean)


60
61
62
# File 'lib/spot_flow/execution.rb', line 60

def ended?
  ended_at.present?
end

#evaluate_condition(condition) ⇒ Object



173
174
175
# File 'lib/spot_flow/execution.rb', line 173

def evaluate_condition(condition)
  evaluate_expression(condition.delete_prefix("=")) == true
end

#evaluate_expression(expression, variables: parent&.variables || {}.with_indifferent_access) ⇒ Object



177
178
179
# File 'lib/spot_flow/execution.rb', line 177

def evaluate_expression(expression, variables: parent&.variables || {}.with_indifferent_access)
  SpotFeel.evaluate(expression.delete_prefix("="), variables:)
end

#execute_step(step, attached_to: nil, sequence_flow: nil) ⇒ Object



84
85
86
87
88
89
# File 'lib/spot_flow/execution.rb', line 84

def execute_step(step, attached_to: nil, sequence_flow: nil)
  child_execution = children.find { |child| child.step.id == step.id }
  child_execution = Execution.new(context: context, step: step, parent: self, attached_to_id: attached_to&.id).tap { |ce| children.push ce } unless child_execution
  child_execution.tokens_in += [sequence_flow.id] if sequence_flow
  child_execution.start
end

#execute_steps(steps) ⇒ Object



80
81
82
# File 'lib/spot_flow/execution.rb', line 80

def execute_steps(steps)
  steps.each { |step| execute_step(step) }
end

#gen_uidObject



38
39
40
# File 'lib/spot_flow/execution.rb', line 38

def gen_uid
  rand(36**8).to_s(36)
end

#has_ended(_child) ⇒ Object

Called by the child step executors when they have ended



204
205
206
207
# File 'lib/spot_flow/execution.rb', line 204

def has_ended(_child)
  step.leave(self) if step.is_a?(SpotFlow::Bpmn::SubProcess) || step.is_a?(SpotFlow::Bpmn::CallActivity)
  self.end(true)
end

#inspectObject



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/spot_flow/execution.rb', line 262

def inspect
  parts = ["#<Execution @id=#{id.inspect}"]
  parts << "@step_type=#{step&.class&.name&.demodulize}" if step
  parts << "@step_id=#{step.id.inspect}" if step
  parts << "@status=#{status.inspect}" if status
  parts << "@started_at=#{started_at.inspect}" if started_at
  parts << "@ended_at=#{ended_at.inspect}" if ended_at
  parts << "@attached_to_id=#{attached_to_id.inspect}" if attached_to_id
  parts << "@variables=#{variables.inspect}" if variables.present?
  # parts << "@tokens_in=#{tokens_in.inspect}" if tokens_in.present?
  # parts << "@tokens_out=#{tokens_out.inspect}" if tokens_out.present?
  parts << "@message_names=#{message_names.inspect}" if message_names.present?
  parts << "@error_names=#{error_names.inspect}" if error_names.present?
  parts << "@timer_expires_at=#{timer_expires_at.inspect}" if timer_expires_at
  parts << "@condition=#{condition.inspect}" if condition
  parts << "@children=#{children.inspect}" if children.present?
  parts.join(" ") + ">"
end

#invoke_listeners(type, sequence_flow = nil) ⇒ Object



91
92
93
# File 'lib/spot_flow/execution.rb', line 91

def invoke_listeners(type, sequence_flow = nil)
  context.listeners.each { |listener| listener[type].call(self, sequence_flow) if listener[type] }
end

#runObject



185
186
187
188
189
190
191
192
193
194
195
# File 'lib/spot_flow/execution.rb', line 185

def run
  return unless step.is_automated?

  result = step.run(self)

  if result.present?
    signal(result)
  else
    wait
  end
end

#run_automated_tasksObject



181
182
183
# File 'lib/spot_flow/execution.rb', line 181

def run_automated_tasks
  waiting_automated_tasks.each { |child| child.run }
end

#serializeObject



238
239
240
# File 'lib/spot_flow/execution.rb', line 238

def serialize(...)
  to_json(...)
end

#signal(result = nil) ⇒ Object

Raises:



141
142
143
144
145
# File 'lib/spot_flow/execution.rb', line 141

def signal(result = nil)
  @variables.merge!(result_to_variables(result)) if result.present?
  raise ExecutionError.new("Cannot signal a step execution that has ended.") if ended?
  step.signal(self)
end

#startObject



95
96
97
98
99
100
101
102
# File 'lib/spot_flow/execution.rb', line 95

def start
  @status = "started"
  @started_at = Time.zone.now
  map_input_variables if step&.input_mappings&.present?
  context.notify_listener(:execution_started, execution: self)
  step.attachments.each { |attachment| parent.execute_step(attachment, attached_to: self) } if step.is_a?(SpotFlow::Bpmn::Activity)
  continue
end

#started?Boolean

Returns:

  • (Boolean)


56
57
58
# File 'lib/spot_flow/execution.rb', line 56

def started?
  started_at.present?
end

#take(sequence_flow) ⇒ Object



133
134
135
136
137
138
139
# File 'lib/spot_flow/execution.rb', line 133

def take(sequence_flow)
  to_step = sequence_flow.target
  tokens_out.push sequence_flow.id
  tokens_out.uniq!
  context.notify_listener(:flow_taken, execution: self, sequence_flow: sequence_flow)
  parent.execute_step(to_step, sequence_flow: sequence_flow)
end

#take_all(sequence_flows) ⇒ Object



129
130
131
# File 'lib/spot_flow/execution.rb', line 129

def take_all(sequence_flows)
  sequence_flows.each { |sequence_flow| take(sequence_flow) }
end

#terminateObject



113
114
115
116
# File 'lib/spot_flow/execution.rb', line 113

def terminate
  @status = "terminated"
  self.end
end

#terminated?Boolean

Returns:

  • (Boolean)


76
77
78
# File 'lib/spot_flow/execution.rb', line 76

def terminated?
  status == "terminated"
end

#throw_error(error_name, variables: {}) ⇒ Object



158
159
160
161
162
163
164
165
166
167
# File 'lib/spot_flow/execution.rb', line 158

def throw_error(error_name, variables: {})
  waiting_children.each do |child|
    step = child.step
    if step.is_a?(SpotFlow::Bpmn::Event) && step.error_event_definitions.any? { |error_event_definition| error_event_definition.error_name == error_name }
      child.signal(variables)
      break
    end
  end
  context.notify_listener(:error_thrown, execution: self, error_name: error_name)
end

#throw_message(message_name, variables: {}) ⇒ Object



147
148
149
150
151
152
153
154
155
156
# File 'lib/spot_flow/execution.rb', line 147

def throw_message(message_name, variables: {})
  waiting_children.each do |child|
    step = child.step
    if step.is_a?(SpotFlow::Bpmn::Event) && step.message_event_definitions.any? { |message_event_definition| message_event_definition.message_name == message_name }
      child.signal(variables)
      break
    end
  end
  context.notify_listener(:message_thrown, execution: self, message_name: message_name)
end

#tokens(active_tokens = []) ⇒ Object



229
230
231
232
233
234
235
236
# File 'lib/spot_flow/execution.rb', line 229

def tokens(active_tokens = [])
  children.each do |child|
    active_tokens = active_tokens + child.tokens_out
    active_tokens = active_tokens - child.tokens_in if child.ended?
    active_tokens = active_tokens + child.tokens(active_tokens)
  end
  active_tokens.uniq
end

#waitObject



108
109
110
111
# File 'lib/spot_flow/execution.rb', line 108

def wait
  @status = "waiting"
  context.notify_listener(:execution_waited, execution: self)
end

#waiting?Boolean

Returns:

  • (Boolean)


68
69
70
# File 'lib/spot_flow/execution.rb', line 68

def waiting?
  status == "waiting"
end

#waiting_automated_tasksObject



225
226
227
# File 'lib/spot_flow/execution.rb', line 225

def waiting_automated_tasks
  waiting_tasks.select { |child| child.step.is_automated? }
end

#waiting_childrenObject



217
218
219
# File 'lib/spot_flow/execution.rb', line 217

def waiting_children
  children.filter { |child| child.waiting? }
end

#waiting_tasksObject



221
222
223
# File 'lib/spot_flow/execution.rb', line 221

def waiting_tasks
  waiting_children.select { |child| child.step.is_a?(SpotFlow::Bpmn::Task) }
end