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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ec2c62e8ee6f2863a951627cfe2733f5d015feb981cd036bbabff22c7d681ea0
|
4
|
+
data.tar.gz: 416207e69a02f5c8ba3308369c46bdcc783a162ed9eb45ef1f8788ec6dbc0dc7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0a0526b9ef739fdb028d6864f4144a9e7e58da25eca2553acfe82933175c82d3d85818e12f33884a5e9efda650b78698aefb093dbd4a70347952a178dc4cd436
|
7
|
+
data.tar.gz: 16c1a43d577b831a774c57e748e57c78c6750eec256aef6bf9cbf5991996aa33fdacf33ecbc37e2e327c21573b525dd2bafffc6dc58216e938fa3729d848f70b
|
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
|
+

|
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,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
|