dynflow 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|