rocketjob_mission_control 2.1.1 → 3.0.0.rc1

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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/app/assets/stylesheets/rocket_job_mission_control/callout.scss +1 -1
  4. data/app/assets/stylesheets/rocket_job_mission_control/worker_processes.scss +2 -2
  5. data/app/controllers/rocket_job_mission_control/active_workers_controller.rb +15 -0
  6. data/app/controllers/rocket_job_mission_control/dirmon_entries/index_filters_controller.rb +8 -13
  7. data/app/controllers/rocket_job_mission_control/dirmon_entries_controller.rb +3 -14
  8. data/app/controllers/rocket_job_mission_control/jobs/failures_controller.rb +6 -11
  9. data/app/controllers/rocket_job_mission_control/jobs/index_filters_controller.rb +35 -14
  10. data/app/controllers/rocket_job_mission_control/jobs_controller.rb +12 -15
  11. data/app/controllers/rocket_job_mission_control/servers/index_filters_controller.rb +49 -0
  12. data/app/controllers/rocket_job_mission_control/servers_controller.rb +93 -0
  13. data/app/datatables/rocket_job_mission_control/abstract_datatable.rb +62 -0
  14. data/app/datatables/rocket_job_mission_control/active_workers_datatable.rb +47 -0
  15. data/app/datatables/rocket_job_mission_control/completed_jobs_datatable.rb +1 -1
  16. data/app/datatables/rocket_job_mission_control/dirmon_entries_datatable.rb +10 -72
  17. data/app/datatables/rocket_job_mission_control/interrupted_jobs_datatable.rb +1 -1
  18. data/app/datatables/rocket_job_mission_control/jobs_datatable.rb +16 -91
  19. data/app/datatables/rocket_job_mission_control/queued_jobs_datatable.rb +1 -1
  20. data/app/datatables/rocket_job_mission_control/running_jobs_datatable.rb +23 -12
  21. data/app/datatables/rocket_job_mission_control/scheduled_jobs_datatable.rb +1 -1
  22. data/app/datatables/rocket_job_mission_control/servers_datatable.rb +67 -0
  23. data/app/helpers/rocket_job_mission_control/{workers_helper.rb → servers_helper.rb} +9 -9
  24. data/app/models/job_failures.rb +3 -16
  25. data/app/models/rocket_job_mission_control/query.rb +51 -0
  26. data/app/views/layouts/rocket_job_mission_control/partials/_header.html.haml +3 -3
  27. data/app/views/layouts/rocket_job_mission_control/partials/_sidebar.html.haml +2 -2
  28. data/app/views/rocket_job_mission_control/{active_processes → active_workers}/index.html.haml +3 -3
  29. data/app/views/rocket_job_mission_control/dirmon_entries/_properties.html.haml +7 -18
  30. data/app/views/rocket_job_mission_control/dirmon_entries/_status.html.haml +0 -5
  31. data/app/views/rocket_job_mission_control/dirmon_entries/show.html.haml +4 -3
  32. data/app/views/rocket_job_mission_control/jobs/_status.html.haml +22 -17
  33. data/app/views/rocket_job_mission_control/jobs/edit.html.haml +1 -1
  34. data/app/views/rocket_job_mission_control/jobs/exceptions.html.haml +29 -0
  35. data/app/views/rocket_job_mission_control/jobs/failures/index.html.haml +7 -7
  36. data/app/views/rocket_job_mission_control/jobs/show.html.haml +23 -11
  37. data/app/views/rocket_job_mission_control/servers/_actions.html.haml +9 -0
  38. data/app/views/rocket_job_mission_control/servers/_sidebar.html.haml +20 -0
  39. data/app/views/rocket_job_mission_control/{workers → servers}/index.html.haml +9 -9
  40. data/app/views/rocket_job_mission_control/{workers → servers}/index_filters/paused.html.haml +8 -8
  41. data/app/views/rocket_job_mission_control/{workers → servers}/index_filters/running.html.haml +8 -8
  42. data/app/views/rocket_job_mission_control/{workers → servers}/index_filters/starting.html.haml +8 -8
  43. data/app/views/rocket_job_mission_control/{workers → servers}/index_filters/stopping.html.haml +8 -8
  44. data/config/locales/en.yml +2 -2
  45. data/config/routes.rb +7 -6
  46. data/lib/rocket_job_mission_control/engine.rb +0 -12
  47. data/lib/rocket_job_mission_control/version.rb +1 -1
  48. data/spec/controllers/dirmon_entries/index_filters_controller_spec.rb +2 -4
  49. data/spec/controllers/dirmon_entries_controller_spec.rb +15 -72
  50. data/spec/controllers/jobs/index_filters_controller_spec.rb +1 -1
  51. data/spec/controllers/workers/index_filters_controller_spec.rb +9 -9
  52. data/spec/controllers/workers_controller_spec.rb +47 -47
  53. data/spec/dummy/config/mongoid.yml +88 -0
  54. data/spec/dummy/config/symmetric-encryption.yml +8 -0
  55. data/spec/dummy/log/test.log +89072 -0
  56. data/spec/helpers/servers_helper_spec.rb +16 -0
  57. data/spec/spec_helper.rb +3 -0
  58. metadata +31 -45
  59. data/app/controllers/rocket_job_mission_control/active_processes_controller.rb +0 -27
  60. data/app/controllers/rocket_job_mission_control/workers/index_filters_controller.rb +0 -50
  61. data/app/controllers/rocket_job_mission_control/workers_controller.rb +0 -93
  62. data/app/datatables/rocket_job_mission_control/active_processes_datatable.rb +0 -79
  63. data/app/datatables/rocket_job_mission_control/workers_datatable.rb +0 -127
  64. data/app/interactors/rocket_job_mission_control/dirmon_entries/search.rb +0 -19
  65. data/app/interactors/rocket_job_mission_control/jobs/search.rb +0 -19
  66. data/app/interactors/rocket_job_mission_control/workers/search.rb +0 -19
  67. data/app/views/rocket_job_mission_control/workers/_actions.html.haml +0 -9
  68. data/app/views/rocket_job_mission_control/workers/_sidebar.html.haml +0 -20
  69. data/spec/dummy/config/mongo.yml +0 -15
  70. data/spec/helpers/workers_helper_spec.rb +0 -16
  71. data/spec/interactors/jobs/search_spec.rb +0 -35
