petri_flow 0.1.7 → 0.1.8
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.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/app/controllers/wf/transitions_controller.rb +2 -1
- data/app/controllers/wf/workitems_controller.rb +6 -2
- data/app/models/wf/case.rb +9 -7
- data/app/models/wf/case_command/enable_transitions.rb +4 -0
- data/app/models/wf/case_command/finished_p.rb +6 -1
- data/app/models/wf/case_command/new.rb +4 -3
- data/app/models/wf/guard.rb +10 -11
- data/app/models/wf/place.rb +4 -0
- data/app/models/wf/transition.rb +16 -0
- data/app/models/wf/workflow.rb +19 -6
- data/app/models/wf/workitem.rb +1 -0
- data/app/views/wf/transitions/_form.html.erb +5 -0
- data/app/views/wf/workflows/show.html.erb +8 -0
- data/db/migrate/20200220070839_remove_unused_column.rb +7 -0
- data/db/migrate/20200220072512_add_sub_workflow.rb +8 -0
- data/lib/wf/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6b2bb56ad2f157e6a7fe5d3cbee2bfab0e3fcef64f9137bd709ac37ea27949b9
|
4
|
+
data.tar.gz: c2721a5074ba1fac6462ad95bef1012c7f00bfb74ff61a1eb6ae284f97c6b013
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f0f933aa496d45610048de363e7fd189586dab4af85bfb012e2b93a55703104163f0183655f61a4a8c60d7f3a20c1f9c69c64d451eb601c3da73e68e0f7b4d1
|
7
|
+
data.tar.gz: 78f35f561c65b366a7c21e20420d9235813b1368f1d06e0e87a35e888a0f8755317272854dd96224acae9ca42626635d44a1551d9b2e9853411348f45a87f2e1
|
data/README.md
CHANGED
@@ -8,6 +8,7 @@ Workflow engine for Rails.
|
|
8
8
|
* Simple web admin for workflow definition and case management.
|
9
9
|
* Build-in simple dynamic form.
|
10
10
|
* Replaceable dynamic form.
|
11
|
+
* Support sub workflow.
|
11
12
|
* Graph screen for workflow definition.
|
12
13
|
* Graph screen for case and token migration.
|
13
14
|
* Powerful guard expression.
|
@@ -58,9 +58,13 @@ module Wf
|
|
58
58
|
|
59
59
|
def finish_and_redirect
|
60
60
|
if @workitem.case.finished?
|
61
|
-
|
61
|
+
if started_by = @workitem.case.started_by_workitem
|
62
|
+
redirect_to workflow_case_path(started_by.workflow, started_by.case), notice: "workitem is done, and goto parent case."
|
63
|
+
else
|
64
|
+
redirect_to workflow_case_path(@workitem.workflow, @workitem.case), notice: "workitem is done, and the case is finished."
|
65
|
+
end
|
62
66
|
else
|
63
|
-
redirect_to workitem_path(
|
67
|
+
redirect_to workitem_path(@workitem.case.workitems.enabled.first), notice: "workitem is done, and goto next fireable workitem."
|
64
68
|
end
|
65
69
|
end
|
66
70
|
|
data/app/models/wf/case.rb
CHANGED
@@ -4,19 +4,21 @@
|
|
4
4
|
#
|
5
5
|
# Table name: wf_cases
|
6
6
|
#
|
7
|
-
# id
|
8
|
-
# workflow_id
|
9
|
-
# targetable_type
|
10
|
-
# targetable_id
|
11
|
-
# state
|
12
|
-
# created_at
|
13
|
-
# updated_at
|
7
|
+
# id :integer not null, primary key
|
8
|
+
# workflow_id :integer
|
9
|
+
# targetable_type :string
|
10
|
+
# targetable_id :string
|
11
|
+
# state :integer default("0")
|
12
|
+
# created_at :datetime not null
|
13
|
+
# updated_at :datetime not null
|
14
|
+
# started_by_workitem_id :integer
|
14
15
|
#
|
15
16
|
|
16
17
|
module Wf
|
17
18
|
class Case < ApplicationRecord
|
18
19
|
belongs_to :workflow
|
19
20
|
belongs_to :targetable, optional: true, polymorphic: true
|
21
|
+
belongs_to :started_by_workitem, optional: true, class_name: "Wf::Workitem"
|
20
22
|
has_many :workitems
|
21
23
|
has_many :tokens
|
22
24
|
has_many :case_assignments
|
@@ -26,6 +26,10 @@ module Wf::CaseCommand
|
|
26
26
|
Wf::FireTimedWorkitemJob.set(wait: transition.trigger_limit.minutes).perform_later(workitem.id) if trigger_time
|
27
27
|
SetWorkitemAssignments.call(workitem)
|
28
28
|
workitem.transition.unassignment_callback.constantize.new(workitem.id).perform_now if workitem.workitem_assignments.count == 0
|
29
|
+
if sub_workflow = transition.sub_workflow
|
30
|
+
sub_case = Wf::CaseCommand::New.call(sub_workflow, nil, workitem).result
|
31
|
+
Wf::CaseCommand::StartCase.call(sub_case)
|
32
|
+
end
|
29
33
|
end
|
30
34
|
end
|
31
35
|
end
|
@@ -20,7 +20,12 @@ module Wf::CaseCommand
|
|
20
20
|
raise("The workflow net is misconstructed: Some parallel executions have not finished.") if free_and_locked_token_num > 1
|
21
21
|
|
22
22
|
ConsumeToken.call(wf_case, end_place)
|
23
|
-
wf_case.finished
|
23
|
+
unless wf_case.finished?
|
24
|
+
wf_case.finished!
|
25
|
+
if started_by_workitem = wf_case.started_by_workitem
|
26
|
+
Wf::CaseCommand::FinishWorkitem.call(started_by_workitem)
|
27
|
+
end
|
28
|
+
end
|
24
29
|
true
|
25
30
|
end
|
26
31
|
end
|
@@ -3,14 +3,15 @@
|
|
3
3
|
module Wf::CaseCommand
|
4
4
|
class New
|
5
5
|
prepend SimpleCommand
|
6
|
-
attr_reader :workflow, :target
|
7
|
-
def initialize(workflow, target = nil)
|
6
|
+
attr_reader :workflow, :target, :started_by
|
7
|
+
def initialize(workflow, target = nil, started_by = nil)
|
8
8
|
@workflow = workflow
|
9
9
|
@target = target
|
10
|
+
@started_by = started_by
|
10
11
|
end
|
11
12
|
|
12
13
|
def call
|
13
|
-
wf_case = workflow.cases.create!(targetable: target, state: :created)
|
14
|
+
wf_case = workflow.cases.create!(targetable: target, started_by_workitem: started_by, state: :created)
|
14
15
|
wf_case
|
15
16
|
end
|
16
17
|
end
|
data/app/models/wf/guard.rb
CHANGED
@@ -4,17 +4,16 @@
|
|
4
4
|
#
|
5
5
|
# Table name: wf_guards
|
6
6
|
#
|
7
|
-
# id
|
8
|
-
# arc_id
|
9
|
-
# workflow_id
|
10
|
-
# fieldable_type
|
11
|
-
# fieldable_id
|
12
|
-
# op
|
13
|
-
# value
|
14
|
-
# exp
|
15
|
-
# created_at
|
16
|
-
# updated_at
|
17
|
-
# target_attr_name :string
|
7
|
+
# id :integer not null, primary key
|
8
|
+
# arc_id :integer
|
9
|
+
# workflow_id :integer
|
10
|
+
# fieldable_type :string
|
11
|
+
# fieldable_id :string
|
12
|
+
# op :string
|
13
|
+
# value :string
|
14
|
+
# exp :string
|
15
|
+
# created_at :datetime not null
|
16
|
+
# updated_at :datetime not null
|
18
17
|
#
|
19
18
|
|
20
19
|
module Wf
|
data/app/models/wf/place.rb
CHANGED
data/app/models/wf/transition.rb
CHANGED
@@ -23,6 +23,7 @@
|
|
23
23
|
# assignment_callback :string default("Wf::Callbacks::AssignmentDefault")
|
24
24
|
# unassignment_callback :string default("Wf::Callbacks::UnassignmentDefault")
|
25
25
|
# form_type :string default("Wf::Form")
|
26
|
+
# sub_workflow_id :integer
|
26
27
|
#
|
27
28
|
|
28
29
|
module Wf
|
@@ -33,6 +34,7 @@ module Wf
|
|
33
34
|
has_many :static_parties, through: :transition_static_assignments, source: "party"
|
34
35
|
has_many :workitems
|
35
36
|
belongs_to :form, optional: true, polymorphic: true
|
37
|
+
belongs_to :sub_workflow, optional: true, class_name: "Wf::Workflow"
|
36
38
|
|
37
39
|
enum trigger_type: {
|
38
40
|
user: 0,
|
@@ -41,10 +43,24 @@ module Wf
|
|
41
43
|
time: 3
|
42
44
|
}
|
43
45
|
|
46
|
+
validate :validate_trigger_type_and_sub
|
47
|
+
|
48
|
+
def is_sub_workflow?
|
49
|
+
!!sub_workflow_id
|
50
|
+
end
|
51
|
+
|
44
52
|
def explicit_or_split?
|
45
53
|
arcs.out.sum(:guards_count) >= 1
|
46
54
|
end
|
47
55
|
|
48
56
|
validates :name, presence: true
|
57
|
+
|
58
|
+
def validate_trigger_type_and_sub
|
59
|
+
errors.add(:trigger_type, "sub workflow must have trigger type: automatic, message and time.") if user? && is_sub_workflow?
|
60
|
+
end
|
61
|
+
|
62
|
+
def graph_id
|
63
|
+
"#{name}/#{id}"
|
64
|
+
end
|
49
65
|
end
|
50
66
|
end
|
data/app/models/wf/workflow.rb
CHANGED
@@ -25,6 +25,8 @@ module Wf
|
|
25
25
|
|
26
26
|
validates :name, presence: true
|
27
27
|
|
28
|
+
scope :valid, -> { where(is_valid: true) }
|
29
|
+
|
28
30
|
after_save do
|
29
31
|
do_validate!
|
30
32
|
end
|
@@ -64,10 +66,11 @@ module Wf
|
|
64
66
|
end
|
65
67
|
end
|
66
68
|
|
67
|
-
def to_graph(wf_case = nil)
|
69
|
+
def to_graph(wf_case = nil, base = nil)
|
68
70
|
fontfamily = "system, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Noto Color Emoji, Segoe UI Symbol"
|
69
71
|
fontfamily_monospace = "SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace"
|
70
|
-
graph = GraphViz.new(name, type: :digraph, rankdir: "LR", splines: true, ratio: :auto)
|
72
|
+
graph = base || GraphViz.new(name, type: :digraph, rankdir: "LR", splines: true, ratio: :auto)
|
73
|
+
|
71
74
|
free_token_places = if wf_case
|
72
75
|
wf_case.tokens.free.map(&:place_id)
|
73
76
|
else
|
@@ -99,7 +102,7 @@ module Wf
|
|
99
102
|
xlabel = nil
|
100
103
|
end
|
101
104
|
|
102
|
-
pg = graph.add_nodes(p.
|
105
|
+
pg = graph.add_nodes(p.graph_id,
|
103
106
|
label: label,
|
104
107
|
xlabel: xlabel,
|
105
108
|
shape: shape,
|
@@ -116,10 +119,20 @@ module Wf
|
|
116
119
|
|
117
120
|
tg_mapping = {}
|
118
121
|
transitions.each do |t|
|
119
|
-
tg = graph.add_nodes(t.
|
120
|
-
|
121
|
-
|
122
|
+
tg = graph.add_nodes(t.graph_id, label: t.name, shape: :box, style: :filled, fillcolor: "#d6ddfa", color: "#d6ddfa",
|
123
|
+
fontcolor: "#2c50ed", fontname: fontfamily,
|
124
|
+
href: Wf::Engine.routes.url_helpers.edit_workflow_transition_path(self, t))
|
122
125
|
tg_mapping[t] = tg
|
126
|
+
# NOTICE: if sub_workflow is transition's workflow, then graph will loop infinite, this is valid for workflow definition.
|
127
|
+
next unless t.is_sub_workflow? && t.sub_workflow != t.workflow
|
128
|
+
|
129
|
+
sub_graph = graph.add_graph("cluster#{t.sub_workflow_id}", rankdir: "LR", splines: true, ratio: :auto)
|
130
|
+
sub_graph[:label] = t.sub_workflow.name
|
131
|
+
sub_graph[:style] = :dashed
|
132
|
+
sub_graph[:color] = :lightgrey
|
133
|
+
# TODO: detect related case for sub workflow.
|
134
|
+
t.sub_workflow.to_graph(nil, sub_graph)
|
135
|
+
graph.add_edges(tg, t.sub_workflow.places.start.first.graph_id, style: :dashed, dir: :both)
|
123
136
|
end
|
124
137
|
|
125
138
|
arcs.order("direction desc").each do |arc|
|
data/app/models/wf/workitem.rb
CHANGED
@@ -31,6 +31,7 @@ module Wf
|
|
31
31
|
has_many :parties, through: :workitem_assignments, source: "party"
|
32
32
|
has_many :comments
|
33
33
|
has_many :entries, class_name: Wf.entry_class.to_s
|
34
|
+
has_one :started_case, foreign_key: :started_by_workitem_id, class_name: "Wf::Case"
|
34
35
|
|
35
36
|
enum state: {
|
36
37
|
enabled: 0,
|
@@ -41,6 +41,11 @@
|
|
41
41
|
<%= f.select :trigger_type, Wf::Transition.trigger_types.keys, {}, class: "form-control custom-select", placeholder: "Trigger Type" %>
|
42
42
|
</div>
|
43
43
|
|
44
|
+
<div class="form-group">
|
45
|
+
<%= f.label :sub_workflow, class: "label" %>
|
46
|
+
<%= f.select :sub_workflow_id, options_for_select(Wf::Workflow.valid.all.map{|x| [x.name, x.id]} || []), {include_blank: 'Start a sub workflow?'}, class: "form-control custom-select", placeholder: "Sub Workflow" %>
|
47
|
+
</div>
|
48
|
+
|
44
49
|
<div class="form-group">
|
45
50
|
<%= f.label :form, class: "label" %>
|
46
51
|
<%= f.select :form, options_for_select(Wf.form_class.constantize.all.map{|x| [x.name, x.to_global_id]} || [], selected: f.object&.form&.to_global_id), {include_blank: 'user can input from custom form?'}, class: "form-control custom-select", placeholder: "Form" %>
|
@@ -118,6 +118,7 @@
|
|
118
118
|
<th scope="col">Trigger Type</th>
|
119
119
|
<th scope="col">Sort Order</th>
|
120
120
|
<th scope="col">Custom Form</th>
|
121
|
+
<th scope="col">Sub Workflow</th>
|
121
122
|
<th scope='col'> </th>
|
122
123
|
</tr>
|
123
124
|
</thead>
|
@@ -138,6 +139,13 @@
|
|
138
139
|
No Form
|
139
140
|
<% end %>
|
140
141
|
</td>
|
142
|
+
<td>
|
143
|
+
<% if transition.sub_workflow %>
|
144
|
+
<%= link_to transition.sub_workflow.name, workflow_path(transition.sub_workflow_id) %>
|
145
|
+
<% else %>
|
146
|
+
No
|
147
|
+
<% end %>
|
148
|
+
</td>
|
141
149
|
<td><%= link_to 'Edit Transition', edit_workflow_transition_path(@workflow, transition), class: 'btn btn-sm btn-link mr-2' %>
|
142
150
|
<%= link_to 'Delete Transition', [@workflow, transition], remote: true, method: :delete, data: {confirm: 'confirm?'}, class: 'btn btn-sm btn-link text-danger' %></td>
|
143
151
|
</tr>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class AddSubWorkflow < ActiveRecord::Migration[6.0]
|
4
|
+
def change
|
5
|
+
add_column :wf_transitions, :sub_workflow_id, :bigint, index: true
|
6
|
+
add_column :wf_cases, :started_by_workitem_id, :bigint, index: true, comment: "As a sub workflow instance, it is started by one workitem."
|
7
|
+
end
|
8
|
+
end
|
data/lib/wf/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: petri_flow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hooopo Wang
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-02-
|
11
|
+
date: 2020-02-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bootstrap
|
@@ -351,6 +351,8 @@ files:
|
|
351
351
|
- db/migrate/20200213085258_add_formable.rb
|
352
352
|
- db/migrate/20200213125753_add_form_id_for_entry.rb
|
353
353
|
- db/migrate/20200213130900_remove_workflow_id_from_form_related.rb
|
354
|
+
- db/migrate/20200220070839_remove_unused_column.rb
|
355
|
+
- db/migrate/20200220072512_add_sub_workflow.rb
|
354
356
|
- lib/tasks/wf_tasks.rake
|
355
357
|
- lib/wf.rb
|
356
358
|
- lib/wf/engine.rb
|