flow_core 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +7 -0
- data/app/models/flow_core/arc.rb +8 -0
- data/app/models/flow_core/arc_guard.rb +27 -2
- data/app/models/flow_core/branch.rb +48 -0
- data/app/models/flow_core/instance.rb +10 -1
- data/app/models/flow_core/pipeline.rb +58 -0
- data/app/models/flow_core/place.rb +2 -1
- data/app/models/flow_core/step.rb +337 -0
- data/app/models/flow_core/steps/end.rb +32 -0
- data/app/models/flow_core/steps/exclusive_choice.rb +72 -0
- data/app/models/flow_core/steps/parallel_split.rb +42 -0
- data/app/models/flow_core/steps/redirection.rb +39 -0
- data/app/models/flow_core/steps/task.rb +25 -0
- data/app/models/flow_core/task.rb +21 -8
- data/app/models/flow_core/transition.rb +44 -6
- data/app/models/flow_core/transition_callback.rb +19 -2
- data/app/models/flow_core/transition_trigger.rb +19 -2
- data/app/models/flow_core/workflow.rb +11 -4
- data/db/migrate/{20200130200532_create_flow_core_tables.rb → 20200130200532_create_workflow_tables.rb} +32 -29
- data/db/migrate/20200130200533_create_pipeline_tables.rb +64 -0
- data/lib/flow_core.rb +3 -4
- data/lib/flow_core/definition/net.rb +6 -4
- data/lib/flow_core/definition/transition.rb +37 -25
- data/lib/flow_core/engine.rb +5 -0
- data/lib/flow_core/locale/en.yml +9 -4
- data/lib/flow_core/task_executable.rb +4 -0
- data/lib/flow_core/version.rb +1 -1
- data/lib/flow_core/workflow_callbacks.rb +2 -0
- metadata +41 -4
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore::Steps
|
4
|
+
class End < FlowCore::Step
|
5
|
+
def deploy_to_workflow!(workflow, input_place_or_transition)
|
6
|
+
target_place = workflow.end_place || workflow.create_end_place!
|
7
|
+
|
8
|
+
if input_place_or_transition.is_a? FlowCore::Transition
|
9
|
+
target_place.input_transitions << input_place_or_transition
|
10
|
+
else
|
11
|
+
input_place_or_transition.input_arcs.update place: target_place
|
12
|
+
input_place_or_transition.reload.destroy!
|
13
|
+
end
|
14
|
+
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def creatable?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def redirection_step?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def redirection_configurable?
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore::Steps
|
4
|
+
class ExclusiveChoice < FlowCore::Step
|
5
|
+
after_create :auto_create_fallback_branch
|
6
|
+
|
7
|
+
def deploy_to_workflow!(workflow, input_transition)
|
8
|
+
return input_transition if branches.empty?
|
9
|
+
|
10
|
+
input_place = find_or_create_input_place(workflow, input_transition)
|
11
|
+
exclusive_choice_transition =
|
12
|
+
if transition_trigger
|
13
|
+
t = input_place.output_transitions.create! workflow: workflow,
|
14
|
+
name: name,
|
15
|
+
output_token_create_strategy: :match_one_or_fallback,
|
16
|
+
generated_by_step_id: id
|
17
|
+
copy_transition_trigger_to t
|
18
|
+
copy_transition_callbacks_to t
|
19
|
+
t
|
20
|
+
else
|
21
|
+
input_place.output_transitions.create! workflow: workflow,
|
22
|
+
name: name,
|
23
|
+
output_token_create_strategy: :match_one_or_fallback,
|
24
|
+
auto_finish_strategy: "synchronously",
|
25
|
+
generated_by_step_id: id
|
26
|
+
end
|
27
|
+
|
28
|
+
simple_merge_place = workflow.places.create! workflow: workflow
|
29
|
+
|
30
|
+
deploy_branches_to(workflow, exclusive_choice_transition, simple_merge_place)
|
31
|
+
|
32
|
+
simple_merge_place
|
33
|
+
end
|
34
|
+
|
35
|
+
def transition_trigger_attachable?
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
def branch_arc_guard_attachable?
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
def fallback_branch_required?
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
class << self
|
48
|
+
def creatable?
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
def multi_branch_step?
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def transition_callback_attachable?
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def branch_configurable?
|
61
|
+
true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def auto_create_fallback_branch
|
68
|
+
branches.create! name: I18n.t("flow_core.pipeline.fallback_branch_name"),
|
69
|
+
fallback_branch: true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore::Steps
|
4
|
+
class ParallelSplit < FlowCore::Step
|
5
|
+
def deploy_to_workflow!(workflow, input_transition)
|
6
|
+
return input_transition if branches.empty?
|
7
|
+
|
8
|
+
input_place = find_or_create_input_place(workflow, input_transition)
|
9
|
+
parallel_split_transition =
|
10
|
+
input_place.output_transitions.create! workflow: workflow,
|
11
|
+
name: name,
|
12
|
+
auto_finish_strategy: "synchronously",
|
13
|
+
generated_by_step_id: id
|
14
|
+
synchronization_transition =
|
15
|
+
workflow.transitions.create! workflow: workflow,
|
16
|
+
name: I18n.t("flow_core.pipeline.synchronization_transition_name"),
|
17
|
+
auto_finish_strategy: "synchronously"
|
18
|
+
|
19
|
+
deploy_branches_to(workflow, parallel_split_transition, synchronization_transition)
|
20
|
+
|
21
|
+
synchronization_transition
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def creatable?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def multi_branch_step?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def branch_configurable?
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
def barrier_step?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore::Steps
|
4
|
+
class Redirection < FlowCore::Step
|
5
|
+
def deploy_to_workflow!(workflow, input_place_or_transition)
|
6
|
+
target_transition = workflow.transitions.find_by! generated_by_step_id: redirect_to_step_id
|
7
|
+
target_place = target_transition.input_places.first
|
8
|
+
|
9
|
+
if input_place_or_transition.is_a? FlowCore::Transition
|
10
|
+
target_place.input_transitions << input_place_or_transition
|
11
|
+
else
|
12
|
+
input_place_or_transition.input_arcs.update place: target_place
|
13
|
+
input_place_or_transition.reload.destroy!
|
14
|
+
end
|
15
|
+
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def creatable?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def redirection_step?
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def redirection_configurable?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def update_verified
|
36
|
+
self.verified = valid? && redirect_to_step_id.present?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore::Steps
|
4
|
+
class Task < FlowCore::Step
|
5
|
+
def deploy_to_workflow!(workflow, input_place_or_transition)
|
6
|
+
input_place = find_or_create_input_place(workflow, input_place_or_transition)
|
7
|
+
|
8
|
+
transition = input_place.output_transitions.create! workflow: workflow, name: name, generated_by_step_id: id
|
9
|
+
copy_transition_trigger_to transition
|
10
|
+
copy_transition_callbacks_to transition
|
11
|
+
|
12
|
+
transition
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def creatable?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def transition_trigger_required?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -80,15 +80,20 @@ module FlowCore
|
|
80
80
|
def enable
|
81
81
|
return false unless can_enable?
|
82
82
|
|
83
|
-
|
84
|
-
|
85
|
-
|
83
|
+
status =
|
84
|
+
with_transaction_returning_status do
|
85
|
+
input_free_tokens.each(&:lock!)
|
86
|
+
update! stage: :enabled, enabled_at: Time.zone.now
|
86
87
|
|
87
|
-
|
88
|
-
|
88
|
+
transition.on_task_enable(self)
|
89
|
+
workflow.on_instance_task_enable(self)
|
89
90
|
|
90
|
-
|
91
|
-
|
91
|
+
true
|
92
|
+
end
|
93
|
+
|
94
|
+
try_auto_finish if status
|
95
|
+
|
96
|
+
status
|
92
97
|
end
|
93
98
|
|
94
99
|
def finish
|
@@ -170,7 +175,7 @@ module FlowCore
|
|
170
175
|
return if output_token_created
|
171
176
|
|
172
177
|
with_transaction_returning_status do
|
173
|
-
transition.
|
178
|
+
transition.create_output_tokens_for(self)
|
174
179
|
update! output_token_created: true
|
175
180
|
|
176
181
|
true
|
@@ -220,5 +225,13 @@ module FlowCore
|
|
220
225
|
def same_origin_tasks
|
221
226
|
instance.tasks.where(created_by_token: created_by_token)
|
222
227
|
end
|
228
|
+
|
229
|
+
def try_auto_finish
|
230
|
+
return unless can_finish?
|
231
|
+
|
232
|
+
if transition.auto_finish_strategy == "synchronously"
|
233
|
+
finish
|
234
|
+
end
|
235
|
+
end
|
223
236
|
end
|
224
237
|
end
|
@@ -6,19 +6,36 @@ module FlowCore
|
|
6
6
|
|
7
7
|
FORBIDDEN_ATTRIBUTES = %i[workflow_id created_at updated_at].freeze
|
8
8
|
|
9
|
+
belongs_to :generated_by,
|
10
|
+
class_name: "FlowCore::Step", foreign_key: :generated_by_step_id,
|
11
|
+
inverse_of: :generated_transitions, optional: true
|
12
|
+
|
9
13
|
belongs_to :workflow, class_name: "FlowCore::Workflow"
|
10
14
|
|
11
15
|
# NOTE: Place - out -> Transition - in -> Place
|
12
16
|
has_many :input_arcs, -> { where(direction: :in) },
|
13
|
-
class_name: "FlowCore::Arc", inverse_of: :transition, dependent: :
|
17
|
+
class_name: "FlowCore::Arc", inverse_of: :transition, dependent: :destroy
|
14
18
|
has_many :output_arcs, -> { where(direction: :out) },
|
15
19
|
class_name: "FlowCore::Arc", inverse_of: :transition, dependent: :destroy
|
16
20
|
|
17
21
|
has_many :input_places, through: :input_arcs, class_name: "FlowCore::Place", source: :place
|
22
|
+
has_many :output_places, through: :output_arcs, class_name: "FlowCore::Place", source: :place
|
18
23
|
|
19
24
|
has_one :trigger, class_name: "FlowCore::TransitionTrigger", dependent: :delete
|
20
25
|
has_many :callbacks, class_name: "FlowCore::TransitionCallback", dependent: :delete_all
|
21
26
|
|
27
|
+
enum output_token_create_strategy: {
|
28
|
+
petri_net: 0,
|
29
|
+
match_one_or_fallback: 1
|
30
|
+
}, _suffix: :strategy
|
31
|
+
|
32
|
+
enum auto_finish_strategy: {
|
33
|
+
disabled: 0,
|
34
|
+
synchronously: 1
|
35
|
+
}, _prefix: :auto_finish
|
36
|
+
|
37
|
+
accepts_nested_attributes_for :trigger
|
38
|
+
|
22
39
|
before_destroy :prevent_destroy
|
23
40
|
after_create :reset_workflow_verification
|
24
41
|
after_destroy :reset_workflow_verification
|
@@ -57,7 +74,7 @@ module FlowCore
|
|
57
74
|
end
|
58
75
|
end
|
59
76
|
|
60
|
-
def
|
77
|
+
def create_output_tokens_for(task)
|
61
78
|
instance = task.instance
|
62
79
|
arcs = output_arcs.includes(:place, :guards).to_a
|
63
80
|
|
@@ -68,12 +85,18 @@ module FlowCore
|
|
68
85
|
return
|
69
86
|
end
|
70
87
|
|
71
|
-
|
88
|
+
unless end_arc.fallback_arc?
|
89
|
+
arcs.delete(end_arc)
|
90
|
+
end
|
72
91
|
end
|
73
92
|
|
74
|
-
candidate_arcs =
|
75
|
-
|
76
|
-
|
93
|
+
candidate_arcs =
|
94
|
+
case output_token_create_strategy
|
95
|
+
when "match_one_or_fallback"
|
96
|
+
find_output_arcs_with_match_one_or_fallback_strategy(arcs, task)
|
97
|
+
else
|
98
|
+
find_output_arcs_with_petri_net_strategy(arcs, task)
|
99
|
+
end
|
77
100
|
|
78
101
|
if candidate_arcs.empty?
|
79
102
|
# TODO: find a better way
|
@@ -127,6 +150,21 @@ module FlowCore
|
|
127
150
|
|
128
151
|
private
|
129
152
|
|
153
|
+
def find_output_arcs_with_petri_net_strategy(arcs, task)
|
154
|
+
arcs.select do |arc|
|
155
|
+
arc.guards.empty? || arc.guards.map { |guard| guard.permit? task }.reduce(&:&)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def find_output_arcs_with_match_one_or_fallback_strategy(arcs, task)
|
160
|
+
fallback_arc = arcs.find(&:fallback_arc?)
|
161
|
+
candidate_arcs = arcs.select do |arc|
|
162
|
+
!arc.fallback_arc? && (arc.guards.empty? || arc.guards.map { |guard| guard.permit? task }.reduce(&:&))
|
163
|
+
end
|
164
|
+
|
165
|
+
[candidate_arcs.first || fallback_arc]
|
166
|
+
end
|
167
|
+
|
130
168
|
def reset_workflow_verification
|
131
169
|
workflow.reset_workflow_verification!
|
132
170
|
end
|
@@ -4,11 +4,28 @@ module FlowCore
|
|
4
4
|
class TransitionCallback < FlowCore::ApplicationRecord
|
5
5
|
self.table_name = "flow_core_transition_callbacks"
|
6
6
|
|
7
|
-
belongs_to :workflow, class_name: "FlowCore::Workflow"
|
8
|
-
belongs_to :transition, class_name: "FlowCore::Transition"
|
7
|
+
belongs_to :workflow, class_name: "FlowCore::Workflow", optional: true
|
8
|
+
belongs_to :transition, class_name: "FlowCore::Transition", optional: true
|
9
|
+
|
10
|
+
belongs_to :pipeline, class_name: "FlowCore::Pipeline", optional: true
|
11
|
+
belongs_to :step, class_name: "FlowCore::Step", optional: true
|
12
|
+
|
13
|
+
validates :transition,
|
14
|
+
presence: true,
|
15
|
+
if: ->(r) { r.workflow }
|
16
|
+
validates :step,
|
17
|
+
presence: true,
|
18
|
+
if: ->(r) { r.pipeline }
|
19
|
+
validates :workflow,
|
20
|
+
presence: true,
|
21
|
+
if: ->(r) { !r.pipeline }
|
22
|
+
validates :pipeline,
|
23
|
+
presence: true,
|
24
|
+
if: ->(r) { !r.workflow }
|
9
25
|
|
10
26
|
before_validation do
|
11
27
|
self.workflow ||= transition&.workflow
|
28
|
+
self.pipeline ||= step&.pipeline
|
12
29
|
end
|
13
30
|
|
14
31
|
def configurable?
|
@@ -4,11 +4,28 @@ module FlowCore
|
|
4
4
|
class TransitionTrigger < FlowCore::ApplicationRecord
|
5
5
|
self.table_name = "flow_core_transition_triggers"
|
6
6
|
|
7
|
-
belongs_to :workflow, class_name: "FlowCore::Workflow"
|
8
|
-
belongs_to :transition, class_name: "FlowCore::Transition"
|
7
|
+
belongs_to :workflow, class_name: "FlowCore::Workflow", optional: true
|
8
|
+
belongs_to :transition, class_name: "FlowCore::Transition", optional: true
|
9
|
+
|
10
|
+
belongs_to :pipeline, class_name: "FlowCore::Pipeline", optional: true
|
11
|
+
belongs_to :step, class_name: "FlowCore::Step", optional: true
|
12
|
+
|
13
|
+
validates :transition,
|
14
|
+
presence: true,
|
15
|
+
if: ->(r) { r.workflow }
|
16
|
+
validates :step,
|
17
|
+
presence: true,
|
18
|
+
if: ->(r) { r.pipeline }
|
19
|
+
validates :workflow,
|
20
|
+
presence: true,
|
21
|
+
if: ->(r) { !r.pipeline }
|
22
|
+
validates :pipeline,
|
23
|
+
presence: true,
|
24
|
+
if: ->(r) { !r.workflow }
|
9
25
|
|
10
26
|
before_validation do
|
11
27
|
self.workflow ||= transition&.workflow
|
28
|
+
self.pipeline ||= step&.pipeline
|
12
29
|
end
|
13
30
|
|
14
31
|
def configurable?
|
@@ -6,6 +6,10 @@ module FlowCore
|
|
6
6
|
|
7
7
|
FORBIDDEN_ATTRIBUTES = %i[verified verified_at created_at updated_at].freeze
|
8
8
|
|
9
|
+
belongs_to :generated_by,
|
10
|
+
class_name: "FlowCore::Pipeline", foreign_key: :generated_by_pipeline_id,
|
11
|
+
inverse_of: :generated_workflows, optional: true
|
12
|
+
|
9
13
|
has_many :instances, class_name: "FlowCore::Instance", dependent: :destroy
|
10
14
|
|
11
15
|
has_many :arcs, class_name: "FlowCore::Arc", dependent: :destroy
|
@@ -53,10 +57,10 @@ module FlowCore
|
|
53
57
|
violations.clear
|
54
58
|
|
55
59
|
unless start_place
|
56
|
-
violations.add(
|
60
|
+
violations.add(self, :no_start_place)
|
57
61
|
end
|
58
62
|
unless end_place
|
59
|
-
violations.add(
|
63
|
+
violations.add(self, :no_end_place)
|
60
64
|
end
|
61
65
|
|
62
66
|
return false unless start_place && end_place
|
@@ -69,7 +73,7 @@ module FlowCore
|
|
69
73
|
end_place_code = "P_#{end_place.id}"
|
70
74
|
|
71
75
|
unless rgl.path?(start_place_code, end_place_code)
|
72
|
-
violations.add
|
76
|
+
violations.add end_place, :unreachable
|
73
77
|
end
|
74
78
|
|
75
79
|
places.find_each do |p|
|
@@ -119,15 +123,18 @@ module FlowCore
|
|
119
123
|
|
120
124
|
def verify!
|
121
125
|
update! verified: verify?, verified_at: Time.zone.now
|
122
|
-
|
126
|
+
verified
|
123
127
|
end
|
124
128
|
|
125
129
|
def reset_workflow_verification!
|
130
|
+
return unless verified?
|
131
|
+
|
126
132
|
update! verified: false, verified_at: nil
|
127
133
|
end
|
128
134
|
|
129
135
|
def fork
|
130
136
|
new_workflow = dup
|
137
|
+
|
131
138
|
transaction do
|
132
139
|
yield new_workflow if block_given?
|
133
140
|
new_workflow.save!
|