@@ -0,0 +1,62 @@
1
+ module RocketJobMissionControl
2
+ class AbstractDatatable
3
+ delegate :params, :link_to, :render, to: :@view
4
+ delegate :h, to: 'ERB::Util'
5
+
6
+ attr_accessor :view, :query
7
+
8
+ def initialize(view, query)
9
+ @view = view
10
+ @query = query
11
+ extract_query_params
12
+ end
13
+
14
+ def as_json(options = {})
15
+ {
16
+ draw: params[:draw].to_i,
17
+ recordsTotal: query.unfiltered_count,
18
+ recordsFiltered: query.count,
19
+ data: data(query.query)
20
+ }
21
+ end
22
+
23
+ private
24
+
25
+ def data(records)
26
+ raise NotImplementedError
27
+ end
28
+
29
+ def extract_query_params
30
+ # Search term
31
+ search = params[:search]
32
+ if search.present? && search[:value].present?
33
+ query.search_term = search[:value] if search.present?
34
+ end
35
+
36
+ # Sort order
37
+ if order_by = extract_sort(params[:order])
38
+ query.order_by = order_by
39
+ end
40
+
41
+ # Pagination
42
+ unless params[:length].present? && params[:length] == '-1'
43
+ query.start = params[:start].to_i
44
+ query.page_size = params.fetch(:length, 10).to_i
45
+ end
46
+ end
47
+
48
+ def extract_sort(order)
49
+ return nil unless order.present?
50
+
51
+ ap order
52
+ sort_by = {}
53
+ order.each_pair do |key, value|
54
+ name = query.display_columns[value[:column].to_i]
55
+ raise(ArgumentError, "Invalid column id: #{value[:column]}. Must fit #{query.display_columns.inspect}") unless name.present?
56
+ sort_by[name] = value[:dir]
57
+ end
58
+ sort_by
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,47 @@
1
+ module RocketJobMissionControl
2
+ class ActiveWorkersDatatable < AbstractDatatable
3
+ delegate :job_path, :state_icon, to: :@view
4
+
5
+ def initialize(view, query)
6
+ super(view, query)
7
+ end
8
+
9
+ private
10
+
11
+ def extract_query_params
12
+ @query.order_by = nil
13
+ end
14
+
15
+ def data(active_workers)
16
+ active_workers.collect do |active_worker|
17
+ {
18
+ '0' => worker_name_with_icon(active_worker, active_worker.job),
19
+ '1' => job_name_with_link(active_worker.job.class.name, active_worker.job.id),
20
+ '2' => h(active_worker.job.description.try!(:truncate, 50)),
21
+ '3' => h("#{active_worker.duration} ago"),
22
+ 'DT_RowClass' => 'card callout callout-running'
23
+ }
24
+ end
25
+ end
26
+
27
+ def worker_name_with_icon(active_worker, job)
28
+ state = active_worker.zombie? ? :zombie : job.state
29
+ <<-EOS
30
+ <i class="fa #{state_icon(state)}" style="font-size: 75%" title="#{state}"></i>
31
+ #{active_worker.name}
32
+ EOS
33
+ end
34
+
35
+ def job_name_with_link(job_class_name, job_id)
36
+ <<-EOS
37
+ <a href="#{job_path(job_id)}">
38
+ #{job_class_name}
39
+ </a>
40
+ EOS
41
+ end
42
+
43
+ def duration(started_at)
44
+ "#{RocketJob.seconds_as_duration(Time.now - started_at)} ago" if started_at
45
+ end
46
+ end
47
+ end
@@ -2,7 +2,7 @@ module RocketJobMissionControl
2
2
  class CompletedJobsDatatable < JobsDatatable
