bpmn 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 33862df282c4b5eae3fa1b4317e7762c299ace6ac8794f138efc86d6225645c0
4
+ data.tar.gz: 7f67bb1e73ee9020c5f5bb5f67ff29c2312f3412ae8ae77a3be142c1d12c6a1e
5
+ SHA512:
6
+ metadata.gz: c4e9586ba65c154da496be6b5635000ab3f6e61624f8733a2b2fec87eecac5b193df176075d4db146d84e09d22e99fcce2090e559fd01ed8847d4399d3808b42
7
+ data.tar.gz: f995dbb81528b381e56d8cd74286ae273d8e890a4a2f2f35fca8de736169cce2eddec467148a48d4bd4943a8b8345bed2b2d99285921417a07457dc8adecfe8f
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2022 Connected Bits, LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # Spot Flow
2
+
3
+ Spot Flow is a workflow engine for Rails applications based on the [bpmn](https://www.bpmn.org) standard. It executes business processes defined in a [modeler](https://camunda.com/download/modeler/). It uses [Spot Feel](https://github.com/connectedbits/spot-feel) to evaluate expressions and business rules in the BPMN model. It can be used with [Spot Form](https://github.com/connectedbits/spot-form) to render dynamic forms for user tasks.
4
+
5
+ ## Usage
6
+
7
+ The engine executes business processes like [this one](/test/fixtures/files/hello_world.bpmn).
8
+
9
+ ![Example](test/fixtures/files/hello_world.png)
10
+
11
+ Processes are made of a series of tasks. Tasks can be manual (require `signal` to be called) or automated (can be `run` by the engine). The engine supports the following task types:
12
+
13
+ - Task and UserTask (manual): requires a signal to continue.
14
+ - ServiceTask (automated): instantiates a type defined in the task definition and invokes `call` on it.
15
+ - BusinessRuleTask (automated): evaluates the decision_id (expects dmn source to be included in the context).
16
+ - ScriptTask (automated): evaluates the FEEL expression in the script property.
17
+
18
+ To start the process, initialize SpotFlow with the BPMN and DMN source files, then call `start`.
19
+
20
+ ```ruby
21
+ sources = [
22
+ File.read("hello_world.bpmn"),
23
+ File.read("choose_greeting.dmn")
24
+ ]
25
+ execution = SpotFlow.new(sources).start
26
+ ```
27
+
28
+ It's often useful to print the process state to the console.
29
+
30
+ ```ruby
31
+ execution.print
32
+ ```
33
+
34
+ ```ruby
35
+ HelloWorld started * Flow_080794y
36
+
37
+ 0 StartEvent Start: completed * out: Flow_080794y
38
+ 1 UserTask IntroduceYourself: waiting * in: Flow_080794y
39
+ 2 BoundaryEvent EggTimer: waiting
40
+ ```
41
+
42
+ The HelloWorld process began at the Start event and is _waiting_ at the IntroduceYourself user task. This is an important concept in the SpotFlow engine. It's designed to be used in a Rails application where a process might be waiting for a user to complete a form, or a background job to complete. It's common to save the state the process until a task is complete. Calling `serialize` on a process will return the execution state so it can be continued later.
43
+
44
+ ```ruby
45
+ # Returns a hash of the process state.
46
+ execution_state = execution.serialize
47
+
48
+ # Now we can save the execution state in a database until a user submits a form (UserTask)
49
+ # or a background job completes (ServiceTask)
50
+
51
+ # Restores the process from the execution state.
52
+ execution = SpotFlow.restore(sources, execution_state:)
53
+
54
+ # Now we can continue the process by `signaling` the waiting task.
55
+ step = execution.step_by_element_id("IntroduceYourself")
56
+ step.signal(name: "Eric", language: "it", formal: false, cookie: true)
57
+ ```
58
+
59
+ After the IntroduceYourself task is _signaled_, the execution continues.
60
+
61
+ ```ruby
62
+ HelloWorld started * Flow_0gi9kt6, Flow_0pb7kh6
63
+
64
+ {
65
+ "name": "Eric",
66
+ "language": "it",
67
+ "formal": false,
68
+ "cookie": true
69
+ }
70
+
71
+ 0 StartEvent Start: completed * out: Flow_080794y
72
+ 1 UserTask IntroduceYourself: completed { "name": "Eric", "language": "it", "formal": false, "cookie": true } * in: Flow_080794y * out: Flow_0t9jhga
73
+ 2 BoundaryEvent EggTimer: terminated
74
+ 3 ParallelGateway Split: completed * in: Flow_0t9jhga * out: Flow_0gi9kt6, Flow_0q1vtg3
75
+ 4 BusinessRuleTask ChooseGreeting: waiting * in: Flow_0gi9kt6
76
+ 5 ExclusiveGateway WantsCookie: completed * in: Flow_0q1vtg3 * out: Flow_0pb7kh6
77
+ 6 ServiceTask ChooseFortune: waiting * in: Flow_0pb7kh6
78
+ ```
79
+
80
+ When execution reaches the Split inclusive gateway, it forks into two paths. The first path is _waiting_ at the ChooseGreeting business rule task. The second reaches the WantsCookie exclusive gateway and _evaluates_ the sequence flow conditions before continuing to the ChooseFortune service task. Automated tasks can be `run` individually by the engine or `run_automated_tasks` to run all _waiting_ tasks.
81
+
82
+ ```ruby
83
+ execution.run_automated_tasks
84
+ ```
85
+
86
+ Now, both paths are joined into one and execution is _waiting_ at the script task. Notice, the results of previous tasks are merged into the process variables.
87
+
88
+ ```ruby
89
+ HelloWorld started * Flow_0ws7a4m
90
+
91
+ {
92
+ "name": "Eric",
93
+ "language": "it",
94
+ "formal": false,
95
+ "cookie": true,
96
+ "choose_greeting": {
97
+ "greeting": "Ciao"
98
+ },
99
+ "choose_fortune": "A foolish man listens to his heart. A wise man listens to cookies."
100
+ }
101
+
102
+ 0 StartEvent Start: completed * out: Flow_080794y
103
+ 1 UserTask IntroduceYourself: completed { "name": "Eric", "language": "it", "formal": false, "cookie": true } * in: Flow_080794y * out: Flow_0t9jhga
104
+ 2 BoundaryEvent EggTimer: terminated
105
+ 3 ParallelGateway Split: completed * in: Flow_0t9jhga * out: Flow_0gi9kt6, Flow_0q1vtg3
106
+ 4 BusinessRuleTask ChooseGreeting: completed { "choose_greeting": { "greeting": "Ciao" } } * in: Flow_0gi9kt6 * out: Flow_1652shv
107
+ 5 ExclusiveGateway WantsCookie: completed * in: Flow_0q1vtg3 * out: Flow_0pb7kh6
108
+ 6 ServiceTask ChooseFortune: completed { "choose_fortune": "A foolish man listens to his heart. A wise man listens to cookies." } * in: Flow_0pb7kh6 * out: Flow_1iuc1xe
109
+ 7 ParallelGateway Join: completed * in: Flow_1652shv, Flow_1iuc1xe * out: Flow_0ws7a4m
110
+ 8 ScriptTask GenerateMessage: waiting * in: Flow_0ws7a4m
111
+ ```
112
+
113
+ This time we'll `run` the script task manually.
114
+
115
+ ```ruby
116
+ step = execution.step_by_element_id("GenerateMessage")
117
+ step.run
118
+ ```
119
+
120
+ Now the process is complete.
121
+
122
+ ```ruby
123
+ HelloWorld completed *
124
+
125
+ {
126
+ "name": "Eric",
127
+ "language": "it",
128
+ "formal": false,
129
+ "cookie": true,
130
+ "choose_greeting": {
131
+ "greeting": "Ciao"
132
+ },
133
+ "choose_fortune": "A foolish man listens to his heart. A wise man listens to cookies.",
134
+ "message": "👋 Ciao Eric 🥠 A foolish man listens to his heart. A wise man listens to cookies."
135
+ }
136
+
137
+ 0 StartEvent Start: completed * out: Flow_080794y
138
+ 1 UserTask IntroduceYourself: completed { "name": "Eric", "language": "it", "formal": false, "cookie": true } * in: Flow_080794y * out: Flow_0t9jhga
139
+ 2 BoundaryEvent EggTimer: terminated
140
+ 3 ParallelGateway Split: completed * in: Flow_0t9jhga * out: Flow_0gi9kt6, Flow_0q1vtg3
141
+ 4 BusinessRuleTask ChooseGreeting: completed { "choose_greeting": { "greeting": "Ciao" } } * in: Flow_0gi9kt6 * out: Flow_1652shv
142
+ 5 ExclusiveGateway WantsCookie: completed * in: Flow_0q1vtg3 * out: Flow_0pb7kh6
143
+ 6 ServiceTask ChooseFortune: completed { "choose_fortune": "A foolish man listens to his heart. A wise man listens to cookies." } * in: Flow_0pb7kh6 * out: Flow_1iuc1xe
144
+ 7 ParallelGateway Join: completed * in: Flow_1652shv, Flow_1iuc1xe * out: Flow_0ws7a4m
145
+ 8 ScriptTask GenerateMessage: completed { "message": "👋 A foolish man listens to his heart. A wise man listens to cookies." } * in: Flow_0ws7a4m * out: Flow_0gkfhvr
146
+ 9 EndEvent End: completed * in: Flow_0gkfhvr
147
+ ```
148
+
149
+ ## Documentation
150
+
151
+ - [Processes](/docs/processes.md)
152
+ - [Tasks](/docs/tasks.md)
153
+ - [Events](/docs/events.md)
154
+ - [Event Definitions](/docs/event_definitions.md)
155
+ - [Gateways](/docs/gateways.md)
156
+ - [Expressions](/docs/expressions.md)
157
+ - [Data Flow](/docs/data_flow.md)
158
+ - [Execution](/docs/execution.md)
159
+
160
+ ## Installation
161
+
162
+ Execute:
163
+
164
+ ```bash
165
+ $ bundle add spot_flow
166
+ ```
167
+
168
+ Or install it directly:
169
+
170
+ ```bash
171
+ $ gem install spot_flow
172
+ ```
173
+
174
+ ## Development
175
+
176
+ ```bash
177
+ $ git clone ...
178
+ $ bin/setup
179
+ $ bin/rake
180
+ $ bin/guard
181
+ ```
182
+
183
+ ## License
184
+
185
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
186
+
187
+ Developed by [Connected Bits](http://www.connectedbits.com)
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/setup"
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << "test"
7
+ t.libs << "lib"
8
+ t.test_files = FileList["test/**/*_test.rb"]
9
+ end
10
+
11
+ task default: :test
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpotFlow
4
+ module Bpmn
5
+ class Definitions
6
+ include ActiveModel::Model
7
+
8
+ attr_accessor :id, :name, :target_namespace, :exporter, :exporter_version, :execution_platform, :execution_platform_version
9
+ attr_reader :messages, :signals, :errors, :processes
10
+
11
+ def self.from_xml(xml)
12
+ XmlHasher.configure do |config|
13
+ config.snakecase = true
14
+ config.ignore_namespaces = true
15
+ config.string_keys = false
16
+ end
17
+ hash = XmlHasher.parse(xml)
18
+ Definitions.new(hash[:definitions].except(:bpmn_diagram)).tap do |definitions|
19
+ definitions.processes.each do |process|
20
+ process.wire_references(definitions)
21
+ end
22
+ end
23
+ end
24
+
25
+ def initialize(attributes={})
26
+ super(attributes.except(:message, :signal, :error, :process))
27
+
28
+ @messages = Array.wrap(attributes[:message]).map { |atts| Message.new(atts) }
29
+ @signals = Array.wrap(attributes[:signal]).map { |atts| Signal.new(atts) }
30
+ @errors = Array.wrap(attributes[:error]).map { |atts| Error.new(atts) }
31
+ @processes = Array.wrap(attributes[:process]).map { |atts| Process.new(atts) }
32
+ end
33
+
34
+ def message_by_id(id)
35
+ messages.find { |message| message.id == id }
36
+ end
37
+
38
+ def signal_by_id(id)
39
+ signals.find { |signal| signal.id == id }
40
+ end
41
+
42
+ def error_by_id(id)
43
+ errors.find { |error| error.id == id }
44
+ end
45
+
46
+ def process_by_id(id)
47
+ processes.find { |process| process.id == id }
48
+ end
49
+
50
+ def inspect
51
+ "#<Bpmn::Definitions @id=#{id.inspect} @name=#{name.inspect} @messages=#{messages.inspect} @signals=#{signals.inspect} @errors=#{errors.inspect} @processes=#{processes.inspect}>"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpotFlow
4
+ module Bpmn
5
+ class Element
6
+ include ActiveModel::Model
7
+
8
+ attr_accessor :id, :name, :extension_elements
9
+
10
+ def initialize(attributes = {})
11
+ super(attributes.slice(:id, :name))
12
+
13
+ @extension_elements = ExtensionElements.new(attributes[:extension_elements]) if attributes[:extension_elements].present?
14
+ end
15
+
16
+ def inspect
17
+ "#<#{self.class.name.gsub(/SpotFlow::/, "")} @id=#{id.inspect} @name=#{name.inspect}>"
18
+ end
19
+ end
20
+
21
+ class Message < Element
22
+ end
23
+
24
+ class Signal < Element
25
+ end
26
+
27
+ class Error < Element
28
+ end
29
+
30
+ class Collaboration < Element
31
+ end
32
+
33
+ class LaneSet < Element
34
+ end
35
+
36
+ class Participant < Element
37
+ attr_accessor :process_ref, :process
38
+
39
+ def initialize(attributes = {})
40
+ super(attributes.except(:process_ref))
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpotFlow
4
+ module Bpmn
5
+ class Event < Step
6
+ attr_accessor :event_definitions
7
+
8
+ def initialize(attributes = {})
9
+ super(attributes.except(:message_event_definition, :signal_event_definition, :error_event_definition, :terminate_event_definition, :timer_event_definition))
10
+
11
+ @event_definitions = []
12
+
13
+ Array.wrap(attributes[:message_event_definition]).each do |med|
14
+ @event_definitions.push MessageEventDefinition.new(med)
15
+ end if attributes[:message_event_definition].present?
16
+
17
+ Array.wrap(attributes[:signal_event_definition]).each do |sed|
18
+ @event_definitions.push SignalEventDefinition.new(sed)
19
+ end if attributes[:signal_event_definition].present?
20
+
21
+ Array.wrap(attributes[:error_event_definition]).each do |eed|
22
+ @event_definitions.push ErrorEventDefinition.new(eed)
23
+ end if attributes[:error_event_definition].present?
24
+
25
+ Array.wrap(attributes[:terminate_event_definition]).each do |ted|
26
+ @event_definitions.push TerminateEventDefinition.new(ted)
27
+ end if attributes[:terminate_event_definition].present?
28
+
29
+ Array.wrap(attributes[:timer_event_definition]).each do |ted|
30
+ @event_definitions.push TimerEventDefinition.new(ted)
31
+ end if attributes[:timer_event_definition].present?
32
+ end
33
+
34
+ def event_definition_ids
35
+ event_definitions.map(&:id)
36
+ end
37
+
38
+ def is_catching?
39
+ false
40
+ end
41
+
42
+ def is_throwing?
43
+ false
44
+ end
45
+
46
+ def is_none?
47
+ event_definitions.empty?
48
+ end
49
+
50
+ def is_conditional?
51
+ conditional_event_definition.present?
52
+ end
53
+
54
+ def is_escalation?
55
+ escalation_event_definition.present?
56
+ end
57
+
58
+ def is_error?
59
+ error_event_definition.present?
60
+ end
61
+
62
+ def is_message?
63
+ !message_event_definitions.empty?
64
+ end
65
+
66
+ def is_signal?
67
+ !signal_event_definitions.empty?
68
+ end
69
+
70
+ def is_terminate?
71
+ terminate_event_definition.present?
72
+ end
73
+
74
+ def is_timer?
75
+ timer_event_definition.present?
76
+ end
77
+
78
+ def conditional_event_definition
79
+ event_definitions.find { |ed| ed.is_a?(ConditionalEventDefinition) }
80
+ end
81
+
82
+ def escalation_event_definition
83
+ event_definitions.find { |ed| ed.is_a?(EscalationEventDefinition) }
84
+ end
85
+
86
+ def error_event_definitions
87
+ event_definitions.select { |ed| ed.is_a?(ErrorEventDefinition) }
88
+ end
89
+
90
+ def error_event_definition
91
+ event_definitions.find { |ed| ed.is_a?(ErrorEventDefinition) }
92
+ end
93
+
94
+ def message_event_definitions
95
+ event_definitions.select { |ed| ed.is_a?(MessageEventDefinition) }
96
+ end
97
+
98
+ def signal_event_definitions
99
+ event_definitions.select { |ed| ed.is_a?(SignalEventDefinition) }
100
+ end
101
+
102
+ def terminate_event_definition
103
+ event_definitions.find { |ed| ed.is_a?(TerminateEventDefinition) }
104
+ end
105
+
106
+ def timer_event_definition
107
+ event_definitions.find { |ed| ed.is_a?(TimerEventDefinition) }
108
+ end
109
+
110
+ def execute(execution)
111
+ event_definitions.each { |event_definition| event_definition.execute(execution) }
112
+ end
113
+ end
114
+
115
+ class StartEvent < Event
116
+
117
+ def is_catching?
118
+ true
119
+ end
120
+
121
+ def execute(execution)
122
+ super
123
+ leave(execution)
124
+ end
125
+ end
126
+
127
+ class IntermediateThrowEvent < Event
128
+
129
+ def is_throwing?
130
+ true
131
+ end
132
+
133
+ def execute(execution)
134
+ super
135
+ leave(execution)
136
+ end
137
+ end
138
+
139
+ class IntermediateCatchEvent < Event
140
+
141
+ def is_catching?
142
+ true
143
+ end
144
+
145
+ def execute(execution)
146
+ super
147
+ execution.wait
148
+ end
149
+
150
+ def signal(execution)
151
+ leave(execution)
152
+ end
153
+ end
154
+
155
+ class BoundaryEvent < Event
156
+ attr_accessor :attached_to_ref, :attached_to, :cancel_activity
157
+
158
+ def initialize(attributes = {})
159
+ super(attributes.except(:attached_to_ref, :cancel_activity))
160
+
161
+ @attached_to_ref = attributes[:attached_to_ref]
162
+ @cancel_activity = true
163
+ if attributes[:cancel_activity].present?
164
+ @cancel_activity = attributes[:cancel_activity] == "false" ? false : true
165
+ end
166
+ end
167
+
168
+ def is_catching?
169
+ true
170
+ end
171
+
172
+ def execute(execution)
173
+ super
174
+ execution.wait
175
+ end
176
+
177
+ def signal(execution)
178
+ execution.attached_to.terminate if cancel_activity
179
+ leave(execution)
180
+ end
181
+ end
182
+
183
+ class EndEvent < Event
184
+
185
+ def is_throwing?
186
+ true
187
+ end
188
+
189
+ def execute(execution)
190
+ super
191
+ execution.end(true)
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpotFlow
4
+ module Bpmn
5
+ class EventDefinition < Element
6
+ def execute(execution)
7
+ end
8
+ end
9
+
10
+ class ConditionalEventDefinition < EventDefinition
11
+ attr_accessor :variable_name, :variable_events, :condition
12
+
13
+ def initialize(attributes = {})
14
+ super(attributes.except(:variable_name, :variable_events, :condition))
15
+
16
+ @variable_name = moddle[:variable_name] # "var1"
17
+ @variable_events = moddle[:variable_events] # "create, update"
18
+ @condition = moddle[:condition] # var1 = 1
19
+ end
20
+ end
21
+
22
+ class EscalationEventDefinition < EventDefinition
23
+ end
24
+
25
+ class ErrorEventDefinition < EventDefinition
26
+ attr_accessor :error_ref, :error
27
+ attr_accessor :error_code_variable, :error_message_variable
28
+
29
+ def initialize(attributes = {})
30
+ super(attributes.except(:error_ref, :error_code_variable, :error_message_variable))
31
+
32
+ @error_ref = attributes[:error_ref]
33
+ @error_code_variable = attributes[:error_code_variable]
34
+ @error_message_variable = attributes[:error_message_variable]
35
+ end
36
+
37
+ def execute(execution)
38
+ if execution.step.is_throwing?
39
+ execution.throw_error(error_name)
40
+ else
41
+ execution.error_names.push error_name
42
+ end
43
+ end
44
+
45
+ def error_id
46
+ error&.id
47
+ end
48
+
49
+ def error_name
50
+ error&.name
51
+ end
52
+ end
53
+
54
+ class MessageEventDefinition < EventDefinition
55
+ attr_accessor :message_ref, :message
56
+
57
+ def initialize(attributes = {})
58
+ super(attributes.except(:message_ref))
59
+
60
+ @message_ref = attributes[:message_ref]
61
+ end
62
+
63
+ def execute(execution)
64
+ if execution.step.is_throwing?
65
+ execution.throw_message(message_name)
66
+ else
67
+ execution.message_names.push message_name
68
+ end
69
+ end
70
+
71
+ def message_id
72
+ message&.id
73
+ end
74
+
75
+ def message_name
76
+ message&.name
77
+ end
78
+ end
79
+
80
+ class SignalEventDefinition < EventDefinition
81
+ attr_accessor :signal_ref, :signal
82
+
83
+ def initialize(attributes = {})
84
+ super(attributes.except(:signal_ref))
85
+
86
+ @signal_ref = moddle[:signal_ref]
87
+ end
88
+
89
+ def signal_id
90
+ signal&.id
91
+ end
92
+
93
+ def signal_name
94
+ signal&.name
95
+ end
96
+ end
97
+
98
+ class TerminateEventDefinition < EventDefinition
99
+
100
+ def execute(execution)
101
+ execution.parent&.terminate
102
+ end
103
+ end
104
+
105
+ class TimerEventDefinition < EventDefinition
106
+ attr_accessor :time_date, :time_duration_type, :time_duration, :time_cycle
107
+
108
+ def initialize(attributes = {})
109
+ super(attributes.except(:time_date, :time_duration, :time_cycle))
110
+
111
+ @time_duration_type = attributes[:time_duration_type]
112
+ @time_duration = attributes[:time_duration]
113
+ end
114
+
115
+ def execute(execution)
116
+ if execution.step.is_catching?
117
+ execution.timer_expires_at = time_due
118
+ end
119
+ end
120
+
121
+ private
122
+
123
+ def time_due
124
+ # Return the next time the timer is due
125
+ if time_date
126
+ return Date.parse(time_date)
127
+ elsif time_duration
128
+ return Time.zone.now + ActiveSupport::Duration.parse(time_duration)
129
+ else
130
+ return Time.zone.now # time_cycle not yet implemented
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpotFlow
4
+ module Bpmn
5
+ class ExtensionElements
6
+ VALID_EXTENSION_NAMESPACES = %w[zeebe]
7
+
8
+ attr_accessor :assignment_definition, :called_element, :called_decision, :form_definition, :io_mapping, :properties, :script, :subscription, :task_definition, :task_headers, :task_schedule
9
+
10
+ def initialize(attributes = {})
11
+ if attributes[:properties].present?
12
+ @properties = HashWithIndifferentAccess.new
13
+ Array.wrap(attributes[:properties][:property]).each { |property_moddle| @properties[property_moddle[:name]] = property_moddle[:value] }
14
+ end
15
+
16
+ @assignment_definition = Zeebe::AssignmentDefinition.new(attributes[:assignment_definition]) if attributes[:assignment_definition].present?
17
+ @called_element = Zeebe::CalledElement.new(attributes[:called_element]) if attributes[:called_element].present?
18
+ @called_decision = Zeebe::CalledDecision.new(attributes[:called_decision]) if attributes[:called_decision].present?
19
+ @form_definition = Zeebe::FormDefinition.new(attributes[:form_definition]) if attributes[:form_definition].present?
20
+ @io_mapping = Zeebe::IoMapping.new(attributes[:io_mapping]) if attributes[:io_mapping].present?
21
+ @script = Zeebe::Script.new(attributes[:script]) if attributes[:script].present?
22
+ @subscription = Zeebe::Subscription.new(attributes[:subscription]) if attributes[:subscription].present?
23
+ @task_definition = Zeebe::TaskDefinition.new(attributes[:task_definition]) if attributes[:task_definition].present?
24
+ @task_headers = Zeebe::TaskHeaders.new(attributes[:task_headers]) if attributes[:task_headers].present?
25
+ @task_schedule = Zeebe::TaskSchedule.new(attributes[:task_schedule]) if attributes[:task_schedule].present?
26
+ end
27
+ end
28
+ end
29
+ end