petri_flow 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b75f2ac3288f2b169d4364a8aaa11586497d710477ecabe92adb6af2ffbf7376
4
- data.tar.gz: 3a12e23472cc059ba4437ee1535d216b4df290f64d1de05c54a8395a648c5246
3
+ metadata.gz: 6b2bb56ad2f157e6a7fe5d3cbee2bfab0e3fcef64f9137bd709ac37ea27949b9
4
+ data.tar.gz: c2721a5074ba1fac6462ad95bef1012c7f00bfb74ff61a1eb6ae284f97c6b013
5
5
  SHA512:
6
- metadata.gz: 6aad07e5b69eb1ed0046afaa59f9679a3f77b259fa7410533eb2939883b43da7adf7518fe4b4d17e1d3f3f3b40feca8d12c954d9c837ca9c052bf1af84385942
7
- data.tar.gz: e278b0c46d95a0b7744ef457ebac2d8f8f56d5ac943ff009264086490b762378cfb37ea3e7c5b64362d7fe09d674374f48bb0eb4008f90d7aa732343cd1cd4f0
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.
@@ -64,7 +64,8 @@ module Wf
64
64
  :assignment_callback,
65
65
  :unassignment_callback,
66
66
  :notification_callback,
67
- :deadline_callback
67
+ :deadline_callback,
68
+ :sub_workflow_id
68
69
  )
69
70
  end
70
71
  end
@@ -58,9 +58,13 @@ module Wf
58
58
 
59
59
  def finish_and_redirect
60
60
  if @workitem.case.finished?
61
- redirect_to workflow_case_path(@workitem.workflow, @workitem.case), notice: "workitem is done, and the case is finished."
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(Wf::Workitem.last.case.workitems.enabled.first), notice: "workitem is done, and goto next fireable workitem."
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
 
@@ -4,19 +4,21 @@
4
4
  #
5
5
  # Table name: wf_cases
6
6
  #
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
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
@@ -4,17 +4,16 @@
4
4
  #
5
5
  # Table name: wf_guards
6
6
  #
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
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
@@ -24,5 +24,9 @@ module Wf
24
24
  normal: 1,
25
25
  end: 2
26
26
  }
27
+
28
+ def graph_id
29
+ "#{name}/#{id}"
30
+ end
27
31
  end
28
32
  end
@@ -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
@@ -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.name,
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.name, label: t.name, shape: :box, style: :filled, fillcolor: "#d6ddfa", color: "#d6ddfa",
120
- fontcolor: "#2c50ed", fontname: fontfamily,
121
- href: Wf::Engine.routes.url_helpers.edit_workflow_transition_path(self, t))
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|
@@ -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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RemoveUnusedColumn < ActiveRecord::Migration[6.0]
4
+ def change
5
+ remove_column :wf_guards, :target_attr_name
6
+ end
7
+ end
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wf
4
- VERSION = "0.1.7"
4
+ VERSION = "0.1.8"
5
5
  end
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.7
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-18 00:00:00.000000000 Z
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