3
3
  private
4
4
 
5
- def data
5
+ def data(jobs)
6
6
  jobs.map do |job|
7
7
  {
8
8
  '0' => class_with_link(job),
@@ -1,88 +1,26 @@
1
1
  module RocketJobMissionControl
2
- class DirmonEntriesDatatable
3
- delegate :params,
4
- :link_to,
5
- :dirmon_entry_path,
6
- :state_icon,
7
- :render, to: :@view
2
+ class DirmonEntriesDatatable < AbstractDatatable
3
+ delegate :dirmon_entry_path, :state_icon, to: :@view
8
4
 
9
- delegate :h, to: 'ERB::Util'
10
-
11
- def initialize(view, dirmons)
12
- @view = view
13
- @unfiltered_dirmons = dirmons
14
- end
15
-
16
- def as_json(options = {})
17
- {
18
- :draw => params[:draw].to_i,
19
- :recordsTotal => get_raw_records.count,
20
- :recordsFiltered => filter_records(get_raw_records).count,
21
- :data => data
22
- }
5
+ def initialize(view, query)
6
+ query.display_columns = %w[name _type pattern]
7
+ query.search_columns = [:job_class_name, :name, :pattern]
8
+ super(view, query)
23
9
  end
24
10
 
25
11
  private
26
12
 
27
- def data
13
+ def data(dirmons)
28
14
  dirmons.map do |dirmon|
29
15
  {
30
- '0' => name_with_link(dirmon),
31
- '1' => h(dirmon.job_class_name),
32
- '2' => h(dirmon.pattern.try(:truncate, 80)),
16
+ '0' => name_with_link(dirmon),
17
+ '1' => h(dirmon.job_class_name),
18
+ '2' => h(dirmon.pattern.try(:truncate, 80)),
33
19
  'DT_RowClass' => "card callout callout-#{dirmon.state}"
34
20
  }
35
21
  end
36
22
  end
37
23
 
38
- def get_raw_records
39
- @unfiltered_dirmons
40
- end
41
-
42
- def dirmons
43
- @dirmons ||= fetch_dirmons
44
- end
45
-
46
- def fetch_dirmons
47
- records = get_raw_records
48
- records = sort_records(records) if params[:order].present?
49
- records = filter_records(records) if params[:search].present?
50
- records = paginate_records(records) unless params[:length].present? && params[:length] == '-1'
51
- records
52
- end
53
-
54
- def page
55
- (params[:start].to_i / per_page) + 1
56
- end
57
-
58
- def per_page
59
- params.fetch(:length, 10).to_i
60
- end
61
-
62
- def sort_records(records)
63
- sort_by = {}
64
- params[:order].keys.each do |key|
65
- sort_by[sort_column(params[:order][key][:column])] = params[:order][key][:dir]
66
- end
67
- records.sort(sort_by)
68
- end
69
-
70
- def sort_column(index)
71
- columns = %w[name max_threads started_at heartbeat.updated_at]
72
- columns[index.to_i]
73
- end
74
-
75
- def filter_records(records)
76
- return records unless (params[:search].present? && params[:search][:value].present?)
77
- conditions = params[:search][:value]#build_conditions_for(params[:search][:value])
78
- records = RocketJobMissionControl::DirmonEntries::Search.new(conditions, records).execute if conditions
79
- records
80
- end
81
-
82
- def paginate_records(records)
83
- Kaminari.paginate_array(records.all).page(page).per(per_page)
84
- end
85
-
86
24
  def name_with_link(dirmon)
87
25
  <<-EOS
88
26
  <a href="#{dirmon_entry_path(dirmon.id)}">
@@ -2,7 +2,7 @@ module RocketJobMissionControl
2
2
  class InterruptedJobsDatatable < JobsDatatable
3
3
  private
4
4
 
5
- def data
5
+ def data(jobs)
6
6
  jobs.map do |job|
7
7
  {
8
8
  '0' => class_with_link(job),
@@ -1,55 +1,29 @@
1
1
  module RocketJobMissionControl
2
- class JobsDatatable
3
- delegate :params, :link_to, :job_path, :job_icon, :edit_job_path,
4
- :abort_job_path, :job_path, :fail_job_path, :run_now_job_path, :pause_job_path,
5
- :resume_job_path, :retry_job_path, :job_failures_path, :job_action_link, to: :@view
6
- delegate :h, to: 'ERB::Util'
2
+ class JobsDatatable < AbstractDatatable
3
+ delegate :job_path, :job_icon, :edit_job_path,
4
+ :abort_job_path, :job_path, :fail_job_path, :run_now_job_path, :pause_job_path,
5
+ :resume_job_path, :retry_job_path, :job_failures_path, :job_action_link, :exceptions_job_path, to: :@view
7
6
 
8
- def initialize(view, jobs)
9
- @view = view
10
- @unfiltered_jobs = jobs
11
- end
12
-
13
- def as_json(options = {})
14
- {
15
- :draw => params[:draw].to_i,
16
- :recordsTotal => get_raw_records.count,
17
- :recordsFiltered => filter_records(get_raw_records).count,
18
- :data => data
19
- }
7
+ def initialize(view, query)
8
+ query.search_columns = [:_type, :description]
9
+ super(view, query)
20
10
  end
21
11
 
22
12
  private
23
13
 
24
- def data
14
+ def data(jobs)
25
15
  jobs.map do |job|
26
16
  {
27
- '0' => class_with_link(job),
28
- '1' => h(job.description.try(:truncate, 50)),
29
- '2' => h(job.created_at),
30
- '3' => h(job.duration),
31
- '4' => action_buttons(job),
17
+ '0' => class_with_link(job),
18
+ '1' => h(job.description.try(:truncate, 50)),
19
+ '2' => h(job.created_at),
20
+ '3' => h(job.duration),
21
+ '4' => action_buttons(job),
32
22
  'DT_RowClass' => "card callout callout-#{job.state}"
33
23
  }
34
24
  end
35
25
  end
36
26
 
37
- def get_raw_records
38
- @unfiltered_jobs
39
- end
40
-
41
- def jobs
42
- @jobs ||= fetch_jobs
43
- end
44
-
45
- def fetch_jobs
46
- records = get_raw_records
47
- records = sort_records(records) if params[:order].present?
48
- records = filter_records(records) if params[:search].present?
49
- records = paginate_records(records) unless params[:length].present? && params[:length] == '-1'
50
- records
51
- end
52
-
53
27
  def class_with_link(job)
54
28
  <<-EOS
55
29
  <a class='job-link' href="#{job_path(job.id)}">
@@ -60,23 +34,11 @@ module RocketJobMissionControl
60
34
  end
61
35
 
62
36
  def action_buttons(job)
63
- events = valid_events(job)
37
+ events = valid_events(job)
64
38
  buttons = "<div class='inline-job-actions'>"
65
- unless job.completed? || job.aborted?
66
- buttons += "#{ link_to( 'Edit', edit_job_path(job), class: 'btn btn-default', title: 'Edit job') }"
67
- end
68
39
  if job.scheduled?
69
40
  buttons += "#{ job_action_link('Run', run_now_job_path(job), :patch) }"
70
41
  end
71
- if events.include?(:abort)
72
- buttons += "#{ job_action_link('Abort', abort_job_path(job), :patch) }"
73
- end
74
- if job.completed? || job.aborted?
75
- buttons += "#{ job_action_link('Destroy', job_path(job), :delete) }"
76
- end
77
- if events.include?(:fail)
78
- buttons += "#{ job_action_link('Fail', fail_job_path(job), :patch) }"
79
- end
80
42
  if events.include?(:pause)
81
43
  buttons += "#{ job_action_link('Pause', pause_job_path(job), :patch) }"
82
44
  end
@@ -86,50 +48,13 @@ module RocketJobMissionControl
86
48
  if events.include?(:retry)
87
49
  buttons += "#{ job_action_link('Retry', retry_job_path(job), :patch) }"
88
50
  end
89
- if job.respond_to?(:input) && job.input.failed_count > 0
90
- buttons += "#{ link_to('View Errors', job_failures_path(job), class: 'btn btn-default') }"
91
- end
51
+ buttons += "#{ job_action_link('Destroy', job_path(job), :delete) }"
92
52
  buttons += "</div>"
93
53
  end
94
54
 
95
55
  def valid_events(job)
96
- job.aasm.events.collect{ |e| e.name }
56
+ job.aasm.events.collect(&:name)
97
57
  end
98
58
 
99
- def page
100
- (params[:start].to_i / per_page) + 1
101
- end
102
-
103
- def per_page
104
- params.fetch(:length, 10).to_i
105
- end
106
-
107
- def sort_records(records)
108
- sort_by = {}
109
- params[:order].keys.each do |key|
110
- sort_by[sort_column(params[:order][key][:column])] = params[:order][key][:dir]
111
- end
112
- records.sort(sort_by)
113
- end
114
-
115
- def counts
116
- RocketJob::Job.counts_by_state
117
- end
118
-
119
- def sort_column(index)
120
- columns = %w[_type description completed_at]
121
- columns[index.to_i]
122
- end
123
-
124
- def filter_records(records)
125
- return records unless (params[:search].present? && params[:search][:value].present?)
126
- conditions = params[:search][:value]
127
- records = RocketJobMissionControl::Jobs::Search.new(conditions, records).execute if conditions
128
- records
129
- end
130
-
131
- def paginate_records(records)
132
- Kaminari.paginate_array(records.all).page(page).per(per_page)
133
- end
134
59
  end
135
60
  end
@@ -2,7 +2,7 @@ module RocketJobMissionControl
2
2
  class QueuedJobsDatatable < JobsDatatable
3
3
  private
4
4
 
5
- def data
5
+ def data(jobs)
6
6
  jobs.map do |job|
7
7
  {
8
8
  '0' => class_with_link(job),
@@ -2,15 +2,15 @@ module RocketJobMissionControl
2
2
  class RunningJobsDatatable < JobsDatatable
3
3
  private
4
4
 
5
- def data
5
+ def data(jobs)
6
6
  jobs.map do |job|
7
7
  {
8
- '0' => class_with_link(job),
9
- '1' => h(job.description.try(:truncate, 50)),
10
- '2' => progress(job),
11
- '3' => h(job.priority),
12
- '4' => h(started(job)),
13
- '5' => action_buttons(job),
8
+ '0' => class_with_link(job),
9
+ '1' => h(job.description.try(:truncate, 50)),
10
+ '2' => progress(job),
11
+ '3' => h(job.priority),
12
+ '4' => h(started(job)),
13
+ '5' => action_buttons(job),
14
14
  'DT_RowClass' => "card callout callout-#{job.state}"
15
15
  }
16
16
  end
@@ -22,11 +22,22 @@ module RocketJobMissionControl
22
22
  end
23
23
 
24
24
  def progress(job)
25
- <<-EOS
26
- <div class='progress'>
27
- <div class='progress-bar' style="width: #{job.percent_complete}%;", title="#{job.percent_complete}% complete."></div>
28
- </div>
29
- EOS
25
+ if (sub_state = job.attributes['sub_state']) && [:before, :after].include?(sub_state)
26
+ <<-EOS
27
+ <div class="job-status">
28
+ <div class="job-state">
29
+ <div class="left">Batch</div>
30
+ <div class="right running">#{sub_state}</div>
31
+ </div>
32
+ </div>
33
+ EOS
34
+ else
35
+ <<-EOS
36
+ <div class='progress'>
37
+ <div class='progress-bar' style="width: #{job.percent_complete}%;", title="#{job.percent_complete}% complete."></div>
38
+ </div>
39
+ EOS
40
+ end
30
41
  end
31
42
 
32
43
  def started(job)
@@ -2,7 +2,7 @@ module RocketJobMissionControl
2
2
  class ScheduledJobsDatatable < JobsDatatable
3
3
  private
4
4
 
5
- def data
5
+ def data(jobs)
6
6
  jobs.map do |job|
7
7
  {
8
8
  '0' => class_with_link(job),
@@ -0,0 +1,67 @@
1
+ module RocketJobMissionControl
2
+ class ServersDatatable < AbstractDatatable
3
+ delegate :server_icon, :server_path, :stop_server_path, :resume_server_path, :pause_server_path, :server_card_class, to: :@view
4
+
5
+ def initialize(view, query)
6
+ query.display_columns = %w[name heartbeat.workers started_at heartbeat.updated_at]
7
+ query.search_columns = [:name]
8
+ super(view, query)
9
+ end
10
+
11
+ private
12
+
13
+ def data(servers)
14
+ servers.collect do |server|
15
+ {
16
+ '0' => name_with_icon(server),
17
+ '1' => h(threads(server)),
18
+ '2' => h(started_ago(server)),
19
+ '3' => h(time_since_heartbeat(server)),
20
+ '4' => action_links_html(server),
21
+ 'DT_RowClass' => "card callout #{server_card_class(server)}"
22
+ }
23
+ end
24
+ end
25
+
26
+ def name_with_icon(server)
27
+ <<-EOS
28
+ <i class="fa #{server_icon(server)}" style="font-size: 75%" title="#{server.state}"></i>
29
+ #{server.name}
30
+ EOS
31
+ end
32
+
33
+ def threads(server)
34
+ "#{server.heartbeat.workers.to_i}/#{server.max_workers}"
35
+ end
36
+
37
+ def started_ago(server)
38
+ "#{RocketJob.seconds_as_duration(Time.now - server.started_at)} ago"
39
+ end
40
+
41
+ def time_since_heartbeat(server)
42
+ "#{RocketJob.seconds_as_duration(Time.now - server.heartbeat.updated_at)} ago"
43
+ end
44
+
45
+ def action_links_html(server)
46
+ actions = '<div class="actions">'
47
+ if server.stopping?
48
+ actions += "Server is stopping..."
49
+ confirmation = ''
50
+ unless server.zombie?
51
+ confirmation << "Warning!\n\nDestroying this server will hard kill its active workers/jobs.\nKilled jobs will be requeued for processing on another worker.\n\n"
52
+ end
53
+ confirmation << "Are you sure you want to destroy #{server.name} ?"
54
+ actions += "#{ link_to "destroy", server_path(server), method: :delete, class: 'btn btn-danger', data: {confirm: confirmation} }"
55
+ else
56
+ if server.paused?
57
+ actions += "#{ link_to "resume", resume_server_path(server), method: :patch, class: 'btn btn-default', data: {confirm: "Resume this server?"} }"
58
+ else
59
+ actions += "#{ link_to "pause", pause_server_path(server), method: :patch, class: 'btn btn-default', data: {confirm: "Pause this server?"} }"
60
+ end
61
+ actions += "#{ link_to "stop", stop_server_path(server), method: :patch, class: 'btn btn-danger', data: {confirm: "Stop this server?"} }"
62
+ end
63
+ actions += '</div>'
64
+ end
65
+
66
+ end
67
+ end