flow_core 0.0.3 → 0.0.5

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.
@@ -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!