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.
@@ -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
- with_transaction_returning_status do
84
- input_free_tokens.each(&:lock!)
85
- update! stage: :enabled, enabled_at: Time.zone.now
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
- transition.on_task_enable(self)
88
- workflow.on_instance_task_enable(self)
88
+ transition.on_task_enable(self)
89
+ workflow.on_instance_task_enable(self)
89
90
 
90
- true
91
- end
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.create_tokens_for_output(task: self)
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: :delete_all
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 create_tokens_for_output(task:)
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
- arcs.delete(end_arc)
88
+ unless end_arc.fallback_arc?
89
+ arcs.delete(end_arc)
90
+ end
72
91
  end
73
92
 
74
- candidate_arcs = arcs.select do |arc|
75
- arc.guards.empty? || arc.guards.map { |guard| guard.permit? task }.reduce(&:&)
76
- end
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(:start_place, :presence)
60
+ violations.add(self, :no_start_place)
57
61
  end
58
62
  unless end_place
59
- violations.add(:end_place, :presence)
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 :end_place, :unreachable
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
- violations.empty?
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!