dynflow 0.7.9 → 0.8.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 +2 -0
- data/.travis.yml +16 -1
- data/Gemfile +13 -1
- data/doc/pages/source/_drafts/2015-03-01-new-documentation.markdown +10 -0
- data/doc/pages/source/_includes/menu.html +1 -0
- data/doc/pages/source/_includes/menu_right.html +1 -1
- data/doc/pages/source/_sass/_bootstrap-variables.sass +1 -0
- data/doc/pages/source/_sass/_style.scss +4 -0
- data/doc/pages/source/blog/index.html +12 -0
- data/doc/pages/source/documentation/index.md +330 -5
- data/dynflow.gemspec +3 -1
- data/examples/example_helper.rb +18 -11
- data/examples/orchestrate_evented.rb +2 -1
- data/examples/remote_executor.rb +53 -20
- data/lib/dynflow.rb +16 -6
- data/lib/dynflow/action/suspended.rb +1 -1
- data/lib/dynflow/action/with_sub_plans.rb +3 -6
- data/lib/dynflow/actor.rb +56 -0
- data/lib/dynflow/clock.rb +43 -38
- data/lib/dynflow/config.rb +107 -0
- data/lib/dynflow/connectors.rb +7 -0
- data/lib/dynflow/connectors/abstract.rb +41 -0
- data/lib/dynflow/connectors/database.rb +175 -0
- data/lib/dynflow/connectors/direct.rb +71 -0
- data/lib/dynflow/coordinator.rb +280 -0
- data/lib/dynflow/coordinator_adapters.rb +8 -0
- data/lib/dynflow/coordinator_adapters/abstract.rb +28 -0
- data/lib/dynflow/coordinator_adapters/sequel.rb +29 -0
- data/lib/dynflow/dispatcher.rb +58 -0
- data/lib/dynflow/dispatcher/abstract.rb +14 -0
- data/lib/dynflow/dispatcher/client_dispatcher.rb +139 -0
- data/lib/dynflow/dispatcher/executor_dispatcher.rb +86 -0
- data/lib/dynflow/errors.rb +7 -1
- data/lib/dynflow/execution_history.rb +46 -0
- data/lib/dynflow/execution_plan.rb +19 -15
- data/lib/dynflow/executors.rb +0 -1
- data/lib/dynflow/executors/abstract.rb +5 -10
- data/lib/dynflow/executors/parallel.rb +16 -13
- data/lib/dynflow/executors/parallel/core.rb +76 -78
- data/lib/dynflow/executors/parallel/execution_plan_manager.rb +4 -5
- data/lib/dynflow/executors/parallel/pool.rb +22 -52
- data/lib/dynflow/executors/parallel/running_steps_manager.rb +9 -2
- data/lib/dynflow/executors/parallel/worker.rb +5 -10
- data/lib/dynflow/persistence.rb +14 -0
- data/lib/dynflow/persistence_adapters/abstract.rb +14 -3
- data/lib/dynflow/persistence_adapters/sequel.rb +142 -38
- data/lib/dynflow/persistence_adapters/sequel_migrations/004_coordinator_records.rb +14 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/005_envelopes.rb +14 -0
- data/lib/dynflow/round_robin.rb +37 -0
- data/lib/dynflow/serializable.rb +1 -2
- data/lib/dynflow/serializer.rb +46 -0
- data/lib/dynflow/testing/dummy_executor.rb +2 -2
- data/lib/dynflow/testing/dummy_world.rb +1 -1
- data/lib/dynflow/transaction_adapters/abstract.rb +0 -5
- data/lib/dynflow/transaction_adapters/active_record.rb +0 -10
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/web.rb +26 -0
- data/lib/dynflow/web/console.rb +108 -0
- data/lib/dynflow/web/console_helpers.rb +158 -0
- data/lib/dynflow/web/filtering_helpers.rb +85 -0
- data/lib/dynflow/web/world_helpers.rb +9 -0
- data/lib/dynflow/web_console.rb +3 -310
- data/lib/dynflow/world.rb +188 -119
- data/test/abnormal_states_recovery_test.rb +152 -0
- data/test/action_test.rb +2 -3
- data/test/clock_test.rb +1 -5
- data/test/coordinator_test.rb +152 -0
- data/test/dispatcher_test.rb +146 -0
- data/test/execution_plan_test.rb +2 -1
- data/test/executor_test.rb +534 -612
- data/test/middleware_test.rb +4 -4
- data/test/persistence_test.rb +17 -0
- data/test/prepare_travis_env.sh +35 -0
- data/test/rescue_test.rb +5 -3
- data/test/round_robin_test.rb +28 -0
- data/test/support/code_workflow_example.rb +0 -73
- data/test/support/dummy_example.rb +130 -0
- data/test/support/test_execution_log.rb +41 -0
- data/test/test_helper.rb +222 -116
- data/test/testing_test.rb +10 -10
- data/test/web_console_test.rb +3 -3
- data/test/world_test.rb +23 -0
- data/web/assets/images/logo-square.png +0 -0
- data/web/assets/stylesheets/application.css +9 -0
- data/web/assets/vendor/bootstrap/config.json +429 -0
- data/web/assets/vendor/bootstrap/css/bootstrap-theme.css +479 -0
- data/web/assets/vendor/bootstrap/css/bootstrap-theme.min.css +10 -0
- data/web/assets/vendor/bootstrap/css/bootstrap.css +5377 -4980
- data/web/assets/vendor/bootstrap/css/bootstrap.min.css +9 -8
- data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
- data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
- data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
- data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/web/assets/vendor/bootstrap/js/bootstrap.js +1674 -1645
- data/web/assets/vendor/bootstrap/js/bootstrap.min.js +11 -5
- data/web/views/execution_history.erb +17 -0
- data/web/views/index.erb +4 -6
- data/web/views/layout.erb +44 -8
- data/web/views/show.erb +4 -5
- data/web/views/worlds.erb +26 -0
- metadata +116 -23
- checksums.yaml +0 -15
- data/lib/dynflow/daemon.rb +0 -30
- data/lib/dynflow/executors/remote_via_socket.rb +0 -43
- data/lib/dynflow/executors/remote_via_socket/core.rb +0 -184
- data/lib/dynflow/future.rb +0 -173
- data/lib/dynflow/listeners.rb +0 -7
- data/lib/dynflow/listeners/abstract.rb +0 -17
- data/lib/dynflow/listeners/serialization.rb +0 -77
- data/lib/dynflow/listeners/socket.rb +0 -117
- data/lib/dynflow/micro_actor.rb +0 -102
- data/lib/dynflow/simple_world.rb +0 -19
- data/test/remote_via_socket_test.rb +0 -170
- data/web/assets/vendor/bootstrap/css/bootstrap-responsive.css +0 -1109
- data/web/assets/vendor/bootstrap/css/bootstrap-responsive.min.css +0 -9
- data/web/assets/vendor/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/web/assets/vendor/bootstrap/img/glyphicons-halflings.png +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'algebrick/serializer'
|
|
2
|
+
|
|
3
|
+
module Dynflow
|
|
4
|
+
def self.serializer
|
|
5
|
+
@serializer ||= Serializer.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class Serializer < Algebrick::Serializer
|
|
9
|
+
|
|
10
|
+
ARBITRARY_TYPE_KEY = :class
|
|
11
|
+
MARSHAL_KEY = :marshaled
|
|
12
|
+
|
|
13
|
+
protected
|
|
14
|
+
|
|
15
|
+
def parse_other(other, options = {})
|
|
16
|
+
if Hash === other
|
|
17
|
+
if (marshal_value = other[MARSHAL_KEY] || other[MARSHAL_KEY.to_s])
|
|
18
|
+
return Marshal.load(Base64.strict_decode64(marshal_value))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if (type_name = other[ARBITRARY_TYPE_KEY] || other[ARBITRARY_TYPE_KEY.to_s])
|
|
22
|
+
type = type_name.constantize rescue nil
|
|
23
|
+
if type && type.respond_to?(:from_hash)
|
|
24
|
+
return type.from_hash other
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
return other
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def generate_other(object, options = {})
|
|
33
|
+
hash = case
|
|
34
|
+
when object.respond_to?(:to_h)
|
|
35
|
+
object.to_h
|
|
36
|
+
when object.respond_to?(:to_hash)
|
|
37
|
+
object.to_hash
|
|
38
|
+
else
|
|
39
|
+
{ ARBITRARY_TYPE_KEY => object.class.to_s,
|
|
40
|
+
MARSHAL_KEY => Base64.strict_encode64(Marshal.dump(object)) }
|
|
41
|
+
end
|
|
42
|
+
raise "Missing #{ARBITRARY_TYPE_KEY} key in #{hash.inspect}" unless hash.key?(ARBITRARY_TYPE_KEY)
|
|
43
|
+
hash
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -8,7 +8,7 @@ module Dynflow
|
|
|
8
8
|
@events_to_process = []
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def event(execution_plan_id, step_id, event, future =
|
|
11
|
+
def event(execution_plan_id, step_id, event, future = Concurrent.future)
|
|
12
12
|
@events_to_process << [execution_plan_id, step_id, event, future]
|
|
13
13
|
end
|
|
14
14
|
|
|
@@ -17,7 +17,7 @@ module Dynflow
|
|
|
17
17
|
events = @events_to_process.dup
|
|
18
18
|
clear
|
|
19
19
|
events.each do |execution_plan_id, step_id, event, future|
|
|
20
|
-
future.
|
|
20
|
+
future.success true
|
|
21
21
|
if event && world.action.state != :suspended
|
|
22
22
|
return false
|
|
23
23
|
end
|
|
@@ -12,16 +12,6 @@ module Dynflow
|
|
|
12
12
|
def cleanup
|
|
13
13
|
::ActiveRecord::Base.clear_active_connections!
|
|
14
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
15
|
end
|
|
26
16
|
end
|
|
27
17
|
end
|
data/lib/dynflow/version.rb
CHANGED
data/lib/dynflow/web.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'dynflow'
|
|
2
|
+
require 'pp'
|
|
3
|
+
require 'sinatra/base'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
|
|
6
|
+
module Dynflow
|
|
7
|
+
module Web
|
|
8
|
+
|
|
9
|
+
def self.setup(&block)
|
|
10
|
+
console = Sinatra.new(Web::Console) { instance_exec(&block)}
|
|
11
|
+
Rack::Builder.app do
|
|
12
|
+
run Rack::URLMap.new('/' => console)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.web_dir(sub_dir)
|
|
17
|
+
web_dir = File.join(File.expand_path('../../../web', __FILE__))
|
|
18
|
+
File.join(web_dir, sub_dir)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
require 'dynflow/web/filtering_helpers'
|
|
22
|
+
require 'dynflow/web/world_helpers'
|
|
23
|
+
require 'dynflow/web/console_helpers'
|
|
24
|
+
require 'dynflow/web/console'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
|
|
2
|
+
module Dynflow
|
|
3
|
+
module Web
|
|
4
|
+
class Console < Sinatra::Base
|
|
5
|
+
|
|
6
|
+
set :public_folder, Web.web_dir('assets')
|
|
7
|
+
set :views, Web.web_dir('views')
|
|
8
|
+
set :per_page, 10
|
|
9
|
+
|
|
10
|
+
helpers ERB::Util
|
|
11
|
+
helpers Web::FilteringHelpers
|
|
12
|
+
helpers Web::WorldHelpers
|
|
13
|
+
helpers Web::ConsoleHelpers
|
|
14
|
+
|
|
15
|
+
get('/') do
|
|
16
|
+
options = find_execution_plans_options
|
|
17
|
+
@plans = world.persistence.find_execution_plans(options)
|
|
18
|
+
erb :index
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
get('/:execution_plan_id/actions/:action_id/sub_plans') do |execution_plan_id, action_id|
|
|
22
|
+
options = find_execution_plans_options(true)
|
|
23
|
+
options[:filters].update('caller_execution_plan_id' => execution_plan_id,
|
|
24
|
+
'caller_action_id' => action_id)
|
|
25
|
+
@plans = world.persistence.find_execution_plans(options)
|
|
26
|
+
erb :index
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
get('/status') do
|
|
30
|
+
# TODO: create a separate page for the overall status, linking
|
|
31
|
+
# to the more detailed pages
|
|
32
|
+
redirect to '/worlds'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
get('/worlds') do
|
|
36
|
+
@worlds = world.coordinator.find_worlds
|
|
37
|
+
erb :worlds
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
post('/worlds/:id/ping') do |id|
|
|
41
|
+
timeout = 5
|
|
42
|
+
ping_response = world.ping(id, timeout).wait
|
|
43
|
+
if ping_response.failed?
|
|
44
|
+
response = "failed: #{ping_response.reason.message}"
|
|
45
|
+
inactive_world_id = id
|
|
46
|
+
else
|
|
47
|
+
response = 'pong'
|
|
48
|
+
end
|
|
49
|
+
redirect(url "/worlds?notice=#{url_encode(response)}&inactive_world_id=#{inactive_world_id}")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
post('/worlds/:id/invalidate') do |id|
|
|
53
|
+
invalidated_world = world.coordinator.find_worlds(false, id: id).first
|
|
54
|
+
unless invalidated_world
|
|
55
|
+
response = "World #{id} not found"
|
|
56
|
+
else
|
|
57
|
+
begin
|
|
58
|
+
world.invalidate(invalidated_world)
|
|
59
|
+
response = "World #{invalidated_world.id} invalidated"
|
|
60
|
+
rescue => e
|
|
61
|
+
response = "World invalidation failed: #{e.message}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
redirect(url "/worlds?notice=#{url_encode(response)}")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
get('/:id') do |id|
|
|
68
|
+
@plan = world.persistence.load_execution_plan(id)
|
|
69
|
+
erb :show
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
post('/:id/resume') do |id|
|
|
73
|
+
plan = world.persistence.load_execution_plan(id)
|
|
74
|
+
if plan.state != :paused
|
|
75
|
+
redirect(url "/#{plan.id}?notice=#{url_encode('The exeuction has to be paused to be able to resume')}")
|
|
76
|
+
else
|
|
77
|
+
world.execute(plan.id)
|
|
78
|
+
redirect(url "/#{plan.id}?notice=#{url_encode('The execution was resumed')}")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
post('/:id/skip/:step_id') do |id, step_id|
|
|
83
|
+
plan = world.persistence.load_execution_plan(id)
|
|
84
|
+
step = plan.steps[step_id.to_i]
|
|
85
|
+
if plan.state != :paused
|
|
86
|
+
redirect(url "/#{plan.id}?notice=#{url_encode('The exeuction has to be paused to be able to skip')}")
|
|
87
|
+
elsif step.state != :error
|
|
88
|
+
redirect(url "/#{plan.id}?notice=#{url_encode('The step has to be failed to be able to skip')}")
|
|
89
|
+
else
|
|
90
|
+
plan.skip(step)
|
|
91
|
+
redirect(url "/#{plan.id}")
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
post('/:id/cancel/:step_id') do |id, step_id|
|
|
96
|
+
plan = world.persistence.load_execution_plan(id)
|
|
97
|
+
step = plan.steps[step_id.to_i]
|
|
98
|
+
if step.cancellable?
|
|
99
|
+
world.event(plan.id, step.id, Dynflow::Action::Cancellable::Cancel)
|
|
100
|
+
redirect(url "/#{plan.id}?notice=#{url_encode('The step was asked to cancel')}")
|
|
101
|
+
else
|
|
102
|
+
redirect(url "/#{plan.id}?notice=#{url_encode('The step does not support cancelling')}")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module Web
|
|
3
|
+
module ConsoleHelpers
|
|
4
|
+
def prettify_value(value)
|
|
5
|
+
YAML.dump(value)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def prettyprint(value)
|
|
9
|
+
value = prettyprint_references(value)
|
|
10
|
+
if value
|
|
11
|
+
pretty_value = prettify_value(value)
|
|
12
|
+
<<-HTML
|
|
13
|
+
<pre class="prettyprint lang-yaml">#{h(pretty_value)}</pre>
|
|
14
|
+
HTML
|
|
15
|
+
else
|
|
16
|
+
""
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def prettyprint_references(value)
|
|
21
|
+
case value
|
|
22
|
+
when Hash
|
|
23
|
+
value.reduce({}) do |h, (key, val)|
|
|
24
|
+
h.update(key => prettyprint_references(val))
|
|
25
|
+
end
|
|
26
|
+
when Array
|
|
27
|
+
value.map { |val| prettyprint_references(val) }
|
|
28
|
+
when ExecutionPlan::OutputReference
|
|
29
|
+
value.inspect
|
|
30
|
+
else
|
|
31
|
+
value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def duration_to_s(duration)
|
|
36
|
+
h("%0.2fs" % duration)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def load_action(step)
|
|
40
|
+
world.persistence.load_action(step)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def step_error(step)
|
|
44
|
+
if step.error
|
|
45
|
+
['<pre>',
|
|
46
|
+
"#{h(step.error.message)} (#{h(step.error.exception_class)})\n",
|
|
47
|
+
h(step.error.backtrace.join("\n")),
|
|
48
|
+
'</pre>'].join
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def show_world(world_id)
|
|
53
|
+
if registered_world = world.coordinator.find_worlds(false, id: world_id).first
|
|
54
|
+
"%{world_id} %{world_meta}" % { world_id: world_id, world_meta: registered_world.meta.inspect }
|
|
55
|
+
else
|
|
56
|
+
world_id
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def show_action_data(label, value)
|
|
61
|
+
value_html = prettyprint(value)
|
|
62
|
+
if !value_html.empty?
|
|
63
|
+
<<-HTML
|
|
64
|
+
<p>
|
|
65
|
+
<b>#{h(label)}</b>
|
|
66
|
+
#{value_html}
|
|
67
|
+
</p>
|
|
68
|
+
HTML
|
|
69
|
+
else
|
|
70
|
+
""
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def atom_css_classes(atom)
|
|
75
|
+
classes = ["atom"]
|
|
76
|
+
step = @plan.steps[atom.step_id]
|
|
77
|
+
case step.state
|
|
78
|
+
when :success
|
|
79
|
+
classes << "success"
|
|
80
|
+
when :error
|
|
81
|
+
classes << "error"
|
|
82
|
+
when :skipped, :skipping
|
|
83
|
+
classes << "skipped"
|
|
84
|
+
end
|
|
85
|
+
return classes.join(" ")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def flow_css_classes(flow, sub_flow = nil)
|
|
89
|
+
classes = []
|
|
90
|
+
case flow
|
|
91
|
+
when Flows::Sequence
|
|
92
|
+
classes << "sequence"
|
|
93
|
+
when Flows::Concurrence
|
|
94
|
+
classes << "concurrence"
|
|
95
|
+
when Flows::Atom
|
|
96
|
+
classes << atom_css_classes(flow)
|
|
97
|
+
else
|
|
98
|
+
raise "Unknown run plan #{run_plan.inspect}"
|
|
99
|
+
end
|
|
100
|
+
classes << atom_css_classes(sub_flow) if sub_flow.is_a? Flows::Atom
|
|
101
|
+
return classes.join(" ")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def step_css_class(step)
|
|
105
|
+
case step.state
|
|
106
|
+
when :success
|
|
107
|
+
"success"
|
|
108
|
+
when :error
|
|
109
|
+
"important"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def progress_width(step)
|
|
114
|
+
if step.state == :error
|
|
115
|
+
100 # we want to show the red bar in full width
|
|
116
|
+
else
|
|
117
|
+
step.progress_done * 100
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def step(step_id)
|
|
122
|
+
@plan.steps[step_id]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def updated_url(new_params)
|
|
126
|
+
url(request.path_info + "?" + Rack::Utils.build_nested_query(params.merge(new_params.stringify_keys)))
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def paginated_url(delta)
|
|
130
|
+
h(updated_url(page: [0, page + delta].max.to_s))
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def order_link(attr, label)
|
|
134
|
+
return h(label) unless supported_ordering?(attr)
|
|
135
|
+
new_ordering_options = { order_by: attr.to_s,
|
|
136
|
+
desc: false }
|
|
137
|
+
arrow = ""
|
|
138
|
+
if ordering_options[:order_by].to_s == attr.to_s
|
|
139
|
+
arrow = ordering_options[:desc] ? "▼" : "▲"
|
|
140
|
+
new_ordering_options[:desc] = !ordering_options[:desc]
|
|
141
|
+
end
|
|
142
|
+
url = updated_url(new_ordering_options)
|
|
143
|
+
return %{<a href="#{url}"> #{arrow} #{h(label)}</a>}
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def filter_checkbox(field, values)
|
|
147
|
+
out = "<p>#{field}: %s</p>"
|
|
148
|
+
checkboxes = values.map do |value|
|
|
149
|
+
field_filter = filtering_options[:filters][field]
|
|
150
|
+
checked = field_filter && field_filter.include?(value)
|
|
151
|
+
%{<input type="checkbox" name="filters[#{field}][]" value="#{value}" #{ "checked" if checked }/>#{value}}
|
|
152
|
+
end.join(' ')
|
|
153
|
+
out %= checkboxes
|
|
154
|
+
return out
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module Web
|
|
3
|
+
module FilteringHelpers
|
|
4
|
+
def supported_filter?(filter_attr)
|
|
5
|
+
world.persistence.adapter.filtering_by.any? do |attr|
|
|
6
|
+
attr.to_s == filter_attr.to_s
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def filtering_options(show_all = false)
|
|
11
|
+
return @filtering_options if @filtering_options
|
|
12
|
+
|
|
13
|
+
if params[:filters]
|
|
14
|
+
params[:filters].map do |key, value|
|
|
15
|
+
unless supported_filter?(key)
|
|
16
|
+
halt 400, "Unsupported ordering"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
filters = params[:filters]
|
|
21
|
+
elsif supported_filter?('state')
|
|
22
|
+
excluded_states = show_all ? [] : ['stopped']
|
|
23
|
+
filters = { 'state' => ExecutionPlan.states.map(&:to_s) - excluded_states }
|
|
24
|
+
else
|
|
25
|
+
filters = {}
|
|
26
|
+
end
|
|
27
|
+
@filtering_options = { filters: filters }.with_indifferent_access
|
|
28
|
+
return @filtering_options
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def find_execution_plans_options(show_all = false)
|
|
32
|
+
options = HashWithIndifferentAccess.new
|
|
33
|
+
options.merge!(filtering_options(show_all))
|
|
34
|
+
options.merge!(pagination_options)
|
|
35
|
+
options.merge!(ordering_options)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def paginate?
|
|
39
|
+
world.persistence.adapter.pagination?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def page
|
|
43
|
+
(params[:page] || 0).to_i
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def per_page
|
|
47
|
+
(params[:per_page] || 10).to_i
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def pagination_options
|
|
51
|
+
if paginate?
|
|
52
|
+
{ page: page, per_page: per_page }
|
|
53
|
+
else
|
|
54
|
+
if params[:page] || params[:per_page]
|
|
55
|
+
halt 400, "The persistence doesn't support pagination"
|
|
56
|
+
end
|
|
57
|
+
return {}
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def supported_ordering?(ord_attr)
|
|
62
|
+
world.persistence.adapter.ordering_by.any? do |attr|
|
|
63
|
+
attr.to_s == ord_attr.to_s
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def ordering_options
|
|
68
|
+
return @ordering_options if @ordering_options
|
|
69
|
+
|
|
70
|
+
if params[:order_by]
|
|
71
|
+
unless supported_ordering?(params[:order_by])
|
|
72
|
+
halt 400, "Unsupported ordering"
|
|
73
|
+
end
|
|
74
|
+
@ordering_options = { order_by: params[:order_by],
|
|
75
|
+
desc: (params[:desc] == 'true') }
|
|
76
|
+
elsif supported_ordering?('started_at')
|
|
77
|
+
@ordering_options = { order_by: 'started_at', desc: true }
|
|
78
|
+
else
|
|
79
|
+
@ordering_options = {}
|
|
80
|
+
end
|
|
81
|
+
return @ordering_options
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|