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