dynflow 0.1.0 → 0.2.0
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.
- data/.gitignore +6 -0
- data/.travis.yml +9 -0
- data/Gemfile +0 -10
- data/MIT-LICENSE +1 -1
- data/README.md +99 -37
- data/Rakefile +2 -6
- data/doc/images/logo.png +0 -0
- data/dynflow.gemspec +10 -1
- data/examples/generate_work_for_daemon.rb +24 -0
- data/examples/orchestrate.rb +121 -0
- data/examples/run_daemon.rb +17 -0
- data/examples/web_console.rb +29 -0
- data/lib/dynflow.rb +27 -6
- data/lib/dynflow/action.rb +185 -77
- data/lib/dynflow/action/cancellable_polling.rb +18 -0
- data/lib/dynflow/action/finalize_phase.rb +18 -0
- data/lib/dynflow/action/flow_phase.rb +44 -0
- data/lib/dynflow/action/format.rb +46 -0
- data/lib/dynflow/action/missing.rb +26 -0
- data/lib/dynflow/action/plan_phase.rb +85 -0
- data/lib/dynflow/action/polling.rb +49 -0
- data/lib/dynflow/action/presenter.rb +51 -0
- data/lib/dynflow/action/progress.rb +62 -0
- data/lib/dynflow/action/run_phase.rb +43 -0
- data/lib/dynflow/action/suspended.rb +21 -0
- data/lib/dynflow/clock.rb +133 -0
- data/lib/dynflow/daemon.rb +29 -0
- data/lib/dynflow/execution_plan.rb +285 -33
- data/lib/dynflow/execution_plan/dependency_graph.rb +29 -0
- data/lib/dynflow/execution_plan/output_reference.rb +52 -0
- data/lib/dynflow/execution_plan/steps.rb +12 -0
- data/lib/dynflow/execution_plan/steps/abstract.rb +121 -0
- data/lib/dynflow/execution_plan/steps/abstract_flow_step.rb +52 -0
- data/lib/dynflow/execution_plan/steps/error.rb +33 -0
- data/lib/dynflow/execution_plan/steps/finalize_step.rb +23 -0
- data/lib/dynflow/execution_plan/steps/plan_step.rb +81 -0
- data/lib/dynflow/execution_plan/steps/run_step.rb +21 -0
- data/lib/dynflow/executors.rb +9 -0
- data/lib/dynflow/executors/abstract.rb +32 -0
- data/lib/dynflow/executors/parallel.rb +88 -0
- data/lib/dynflow/executors/parallel/core.rb +119 -0
- data/lib/dynflow/executors/parallel/execution_plan_manager.rb +120 -0
- data/lib/dynflow/executors/parallel/flow_manager.rb +48 -0
- data/lib/dynflow/executors/parallel/pool.rb +102 -0
- data/lib/dynflow/executors/parallel/running_steps_manager.rb +63 -0
- data/lib/dynflow/executors/parallel/sequence_cursor.rb +97 -0
- data/lib/dynflow/executors/parallel/sequential_manager.rb +81 -0
- data/lib/dynflow/executors/parallel/work_queue.rb +44 -0
- data/lib/dynflow/executors/parallel/worker.rb +30 -0
- data/lib/dynflow/executors/remote_via_socket.rb +38 -0
- data/lib/dynflow/executors/remote_via_socket/core.rb +150 -0
- data/lib/dynflow/flows.rb +13 -0
- data/lib/dynflow/flows/abstract.rb +36 -0
- data/lib/dynflow/flows/abstract_composed.rb +104 -0
- data/lib/dynflow/flows/atom.rb +36 -0
- data/lib/dynflow/flows/concurrence.rb +28 -0
- data/lib/dynflow/flows/sequence.rb +13 -0
- data/lib/dynflow/future.rb +173 -0
- data/lib/dynflow/listeners.rb +7 -0
- data/lib/dynflow/listeners/abstract.rb +13 -0
- data/lib/dynflow/listeners/serialization.rb +41 -0
- data/lib/dynflow/listeners/socket.rb +88 -0
- data/lib/dynflow/logger_adapters.rb +8 -0
- data/lib/dynflow/logger_adapters/abstract.rb +30 -0
- data/lib/dynflow/logger_adapters/delegator.rb +13 -0
- data/lib/dynflow/logger_adapters/formatters.rb +8 -0
- data/lib/dynflow/logger_adapters/formatters/abstract.rb +33 -0
- data/lib/dynflow/logger_adapters/formatters/exception.rb +15 -0
- data/lib/dynflow/logger_adapters/simple.rb +59 -0
- data/lib/dynflow/micro_actor.rb +102 -0
- data/lib/dynflow/persistence.rb +53 -0
- data/lib/dynflow/persistence_adapters.rb +6 -0
- data/lib/dynflow/persistence_adapters/abstract.rb +56 -0
- data/lib/dynflow/persistence_adapters/sequel.rb +160 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/001_initial.rb +52 -0
- data/lib/dynflow/serializable.rb +66 -0
- data/lib/dynflow/simple_world.rb +18 -0
- data/lib/dynflow/stateful.rb +40 -0
- data/lib/dynflow/testing.rb +32 -0
- data/lib/dynflow/testing/assertions.rb +64 -0
- data/lib/dynflow/testing/dummy_execution_plan.rb +40 -0
- data/lib/dynflow/testing/dummy_executor.rb +29 -0
- data/lib/dynflow/testing/dummy_planned_action.rb +18 -0
- data/lib/dynflow/testing/dummy_step.rb +19 -0
- data/lib/dynflow/testing/dummy_world.rb +33 -0
- data/lib/dynflow/testing/factories.rb +83 -0
- data/lib/dynflow/testing/managed_clock.rb +23 -0
- data/lib/dynflow/testing/mimic.rb +38 -0
- data/lib/dynflow/transaction_adapters.rb +9 -0
- data/lib/dynflow/transaction_adapters/abstract.rb +26 -0
- data/lib/dynflow/transaction_adapters/active_record.rb +27 -0
- data/lib/dynflow/transaction_adapters/none.rb +12 -0
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/web_console.rb +277 -0
- data/lib/dynflow/world.rb +168 -0
- data/test/action_test.rb +89 -11
- data/test/clock_test.rb +59 -0
- data/test/code_workflow_example.rb +382 -0
- data/test/execution_plan_test.rb +195 -64
- data/test/executor_test.rb +692 -0
- data/test/persistance_adapters_test.rb +173 -0
- data/test/test_helper.rb +316 -1
- data/test/testing_test.rb +148 -0
- data/test/web_console_test.rb +38 -0
- data/web/assets/javascripts/application.js +25 -0
- data/web/assets/stylesheets/application.css +101 -0
- data/web/assets/vendor/bootstrap/css/bootstrap-responsive.css +1109 -0
- data/web/assets/vendor/bootstrap/css/bootstrap-responsive.min.css +9 -0
- data/web/assets/vendor/bootstrap/css/bootstrap.css +6167 -0
- data/web/assets/vendor/bootstrap/css/bootstrap.min.css +9 -0
- data/web/assets/vendor/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/web/assets/vendor/bootstrap/img/glyphicons-halflings.png +0 -0
- data/web/assets/vendor/bootstrap/js/bootstrap.js +2280 -0
- data/web/assets/vendor/bootstrap/js/bootstrap.min.js +6 -0
- data/web/assets/vendor/google-code-prettify/lang-basic.js +3 -0
- data/web/assets/vendor/google-code-prettify/prettify.css +1 -0
- data/web/assets/vendor/google-code-prettify/prettify.js +30 -0
- data/web/assets/vendor/google-code-prettify/run_prettify.js +34 -0
- data/web/assets/vendor/jquery/jquery.js +9807 -0
- data/web/views/flow.erb +19 -0
- data/web/views/flow_step.erb +31 -0
- data/web/views/index.erb +39 -0
- data/web/views/layout.erb +20 -0
- data/web/views/plan_step.erb +11 -0
- data/web/views/show.erb +54 -0
- metadata +250 -11
- data/examples/events.rb +0 -71
- data/examples/workflow.rb +0 -140
- data/lib/dynflow/bus.rb +0 -168
- data/lib/dynflow/dispatcher.rb +0 -36
- data/lib/dynflow/logger.rb +0 -34
- data/lib/dynflow/step.rb +0 -234
- data/test/bus_test.rb +0 -150
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module Testing
|
|
3
|
+
class DummyExecutor
|
|
4
|
+
attr_reader :world
|
|
5
|
+
|
|
6
|
+
def initialize(world)
|
|
7
|
+
@world = world
|
|
8
|
+
@events_to_process = []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def event(execution_plan_id, step_id, event, future = Future.new)
|
|
12
|
+
@events_to_process << [execution_plan_id, step_id, event, future]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def progress
|
|
16
|
+
events = @events_to_process.dup
|
|
17
|
+
clear
|
|
18
|
+
events.each do |execution_plan_id, step_id, event, future|
|
|
19
|
+
future.resolve true
|
|
20
|
+
world.action.execute event
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def clear
|
|
25
|
+
@events_to_process.clear
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module Testing
|
|
3
|
+
class DummyPlannedAction
|
|
4
|
+
attr_accessor :output, :plan_input
|
|
5
|
+
include Mimic
|
|
6
|
+
|
|
7
|
+
def initialize(klass)
|
|
8
|
+
mimic! klass
|
|
9
|
+
@output = ExecutionPlan::OutputReference.new(Testing.get_id, Testing.get_id)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def execute(execution_plan, event, *args)
|
|
13
|
+
@plan_input = args
|
|
14
|
+
self
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module Testing
|
|
3
|
+
class DummyStep
|
|
4
|
+
extend Mimic
|
|
5
|
+
mimic! ExecutionPlan::Steps::Abstract
|
|
6
|
+
|
|
7
|
+
attr_accessor :state, :error
|
|
8
|
+
attr_reader :id
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@state = :pending
|
|
12
|
+
@id = Testing.get_id
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def save
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module Testing
|
|
3
|
+
class DummyWorld
|
|
4
|
+
extend Mimic
|
|
5
|
+
mimic! World
|
|
6
|
+
|
|
7
|
+
attr_reader :clock, :executor
|
|
8
|
+
attr_accessor :action
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@logger_adapter = Testing.logger_adapter
|
|
12
|
+
@clock = ManagedClock.new
|
|
13
|
+
@executor = DummyExecutor.new(self)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def action_logger
|
|
17
|
+
@logger_adapter.action_logger
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def logger
|
|
21
|
+
@logger_adapter.dynflow_logger
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def subscribed_actions(klass)
|
|
25
|
+
[]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def event(execution_plan_id, step_id, event, future = Future.new)
|
|
29
|
+
executor.event execution_plan_id, step_id, event, future
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module Testing
|
|
3
|
+
module Factories
|
|
4
|
+
include Algebrick::TypeCheck
|
|
5
|
+
|
|
6
|
+
# @return [Action::PlanPhase]
|
|
7
|
+
def create_action(action_class, trigger = nil)
|
|
8
|
+
execution_plan = DummyExecutionPlan.new
|
|
9
|
+
step = DummyStep.new
|
|
10
|
+
action_class.plan_phase.new(
|
|
11
|
+
{ step: DummyStep.new,
|
|
12
|
+
execution_plan_id: execution_plan.id,
|
|
13
|
+
id: Testing.get_id,
|
|
14
|
+
plan_step_id: step.id },
|
|
15
|
+
execution_plan, trigger)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @return [Action::PlanPhase]
|
|
19
|
+
def plan_action(plan_action, *args, &block)
|
|
20
|
+
Type! plan_action, Dynflow::Action::PlanPhase
|
|
21
|
+
|
|
22
|
+
plan_action.execute *args, &block
|
|
23
|
+
raise plan_action.error if plan_action.error
|
|
24
|
+
plan_action
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def create_and_plan_action(action_class, *args, &block)
|
|
28
|
+
plan_action create_action(action_class), *args, &block
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [Action::RunPhase]
|
|
32
|
+
def run_action(plan_action, event = nil, &stubbing)
|
|
33
|
+
Type! plan_action, Dynflow::Action::PlanPhase, Dynflow::Action::RunPhase
|
|
34
|
+
step = DummyStep.new
|
|
35
|
+
run_action = if Dynflow::Action::PlanPhase === plan_action
|
|
36
|
+
plan_action.action_class.run_phase.new(
|
|
37
|
+
{ step: step,
|
|
38
|
+
execution_plan_id: plan_action.execution_plan_id,
|
|
39
|
+
id: plan_action.id,
|
|
40
|
+
plan_step_id: plan_action.plan_step_id,
|
|
41
|
+
run_step_id: step.id,
|
|
42
|
+
input: plan_action.input },
|
|
43
|
+
plan_action.world)
|
|
44
|
+
|
|
45
|
+
else
|
|
46
|
+
plan_action
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
run_action.world.action ||= run_action
|
|
50
|
+
run_action.world.clock.clear
|
|
51
|
+
stubbing.call run_action if stubbing
|
|
52
|
+
run_action.execute event
|
|
53
|
+
raise run_action.error if run_action.error
|
|
54
|
+
run_action
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [Action::FinalizePhase]
|
|
58
|
+
def finalize_action(run_action, &stubbing)
|
|
59
|
+
Type! run_action, Dynflow::Action::RunPhase
|
|
60
|
+
step = DummyStep.new
|
|
61
|
+
finalize_action = run_action.action_class.finalize_phase.new(
|
|
62
|
+
{ step: step,
|
|
63
|
+
execution_plan_id: run_action.execution_plan_id,
|
|
64
|
+
id: run_action.id,
|
|
65
|
+
plan_step_id: run_action.plan_step_id,
|
|
66
|
+
run_step_id: run_action.run_step_id,
|
|
67
|
+
finalize_step_id: step.id,
|
|
68
|
+
input: run_action.input },
|
|
69
|
+
run_action.world)
|
|
70
|
+
|
|
71
|
+
stubbing.call finalize_action if stubbing
|
|
72
|
+
finalize_action.execute
|
|
73
|
+
finalize_action
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def progress_action_time action
|
|
77
|
+
Type! action, Dynflow::Action::RunPhase
|
|
78
|
+
action.world.clock.progress
|
|
79
|
+
action.world.executor.progress
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module Testing
|
|
3
|
+
class ManagedClock
|
|
4
|
+
def initialize
|
|
5
|
+
@pings_to_process = []
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def ping(who, time, with_what = nil, where = :<<)
|
|
9
|
+
@pings_to_process << [who, [where, with_what].compact]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def progress
|
|
13
|
+
copy = @pings_to_process.dup
|
|
14
|
+
clear
|
|
15
|
+
copy.each { |who, args| who.send *args }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def clear
|
|
19
|
+
@pings_to_process.clear
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module Testing
|
|
3
|
+
|
|
4
|
+
# when extended into Class or an_object it makes all instances of the class or the object
|
|
5
|
+
# mimic the supplied types. It does so by hooking into kind_of? method.
|
|
6
|
+
# @example
|
|
7
|
+
# m = mock('product')
|
|
8
|
+
# m.is_a? ::Product # => false
|
|
9
|
+
# m.extend Mimic
|
|
10
|
+
# m.mimic! ::Product
|
|
11
|
+
# m.is_a? ::Product # => true
|
|
12
|
+
module Mimic
|
|
13
|
+
class ::Module
|
|
14
|
+
def ===(v)
|
|
15
|
+
v.kind_of? self
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def mimic!(*types)
|
|
20
|
+
define =-> _ do
|
|
21
|
+
define_method :kind_of? do |type|
|
|
22
|
+
types.any? { |t| t <= type } || super(type)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
alias_method :is_a?, :kind_of?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if self.kind_of? ::Class
|
|
29
|
+
self.class_eval &define
|
|
30
|
+
else
|
|
31
|
+
self.singleton_class.class_eval &define
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module TransactionAdapters
|
|
3
|
+
class Abstract
|
|
4
|
+
# start transaction around +block+
|
|
5
|
+
def transaction(&block)
|
|
6
|
+
raise NotImplementedError
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# rollback the transaction
|
|
10
|
+
def rollback
|
|
11
|
+
raise NotImplementedError
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Called on each thread after work is done.
|
|
15
|
+
# E.g. it's used to checkin ActiveRecord connections back to pool.
|
|
16
|
+
def cleanup
|
|
17
|
+
# override if needed
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Called after World instantiation, it can be used to check Dynflow configuration etc.
|
|
21
|
+
def check(world)
|
|
22
|
+
# override if needed
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module TransactionAdapters
|
|
3
|
+
class ActiveRecord < Abstract
|
|
4
|
+
def transaction(&block)
|
|
5
|
+
::ActiveRecord::Base.transaction(&block)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def rollback
|
|
9
|
+
raise ::ActiveRecord::Rollback
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def cleanup
|
|
13
|
+
::ActiveRecord::Base.clear_active_connections!
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def check(world)
|
|
17
|
+
# missing reader in ConnectionPool
|
|
18
|
+
ar_pool_size = ::ActiveRecord::Base.connection_pool.instance_variable_get(:@size)
|
|
19
|
+
if (world.options[:pool_size] / 2.0) > ar_pool_size
|
|
20
|
+
world.logger.warn 'Consider increasing ActiveRecord::Base.connection_pool size, ' +
|
|
21
|
+
"it's #{ar_pool_size} but there is #{world.options[:pool_size]} " +
|
|
22
|
+
'threads in Dynflow pool.'
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/dynflow/version.rb
CHANGED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
require 'dynflow'
|
|
2
|
+
require 'pp'
|
|
3
|
+
require 'sinatra'
|
|
4
|
+
|
|
5
|
+
module Dynflow
|
|
6
|
+
class WebConsole < Sinatra::Base
|
|
7
|
+
|
|
8
|
+
def self.setup(&block)
|
|
9
|
+
Sinatra.new(self) do
|
|
10
|
+
instance_exec(&block)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
web_dir = File.join(File.expand_path('../../../web', __FILE__))
|
|
15
|
+
|
|
16
|
+
set :public_folder, File.join(web_dir, 'assets')
|
|
17
|
+
set :views, File.join(web_dir, 'views')
|
|
18
|
+
set :per_page, 10
|
|
19
|
+
|
|
20
|
+
helpers ERB::Util
|
|
21
|
+
|
|
22
|
+
helpers do
|
|
23
|
+
def world
|
|
24
|
+
settings.world
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def prettyprint(value)
|
|
28
|
+
value = prettyprint_references(value)
|
|
29
|
+
if value
|
|
30
|
+
pretty_value = value.pretty_inspect
|
|
31
|
+
<<-HTML
|
|
32
|
+
<pre class="prettyprint">#{h(pretty_value)}</pre>
|
|
33
|
+
HTML
|
|
34
|
+
else
|
|
35
|
+
""
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def prettyprint_references(value)
|
|
40
|
+
case value
|
|
41
|
+
when Hash
|
|
42
|
+
value.reduce({}) do |h, (key, val)|
|
|
43
|
+
h.update(key => prettyprint_references(val))
|
|
44
|
+
end
|
|
45
|
+
when Array
|
|
46
|
+
value.map { |val| prettyprint_references(val) }
|
|
47
|
+
when ExecutionPlan::OutputReference
|
|
48
|
+
value.inspect
|
|
49
|
+
else
|
|
50
|
+
value
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def load_action(step)
|
|
55
|
+
world.persistence.load_action(step)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def step_error(step)
|
|
59
|
+
if step.error
|
|
60
|
+
['<pre>',
|
|
61
|
+
"#{h(step.error.message)} (#{h(step.error.exception_class)})\n",
|
|
62
|
+
h(step.error.backtrace.join("\n")),
|
|
63
|
+
'</pre>'].join
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def show_action_data(label, value)
|
|
68
|
+
value_html = prettyprint(value)
|
|
69
|
+
if !value_html.empty?
|
|
70
|
+
<<-HTML
|
|
71
|
+
<p>
|
|
72
|
+
#{h(label)}
|
|
73
|
+
#{value_html}
|
|
74
|
+
</p>
|
|
75
|
+
HTML
|
|
76
|
+
else
|
|
77
|
+
""
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def atom_css_classes(atom)
|
|
82
|
+
classes = ["atom"]
|
|
83
|
+
step = @plan.steps[atom.step_id]
|
|
84
|
+
case step.state
|
|
85
|
+
when :success
|
|
86
|
+
classes << "success"
|
|
87
|
+
when :error
|
|
88
|
+
classes << "error"
|
|
89
|
+
when :skipped
|
|
90
|
+
classes << "skipped"
|
|
91
|
+
end
|
|
92
|
+
return classes.join(" ")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def flow_css_classes(flow, sub_flow = nil)
|
|
96
|
+
classes = []
|
|
97
|
+
case flow
|
|
98
|
+
when Flows::Sequence
|
|
99
|
+
classes << "sequence"
|
|
100
|
+
when Flows::Concurrence
|
|
101
|
+
classes << "concurrence"
|
|
102
|
+
when Flows::Atom
|
|
103
|
+
classes << atom_css_classes(flow)
|
|
104
|
+
else
|
|
105
|
+
raise "Unknown run plan #{run_plan.inspect}"
|
|
106
|
+
end
|
|
107
|
+
classes << atom_css_classes(sub_flow) if sub_flow.is_a? Flows::Atom
|
|
108
|
+
return classes.join(" ")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def step_css_class(step)
|
|
112
|
+
case step.state
|
|
113
|
+
when :success
|
|
114
|
+
"success"
|
|
115
|
+
when :error
|
|
116
|
+
"important"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def progress_width(action)
|
|
121
|
+
if action.state == :error
|
|
122
|
+
100 # we want to show the red bar in full width
|
|
123
|
+
else
|
|
124
|
+
action.progress_done * 100
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def step(step_id)
|
|
129
|
+
@plan.steps[step_id]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def paginate?
|
|
133
|
+
world.persistence.adapter.pagination?
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def updated_url(new_params)
|
|
137
|
+
url("?" + Rack::Utils.build_query(params.merge(new_params.stringify_keys)))
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def paginated_url(delta)
|
|
141
|
+
h(updated_url(page: [0, page + delta].max))
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def pagination_options
|
|
145
|
+
if paginate?
|
|
146
|
+
{ page: page, per_page: per_page }
|
|
147
|
+
else
|
|
148
|
+
if params[:page] || params[:per_page]
|
|
149
|
+
halt 400, "The persistence doesn't support pagination"
|
|
150
|
+
end
|
|
151
|
+
return {}
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def page
|
|
156
|
+
(params[:page] || 0).to_i
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def per_page
|
|
160
|
+
(params[:per_page] || 10).to_i
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def supported_ordering?(ord_attr)
|
|
164
|
+
world.persistence.adapter.ordering_by.any? do |attr|
|
|
165
|
+
attr.to_s == ord_attr.to_s
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def ordering_options
|
|
170
|
+
return @ordering_options if @ordering_options
|
|
171
|
+
|
|
172
|
+
if params[:order_by]
|
|
173
|
+
unless supported_ordering?(params[:order_by])
|
|
174
|
+
halt 400, "Unsupported ordering"
|
|
175
|
+
end
|
|
176
|
+
@ordering_options = { order_by: params[:order_by],
|
|
177
|
+
desc: (params[:desc] == 'true') }
|
|
178
|
+
elsif supported_ordering?('started_at')
|
|
179
|
+
@ordering_options = { order_by: 'started_at', desc: true }
|
|
180
|
+
else
|
|
181
|
+
@ordering_options = {}
|
|
182
|
+
end
|
|
183
|
+
return @ordering_options
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def order_link(attr, label)
|
|
187
|
+
return h(label) unless supported_ordering?(attr)
|
|
188
|
+
new_ordering_options = { order_by: attr.to_s,
|
|
189
|
+
desc: false }
|
|
190
|
+
arrow = ""
|
|
191
|
+
if ordering_options[:order_by].to_s == attr.to_s
|
|
192
|
+
arrow = ordering_options[:desc] ? "▼" : "▲"
|
|
193
|
+
new_ordering_options[:desc] = !ordering_options[:desc]
|
|
194
|
+
end
|
|
195
|
+
url = updated_url(new_ordering_options)
|
|
196
|
+
return %{<a href="#{url}"> #{arrow} #{h(label)}</a>}
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def supported_filter?(filter_attr)
|
|
200
|
+
world.persistence.adapter.filtering_by.any? do |attr|
|
|
201
|
+
attr.to_s == filter_attr.to_s
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def filtering_options
|
|
206
|
+
return @filtering_options if @filtering_options
|
|
207
|
+
|
|
208
|
+
if params[:filters]
|
|
209
|
+
params[:filters].map do |key, value|
|
|
210
|
+
unless supported_filter?(key)
|
|
211
|
+
halt 400, "Unsupported ordering"
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
filters = params[:filters]
|
|
216
|
+
elsif supported_filter?('state')
|
|
217
|
+
filters = { 'state' => ExecutionPlan.states.map(&:to_s) - ['stopped'] }
|
|
218
|
+
else
|
|
219
|
+
filters = {}
|
|
220
|
+
end
|
|
221
|
+
@filtering_options = { filters: filters }.with_indifferent_access
|
|
222
|
+
return @filtering_options
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def filter_checkbox(field, values)
|
|
226
|
+
out = "<p>#{field}: %s</p>"
|
|
227
|
+
checkboxes = values.map do |value|
|
|
228
|
+
field_filter = filtering_options[:filters][field]
|
|
229
|
+
checked = field_filter && field_filter.include?(value)
|
|
230
|
+
%{<input type="checkbox" name="filters[#{field}][]" value="#{value}" #{ "checked" if checked }/>#{value}}
|
|
231
|
+
end.join(' ')
|
|
232
|
+
out %= checkboxes
|
|
233
|
+
return out
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
get('/') do
|
|
239
|
+
options = HashWithIndifferentAccess.new
|
|
240
|
+
options.merge!(filtering_options)
|
|
241
|
+
options.merge!(pagination_options)
|
|
242
|
+
options.merge!(ordering_options)
|
|
243
|
+
|
|
244
|
+
@plans = world.persistence.find_execution_plans(options)
|
|
245
|
+
erb :index
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
get('/:id') do |id|
|
|
249
|
+
@plan = world.persistence.load_execution_plan(id)
|
|
250
|
+
erb :show
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
post('/:id/resume') do |id|
|
|
254
|
+
plan = world.persistence.load_execution_plan(id)
|
|
255
|
+
if plan.state != :paused
|
|
256
|
+
redirect(url "/#{plan.id}?notice=#{url_encode('The exeuction has to be paused to be able to resume')}")
|
|
257
|
+
else
|
|
258
|
+
world.execute(plan.id)
|
|
259
|
+
redirect(url "/#{plan.id}?notice=#{url_encode('The execution was resumed')}")
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
post('/:id/skip/:step_id') do |id, step_id|
|
|
264
|
+
plan = world.persistence.load_execution_plan(id)
|
|
265
|
+
step = plan.steps[step_id.to_i]
|
|
266
|
+
if plan.state != :paused
|
|
267
|
+
redirect(url "/#{plan.id}?notice=#{url_encode('The exeuction has to be paused to be able to skip')}")
|
|
268
|
+
elsif step.state != :error
|
|
269
|
+
redirect(url "/#{plan.id}?notice=#{url_encode('The step has to be failed to be able to skip')}")
|
|
270
|
+
else
|
|
271
|
+
plan.skip(step)
|
|
272
|
+
redirect(url "/#{plan.id}")
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
end
|
|
277
|
+
end
|