flow_core 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 +255 -0
- data/Rakefile +29 -0
- data/app/models/flow_core/application_record.rb +7 -0
- data/app/models/flow_core/arc.rb +43 -0
- data/app/models/flow_core/arc_guard.rb +18 -0
- data/app/models/flow_core/end_place.rb +17 -0
- data/app/models/flow_core/instance.rb +115 -0
- data/app/models/flow_core/place.rb +63 -0
- data/app/models/flow_core/start_place.rb +17 -0
- data/app/models/flow_core/task.rb +197 -0
- data/app/models/flow_core/token.rb +105 -0
- data/app/models/flow_core/transition.rb +143 -0
- data/app/models/flow_core/transition_callback.rb +24 -0
- data/app/models/flow_core/transition_trigger.rb +24 -0
- data/app/models/flow_core/workflow.rb +131 -0
- data/config/routes.rb +4 -0
- data/db/migrate/20200130200532_create_flow_core_tables.rb +151 -0
- data/lib/flow_core.rb +25 -0
- data/lib/flow_core/arc_guardable.rb +15 -0
- data/lib/flow_core/definition.rb +17 -0
- data/lib/flow_core/definition/callback.rb +33 -0
- data/lib/flow_core/definition/guard.rb +33 -0
- data/lib/flow_core/definition/net.rb +111 -0
- data/lib/flow_core/definition/place.rb +33 -0
- data/lib/flow_core/definition/transition.rb +107 -0
- data/lib/flow_core/definition/trigger.rb +33 -0
- data/lib/flow_core/engine.rb +6 -0
- data/lib/flow_core/errors.rb +14 -0
- data/lib/flow_core/locale/en.yml +26 -0
- data/lib/flow_core/task_executable.rb +34 -0
- data/lib/flow_core/transition_callbackable.rb +33 -0
- data/lib/flow_core/transition_triggerable.rb +27 -0
- data/lib/flow_core/version.rb +5 -0
- data/lib/flow_core/violations.rb +253 -0
- data/lib/tasks/flow_core_tasks.rake +6 -0
- metadata +123 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore
|
4
|
+
class TransitionCallback < FlowCore::ApplicationRecord
|
5
|
+
self.table_name = "flow_core_transition_callbacks"
|
6
|
+
|
7
|
+
belongs_to :workflow, class_name: "FlowCore::Workflow"
|
8
|
+
belongs_to :transition, class_name: "FlowCore::Transition"
|
9
|
+
|
10
|
+
before_validation do
|
11
|
+
self.workflow ||= transition&.workflow
|
12
|
+
end
|
13
|
+
|
14
|
+
def configurable?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def type_key
|
19
|
+
self.class.to_s.split("::").last.underscore
|
20
|
+
end
|
21
|
+
|
22
|
+
include FlowCore::TransitionCallbackable
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore
|
4
|
+
class TransitionTrigger < FlowCore::ApplicationRecord
|
5
|
+
self.table_name = "flow_core_transition_triggers"
|
6
|
+
|
7
|
+
belongs_to :workflow, class_name: "FlowCore::Workflow"
|
8
|
+
belongs_to :transition, class_name: "FlowCore::Transition"
|
9
|
+
|
10
|
+
before_validation do
|
11
|
+
self.workflow ||= transition&.workflow
|
12
|
+
end
|
13
|
+
|
14
|
+
def configurable?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def type_key
|
19
|
+
self.class.to_s.split("::").last.underscore
|
20
|
+
end
|
21
|
+
|
22
|
+
include FlowCore::TransitionTriggerable
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore
|
4
|
+
class Workflow < FlowCore::ApplicationRecord
|
5
|
+
self.table_name = "flow_core_workflows"
|
6
|
+
|
7
|
+
FORBIDDEN_ATTRIBUTES = %i[verified verified_at created_at updated_at].freeze
|
8
|
+
|
9
|
+
has_many :instances, class_name: "FlowCore::Instance", dependent: :destroy
|
10
|
+
|
11
|
+
has_many :arcs, class_name: "FlowCore::Arc", dependent: :delete_all
|
12
|
+
has_many :places, class_name: "FlowCore::Place", dependent: :delete_all
|
13
|
+
has_many :transitions, class_name: "FlowCore::Transition", dependent: :delete_all
|
14
|
+
|
15
|
+
has_one :start_place, class_name: "FlowCore::StartPlace", dependent: :delete
|
16
|
+
has_one :end_place, class_name: "FlowCore::EndPlace", dependent: :delete
|
17
|
+
|
18
|
+
def create_instance!(attributes = {})
|
19
|
+
unless verified?
|
20
|
+
raise FlowCore::UnverifiedWorkflow, "Workflow##{id} didn't do soundness check yet."
|
21
|
+
end
|
22
|
+
|
23
|
+
instances.create! attributes.with_indifferent_access.except(FlowCore::Instance::FORBIDDEN_ATTRIBUTES)
|
24
|
+
end
|
25
|
+
|
26
|
+
def invalid?
|
27
|
+
!verified?
|
28
|
+
end
|
29
|
+
|
30
|
+
def verify?
|
31
|
+
violations.clear
|
32
|
+
|
33
|
+
unless start_place
|
34
|
+
violations.add(:start_place, :presence)
|
35
|
+
end
|
36
|
+
unless end_place
|
37
|
+
violations.add(:end_place, :presence)
|
38
|
+
end
|
39
|
+
|
40
|
+
return false unless start_place && end_place
|
41
|
+
|
42
|
+
# TODO: Naive implementation for now, Help wanted!
|
43
|
+
|
44
|
+
rgl = to_rgl
|
45
|
+
|
46
|
+
start_place_code = "P_#{start_place.id}"
|
47
|
+
end_place_code = "P_#{end_place.id}"
|
48
|
+
|
49
|
+
unless rgl.path?(start_place_code, end_place_code)
|
50
|
+
violations.add :end_place, :unreachable
|
51
|
+
end
|
52
|
+
|
53
|
+
places.find_each do |p|
|
54
|
+
next if p == start_place
|
55
|
+
next if p == end_place
|
56
|
+
|
57
|
+
place_code = "P_#{p.id}"
|
58
|
+
|
59
|
+
unless rgl.path?(start_place_code, place_code)
|
60
|
+
violations.add p, :unreachable
|
61
|
+
end
|
62
|
+
|
63
|
+
unless rgl.path?(place_code, end_place_code)
|
64
|
+
violations.add p, :impassable
|
65
|
+
end
|
66
|
+
end
|
67
|
+
transitions.includes(:trigger).find_each do |t|
|
68
|
+
transition_code = "T_#{t.id}"
|
69
|
+
|
70
|
+
unless rgl.path?(start_place_code, transition_code)
|
71
|
+
violations.add t, :unreachable
|
72
|
+
end
|
73
|
+
|
74
|
+
unless rgl.path?(transition_code, end_place_code)
|
75
|
+
violations.add t, :impassable
|
76
|
+
end
|
77
|
+
|
78
|
+
t.verify violations: violations
|
79
|
+
end
|
80
|
+
|
81
|
+
violations.empty?
|
82
|
+
end
|
83
|
+
|
84
|
+
def violations
|
85
|
+
@violations ||= FlowCore::Violations.new
|
86
|
+
end
|
87
|
+
|
88
|
+
def verify_status
|
89
|
+
if verified_at.blank?
|
90
|
+
:unverified
|
91
|
+
elsif verified?
|
92
|
+
:verified
|
93
|
+
else
|
94
|
+
:invalid
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def verify!
|
99
|
+
update! verified: verify?, verified_at: Time.zone.now
|
100
|
+
violations.empty?
|
101
|
+
end
|
102
|
+
|
103
|
+
def reset_workflow_verification!
|
104
|
+
update! verified: false, verified_at: nil
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def to_rgl
|
110
|
+
graph = RGL::DirectedAdjacencyGraph.new
|
111
|
+
|
112
|
+
places.find_each do |p|
|
113
|
+
graph.add_vertex "P_#{p.id}"
|
114
|
+
end
|
115
|
+
|
116
|
+
transitions.find_each do |t|
|
117
|
+
graph.add_vertex "T_#{t.id}"
|
118
|
+
end
|
119
|
+
|
120
|
+
arcs.find_each do |arc|
|
121
|
+
if arc.in?
|
122
|
+
graph.add_edge "P_#{arc.place_id}", "T_#{arc.transition_id}"
|
123
|
+
else
|
124
|
+
graph.add_edge "T_#{arc.transition_id}", "P_#{arc.place_id}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
graph
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateFlowCoreTables < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
create_table :flow_core_workflows do |t|
|
6
|
+
t.string :name, null: false
|
7
|
+
t.string :tag
|
8
|
+
|
9
|
+
t.integer :verified, null: false, default: false
|
10
|
+
t.datetime :verified_at
|
11
|
+
|
12
|
+
t.string :type
|
13
|
+
|
14
|
+
t.timestamps
|
15
|
+
end
|
16
|
+
|
17
|
+
create_table :flow_core_places do |t|
|
18
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
19
|
+
|
20
|
+
t.string :name
|
21
|
+
t.string :tag
|
22
|
+
|
23
|
+
t.string :type
|
24
|
+
|
25
|
+
t.timestamps
|
26
|
+
end
|
27
|
+
|
28
|
+
create_table :flow_core_transitions do |t|
|
29
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
30
|
+
|
31
|
+
t.string :name
|
32
|
+
t.string :tag
|
33
|
+
|
34
|
+
t.timestamps
|
35
|
+
end
|
36
|
+
|
37
|
+
create_table :flow_core_transition_callbacks do |t|
|
38
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
39
|
+
t.references :transition, null: false, foreign_key: { to_table: :flow_core_transitions }
|
40
|
+
|
41
|
+
t.string :configuration
|
42
|
+
t.string :type
|
43
|
+
|
44
|
+
t.timestamps
|
45
|
+
end
|
46
|
+
|
47
|
+
create_table :flow_core_transition_triggers do |t|
|
48
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
49
|
+
t.references :transition, null: false, foreign_key: { to_table: :flow_core_transitions }
|
50
|
+
|
51
|
+
t.text :configuration
|
52
|
+
t.string :type
|
53
|
+
|
54
|
+
t.timestamps
|
55
|
+
end
|
56
|
+
|
57
|
+
create_table :flow_core_arcs do |t|
|
58
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
59
|
+
t.references :transition, null: false, foreign_key: { to_table: :flow_core_transitions }
|
60
|
+
t.references :place, null: false, foreign_key: { to_table: :flow_core_places }
|
61
|
+
|
62
|
+
t.integer :direction, null: false, default: 0, comment: "0-in, 1-out"
|
63
|
+
|
64
|
+
t.timestamps
|
65
|
+
end
|
66
|
+
|
67
|
+
create_table :flow_core_arc_guards do |t|
|
68
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
69
|
+
t.references :arc, null: false, foreign_key: { to_table: :flow_core_arcs }
|
70
|
+
|
71
|
+
t.text :configuration
|
72
|
+
t.string :type
|
73
|
+
|
74
|
+
t.timestamps
|
75
|
+
end
|
76
|
+
|
77
|
+
create_table :flow_core_instances do |t|
|
78
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
79
|
+
|
80
|
+
t.string :tag, index: { unique: true }
|
81
|
+
|
82
|
+
t.integer :stage, default: 0, comment: "0-created, 1-activated, 2-canceled, 3-finished, 4-terminated"
|
83
|
+
t.datetime :activated_at
|
84
|
+
t.datetime :finished_at
|
85
|
+
t.datetime :canceled_at
|
86
|
+
t.datetime :terminated_at
|
87
|
+
|
88
|
+
t.string :terminated_reason
|
89
|
+
|
90
|
+
t.datetime :errored_at
|
91
|
+
t.datetime :rescued_at
|
92
|
+
|
93
|
+
t.datetime :suspended_at
|
94
|
+
t.datetime :resumed_at
|
95
|
+
|
96
|
+
t.text :payload
|
97
|
+
|
98
|
+
t.timestamps
|
99
|
+
end
|
100
|
+
|
101
|
+
create_table :flow_core_tokens do |t|
|
102
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
103
|
+
t.references :instance, null: false, foreign_key: { to_table: :flow_core_instances }
|
104
|
+
t.references :place, null: false, foreign_key: { to_table: :flow_core_places }
|
105
|
+
|
106
|
+
t.integer :stage, default: 0, comment: "0-free, 1-locked, 11-consumed, 12-terminated"
|
107
|
+
t.datetime :locked_at
|
108
|
+
t.datetime :consumed_at
|
109
|
+
t.datetime :terminated_at
|
110
|
+
|
111
|
+
t.references :created_by_task, foreign_key: { to_table: :flow_core_tasks }
|
112
|
+
t.references :consumed_by_task, foreign_key: { to_table: :flow_core_tasks }
|
113
|
+
|
114
|
+
t.boolean :task_created, null: false, default: false
|
115
|
+
|
116
|
+
t.timestamps
|
117
|
+
end
|
118
|
+
|
119
|
+
create_table :flow_core_tasks do |t|
|
120
|
+
t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
|
121
|
+
t.references :instance, null: false, foreign_key: { to_table: :flow_core_instances }
|
122
|
+
t.references :transition, null: false, foreign_key: { to_table: :flow_core_transitions }
|
123
|
+
|
124
|
+
t.string :tag, index: { unique: true }
|
125
|
+
|
126
|
+
t.references :created_by_token, foreign_key: { to_table: :flow_core_tokens }
|
127
|
+
|
128
|
+
t.integer :stage, default: 0, comment: "0-created, 1-enabled, 11-finished, 12-terminated"
|
129
|
+
t.datetime :enabled_at
|
130
|
+
t.datetime :finished_at
|
131
|
+
t.datetime :terminated_at
|
132
|
+
|
133
|
+
t.string :terminate_reason
|
134
|
+
|
135
|
+
t.datetime :errored_at
|
136
|
+
t.datetime :rescued_at
|
137
|
+
t.string :error_reason
|
138
|
+
|
139
|
+
t.datetime :suspended_at
|
140
|
+
t.datetime :resumed_at
|
141
|
+
|
142
|
+
t.boolean :output_token_created, null: false, default: false
|
143
|
+
|
144
|
+
t.references :executable, polymorphic: true
|
145
|
+
|
146
|
+
t.text :payload
|
147
|
+
|
148
|
+
t.timestamps
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/lib/flow_core.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rgl/adjacency"
|
4
|
+
require "rgl/dijkstra"
|
5
|
+
require "rgl/topsort"
|
6
|
+
require "rgl/traversal"
|
7
|
+
require "rgl/path"
|
8
|
+
|
9
|
+
require "flow_core/engine"
|
10
|
+
|
11
|
+
require "flow_core/errors"
|
12
|
+
require "flow_core/arc_guardable"
|
13
|
+
require "flow_core/transition_triggerable"
|
14
|
+
require "flow_core/transition_callbackable"
|
15
|
+
require "flow_core/task_executable"
|
16
|
+
|
17
|
+
require "flow_core/definition"
|
18
|
+
require "flow_core/violations"
|
19
|
+
|
20
|
+
ActiveSupport.on_load(:i18n) do
|
21
|
+
I18n.load_path << File.expand_path("flow_core/locale/en.yml", __dir__)
|
22
|
+
end
|
23
|
+
|
24
|
+
module FlowCore
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "flow_core/definition/place"
|
4
|
+
require "flow_core/definition/transition"
|
5
|
+
require "flow_core/definition/net"
|
6
|
+
require "flow_core/definition/trigger"
|
7
|
+
require "flow_core/definition/guard"
|
8
|
+
require "flow_core/definition/callback"
|
9
|
+
|
10
|
+
module FlowCore::Definition
|
11
|
+
class << self
|
12
|
+
def build(attributes = {}, &block)
|
13
|
+
FlowCore::Definition::Net.new(attributes, &block)
|
14
|
+
end
|
15
|
+
alias new build
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore::Definition
|
4
|
+
class Callback
|
5
|
+
def initialize(attributes)
|
6
|
+
constant_or_klass = attributes.is_a?(Hash) ? attributes.delete(:type) : attributes
|
7
|
+
@klass =
|
8
|
+
if constant_or_klass.is_a? String
|
9
|
+
constant_or_klass.safe_constantize
|
10
|
+
else
|
11
|
+
constant_or_klass
|
12
|
+
end
|
13
|
+
unless @klass && @klass < FlowCore::TransitionCallback
|
14
|
+
raise TypeError, "First argument expect `FlowCore::TransitionCallback` subclass or its constant name - #{constant_or_klass}"
|
15
|
+
end
|
16
|
+
|
17
|
+
@configuration = attributes.is_a?(Hash) ? attributes : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def compile
|
21
|
+
if @configuration&.any?
|
22
|
+
{
|
23
|
+
type: @klass.to_s,
|
24
|
+
configuration: @configuration
|
25
|
+
}
|
26
|
+
else
|
27
|
+
{
|
28
|
+
type: @klass.to_s
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore::Definition
|
4
|
+
class Guard
|
5
|
+
def initialize(attributes)
|
6
|
+
constant_or_klass = attributes.is_a?(Hash) ? attributes.delete(:type) : attributes
|
7
|
+
@klass =
|
8
|
+
if constant_or_klass.is_a? String
|
9
|
+
constant_or_klass.safe_constantize
|
10
|
+
else
|
11
|
+
constant_or_klass
|
12
|
+
end
|
13
|
+
unless @klass && @klass < FlowCore::ArcGuard
|
14
|
+
raise TypeError, "First argument expect `FlowCore::TransitionTrigger` subclass or its constant name - #{constant_or_klass}"
|
15
|
+
end
|
16
|
+
|
17
|
+
@configuration = attributes.is_a?(Hash) ? attributes : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def compile
|
21
|
+
if @configuration&.any?
|
22
|
+
{
|
23
|
+
type: @klass.to_s,
|
24
|
+
configuration: @configuration
|
25
|
+
}
|
26
|
+
else
|
27
|
+
{
|
28
|
+
type: @klass.to_s
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|