toro 0.2.1 → 0.2.2

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/.gitignore +7 -0
  3. data/Appraisals +7 -0
  4. data/Gemfile +3 -0
  5. data/app/assets/javascripts/toro/monitor/abstract_jobs_table.js.coffee +188 -0
  6. data/app/assets/javascripts/toro/monitor/application.js.coffee +14 -0
  7. data/app/assets/javascripts/toro/monitor/chart.js.coffee +203 -0
  8. data/app/assets/javascripts/toro/monitor/initialize.js.coffee.erb +25 -0
  9. data/app/assets/javascripts/toro/monitor/jobs_table.js.coffee +63 -0
  10. data/app/assets/javascripts/toro/monitor/processes_table.js.coffee +96 -0
  11. data/app/assets/javascripts/toro/monitor/queue_jobs_table.js.coffee +91 -0
  12. data/app/assets/stylesheets/toro/monitor/application.css.sass +5 -0
  13. data/app/assets/stylesheets/toro/monitor/jobs_table.css.sass +69 -0
  14. data/app/assets/stylesheets/toro/monitor/layout.css.sass +5 -0
  15. data/app/controllers/toro/monitor/api/jobs_controller.rb +65 -0
  16. data/app/controllers/toro/monitor/api/processes_controller.rb +13 -0
  17. data/app/controllers/toro/monitor/api/queues_controller.rb +24 -0
  18. data/app/controllers/toro/monitor/base_controller.rb +11 -0
  19. data/app/controllers/toro/monitor/jobs_controller.rb +11 -0
  20. data/app/controllers/toro/monitor/processes_controller.rb +8 -0
  21. data/app/controllers/toro/monitor/queues_controller.rb +9 -0
  22. data/app/datatables/toro/monitor/abstract_datatable.rb +71 -0
  23. data/app/datatables/toro/monitor/jobs_datatable.rb +60 -0
  24. data/app/datatables/toro/monitor/processes_datatable.rb +60 -0
  25. data/app/helpers/toro/monitor/toro_monitor_helper.rb +13 -0
  26. data/app/views/toro/monitor/jobs/_jobs.slim +14 -0
  27. data/app/views/toro/monitor/jobs/chart.slim +1 -0
  28. data/app/views/toro/monitor/jobs/index.slim +1 -0
  29. data/app/views/toro/monitor/layouts/application.slim +32 -0
  30. data/app/views/toro/monitor/processes/index.slim +14 -0
  31. data/app/views/toro/monitor/queues/_jobs.slim +14 -0
  32. data/app/views/toro/monitor/queues/index.slim +5 -0
  33. data/config/routes.rb +16 -0
  34. data/examples/chart.png +0 -0
  35. data/examples/job.png +0 -0
  36. data/examples/jobs.png +0 -0
  37. data/examples/processes.png +0 -0
  38. data/examples/queues.png +0 -0
  39. data/gemfiles/rails_3.gemfile +7 -0
  40. data/gemfiles/rails_3.gemfile.lock +146 -0
  41. data/gemfiles/rails_4.gemfile +7 -0
  42. data/gemfiles/rails_4.gemfile.lock +147 -0
  43. data/lib/toro/version.rb +1 -1
  44. data/spec/config/database.yml.example +6 -0
  45. data/spec/lib/cli_spec.rb +28 -0
  46. data/spec/lib/client_spec.rb +26 -0
  47. data/spec/lib/fetcher_spec.rb +105 -0
  48. data/spec/lib/job_spec.rb +17 -0
  49. data/spec/lib/listener_spec.rb +72 -0
  50. data/spec/lib/manager_spec.rb +78 -0
  51. data/spec/lib/middleware/server/error_spec.rb +18 -0
  52. data/spec/lib/middleware/server/error_storage_spec.rb +22 -0
  53. data/spec/lib/middleware/server/properties_spec.rb +18 -0
  54. data/spec/lib/middleware/server/retry_spec.rb +38 -0
  55. data/spec/lib/processor_spec.rb +33 -0
  56. data/spec/lib/worker_spec.rb +50 -0
  57. data/spec/spec_helper.rb +31 -0
  58. data/spec/support/active_record_spec_helper.rb +18 -0
  59. data/spec/support/jobs_helper.rb +4 -0
  60. data/spec/support/workers_helper.rb +56 -0
  61. data/toro.gemspec +29 -0
  62. data/vendor/assets/javascripts/toro/monitor/bootstrap-select.min.js +1 -0
  63. data/vendor/assets/javascripts/toro/monitor/bootstrap.min.js +6 -0
  64. data/vendor/assets/javascripts/toro/monitor/d3.js +8795 -0
  65. data/vendor/assets/javascripts/toro/monitor/datatables.bootstrap.js +96 -0
  66. data/vendor/assets/javascripts/toro/monitor/datatables.js +256 -0
  67. data/vendor/assets/javascripts/toro/monitor/datatables.standing_redraw.js.coffee +5 -0
  68. data/vendor/assets/javascripts/toro/monitor/jquery.timeago.js +27 -0
  69. data/vendor/assets/stylesheets/toro/monitor/bootstrap-select.min.css +1 -0
  70. data/vendor/assets/stylesheets/toro/monitor/bootstrap.min.css +9 -0
  71. metadata +103 -5
@@ -0,0 +1,63 @@
1
+ class ToroMonitor.JobsTable extends ToroMonitor.AbstractJobsTable
2
+
3
+ initialize: =>
4
+ options =
5
+ table_selector: 'table.jobs'
6
+ columns:
7
+ id: 0
8
+ started_by: 1
9
+ queue: 2
10
+ class_name: 3
11
+ name: 4
12
+ created_at: 5
13
+ started_at: 6
14
+ duration: 7
15
+ message: 8
16
+ status: 9
17
+ properties: 10
18
+ args: 11
19
+ column_options: [
20
+ { bVisible: false }
21
+ { bVisible: false }
22
+ null
23
+ null
24
+ { bSortable: false }
25
+ {
26
+ fnRender: (oObj) =>
27
+ @format_time_ago(oObj.aData[@columns.created_at])
28
+ }
29
+ {
30
+ fnRender: (oObj) =>
31
+ @format_time_ago(oObj.aData[@columns.started_at])
32
+ }
33
+ null
34
+ { bSortable: false }
35
+ {
36
+ fnRender: (oObj) =>
37
+ status = oObj.aData[@columns.status]
38
+ class_name = switch status
39
+ when 'failed'
40
+ 'danger'
41
+ when 'complete'
42
+ 'success'
43
+ when 'running'
44
+ 'primary'
45
+ when 'scheduled'
46
+ 'inverse'
47
+ else
48
+ 'info'
49
+ html = """<a href="#" class="btn btn-#{class_name} btn-mini status-value">#{oObj.aData[@columns.status]}</a>"""
50
+ if status == 'failed'
51
+ html += """<a href="#" class="btn btn-mini btn-primary retry-job" data-job-id="#{oObj.aData[@columns.id]}">Retry<a>"""
52
+ """<span class="action-buttons">#{html}</span>"""
53
+ }
54
+ { bVisible: false }
55
+ { bVisible: false }
56
+ ]
57
+
58
+ return null unless $(options.table_selector).length
59
+
60
+ @initialize_with_options(options)
61
+
62
+ $ ->
63
+ new ToroMonitor.JobsTable
@@ -0,0 +1,96 @@
1
+ class ToroMonitor.ProcessesTable extends ToroMonitor.AbstractJobsTable
2
+
3
+ initialize: =>
4
+ @options =
5
+ table_selector: 'table.processes'
6
+ columns:
7
+ id: 0
8
+ started_by: 1
9
+ queue: 2
10
+ class_name: 3
11
+ name: 4
12
+ created_at: 5
13
+ started_at: 6
14
+ duration: 7
15
+ message: 8
16
+ status: 9
17
+ properties: 10
18
+ args: 11
19
+ column_options: [
20
+ { bVisible: false }
21
+ {
22
+ fnRender: (oObj) =>
23
+ """<span class="nowrap">#{oObj.aData[@columns.started_by]}</span>"""
24
+ }
25
+ null
26
+ null
27
+ { bSortable: false }
28
+ {
29
+ fnRender: (oObj) =>
30
+ @format_time_ago(oObj.aData[@columns.created_at])
31
+ }
32
+ {
33
+ fnRender: (oObj) =>
34
+ @format_time_ago(oObj.aData[@columns.started_at])
35
+ }
36
+ null
37
+ { bSortable: false }
38
+ {
39
+ fnRender: (oObj) =>
40
+ status = oObj.aData[@columns.status]
41
+ class_name = switch status
42
+ when 'failed'
43
+ 'danger'
44
+ when 'complete'
45
+ 'success'
46
+ when 'running'
47
+ 'primary'
48
+ else
49
+ 'info'
50
+ html = """<a href="#" class="btn btn-#{class_name} btn-mini status-value">#{oObj.aData[@columns.status]}</a>"""
51
+ if status == 'failed'
52
+ html += """<a href="#" class="btn btn-mini btn-primary retry-job" data-job-id="#{oObj.aData[@columns.id]}">Retry<a>"""
53
+ """<span class="action-buttons">#{html}</span>"""
54
+ }
55
+ { bVisible: false }
56
+ { bVisible: false }
57
+ ]
58
+
59
+ return null unless $(@options.table_selector).length
60
+
61
+ @table = $(@options.table_selector)
62
+
63
+ @columns = @options.columns
64
+ @status_filter = null
65
+
66
+ @table.dataTable
67
+ bProcessing: true
68
+ bServerSide: true
69
+ sAjaxSource: @table.data('source')
70
+ iDisplayLength: 10
71
+ aaSorting: [[@columns.created_at, 'desc']]
72
+ sPaginationType: 'bootstrap'
73
+ aoColumns: @options.column_options
74
+ oLanguage:
75
+ sInfo: '_TOTAL_ jobs'
76
+ sInfoFiltered: ' (filtered from _MAX_)'
77
+ sLengthMenu: 'Per page: _MENU_'
78
+ sSearch: ''
79
+ fnRowCallback: (nRow, aData, iDisplayIndex) =>
80
+ $('.timeago', nRow).timeago()
81
+ fnInitComplete: () =>
82
+ filter_container = @table.siblings('.dataTables_filter')
83
+ filter_container.find('input').attr('placeholder', 'Search...')
84
+ fnServerData: (sSource, aoData, fnCallback) =>
85
+ $.each @api_params, (key, value) =>
86
+ aoData.push
87
+ name: key
88
+ value: @api_params[key]
89
+ $.getJSON sSource, aoData, (json) -> fnCallback(json)
90
+
91
+ @table.parents('.dataTables_wrapper').addClass('jobs-table-wrapper')
92
+
93
+ @initialize_ui()
94
+
95
+ $ ->
96
+ new ToroMonitor.ProcessesTable
@@ -0,0 +1,91 @@
1
+ class ToroMonitor.QueueJobsTable extends ToroMonitor.AbstractJobsTable
2
+
3
+ initialize: =>
4
+ options =
5
+ table_selector: 'table.queue-jobs'
6
+ columns:
7
+ id: 0
8
+ started_by: 1
9
+ queue: 2
10
+ class_name: 3
11
+ name: 4
12
+ created_at: 5
13
+ started_at: 6
14
+ duration: 7
15
+ message: 8
16
+ status: 9
17
+ properties: 10
18
+ args: 11
19
+ column_options: [
20
+ { bVisible: false }
21
+ { bVisible: false }
22
+ { bVisible: false }
23
+ null
24
+ { bSortable: false }
25
+ {
26
+ fnRender: (oObj) =>
27
+ @format_time_ago(oObj.aData[@columns.created_at])
28
+ }
29
+ {
30
+ fnRender: (oObj) =>
31
+ @format_time_ago(oObj.aData[@columns.started_at])
32
+ }
33
+ null
34
+ { bSortable: false }
35
+ {
36
+ fnRender: (oObj) =>
37
+ status = oObj.aData[@columns.status]
38
+ class_name = switch status
39
+ when 'failed'
40
+ 'danger'
41
+ when 'complete'
42
+ 'success'
43
+ when 'running'
44
+ 'primary'
45
+ else
46
+ 'info'
47
+ html = """<a href="#" class="btn btn-#{class_name} btn-mini status-value">#{oObj.aData[@columns.status]}</a>"""
48
+ if status == 'failed'
49
+ html += """<a href="#" class="btn btn-mini btn-primary retry-job" data-job-id="#{oObj.aData[@columns.id]}">Retry<a>"""
50
+ """<span class="action-buttons">#{html}</span>"""
51
+ }
52
+ { bVisible: false }
53
+ { bVisible: false }
54
+ ]
55
+ return null unless $(options.table_selector).length
56
+
57
+ @queue_select = $('[name=queue_select]')
58
+ @queue_select.selectpicker()
59
+ @queue_select.change =>
60
+ @load_selected_queue()
61
+ @reload_table()
62
+ @queue_select.val(@queue_select.find('option:first').val())
63
+ @load_selected_queue()
64
+
65
+ @initialize_with_options(options)
66
+
67
+ show_queue_stats: =>
68
+ $.getJSON ToroMonitor.settings.api_url("queues/#{@queue}"), (stats) =>
69
+ if stats
70
+ header_cells = ""
71
+ value_cells = ""
72
+ $.each stats.status_counts, (status, count) =>
73
+ header_cells += "<th>#{status}</th>"
74
+ value_cells += "<td>#{count}</td>"
75
+ html = """
76
+ <table class="table table-striped table-condensed table-bordered">
77
+ <tr>#{header_cells}</tr>
78
+ <tr>#{value_cells}</tr>
79
+ </table>
80
+ """
81
+ else
82
+ html = ''
83
+ $('.queue-stats').html(html)
84
+
85
+ load_selected_queue: =>
86
+ @queue = @queue_select.val()
87
+ @api_params['queue'] = @queue
88
+ @show_queue_stats()
89
+
90
+ $ ->
91
+ new ToroMonitor.QueueJobsTable
@@ -0,0 +1,5 @@
1
+ @import toro/monitor/bootstrap.min
2
+ @import toro/monitor/bootstrap-select.min
3
+ @import dataTables/bootstrap/2/jquery.dataTables.bootstrap
4
+ @import toro/monitor/layout
5
+ @import toro/monitor/jobs_table
@@ -0,0 +1,69 @@
1
+ div.dataTables_length label
2
+ float: right
3
+
4
+ .dataTables_processing
5
+ display: none
6
+
7
+ .modal-body
8
+ max-height: 500px
9
+
10
+ .dataTable
11
+ .nowrap
12
+ white-space: nowrap
13
+
14
+ .jobs-table
15
+ td
16
+ max-width: 200px
17
+ overflow: auto
18
+ text-overflow: ellipsis
19
+ white-space: nowrap
20
+
21
+ .jobs-table-wrapper
22
+ clear: both
23
+
24
+ .status-value
25
+ text-transform: capitalize
26
+
27
+ .status-filter
28
+ float: left
29
+ margin-right: 15px
30
+ button
31
+ text-transform: capitalize
32
+
33
+ .action-buttons
34
+ white-space: nowrap
35
+
36
+ .retry-job
37
+ margin-left: 5px
38
+
39
+ .dataTables_filter > label
40
+ float: left
41
+
42
+ .dataTables_filter > label > input
43
+ width: 120px
44
+
45
+ .queue-stats
46
+ float: right
47
+ font-size: 12px
48
+ margin-left: 10px
49
+
50
+ &:after
51
+ content: "."
52
+ display: block
53
+ height: 0
54
+ clear: both
55
+ visibility: hidden
56
+
57
+ table
58
+ margin-bottom: 10px
59
+
60
+ th, td
61
+ padding: 0 5px
62
+ text-align: center
63
+
64
+ th
65
+ text-transform: capitalize
66
+ font-weight: normal
67
+
68
+ td
69
+ font-weight: bold
@@ -0,0 +1,5 @@
1
+ body
2
+ padding-top: 60px
3
+
4
+ .navbar .brand
5
+ font-weight: bold
@@ -0,0 +1,65 @@
1
+ module Toro
2
+ module Monitor
3
+ module Api
4
+ class JobsController < ActionController::Base
5
+ protect_from_forgery
6
+
7
+ def index
8
+ render json: JobsDatatable.new(view_context)
9
+ end
10
+
11
+ def chart
12
+ queues_jobs = Job.select('queue, status').all.group_by(&:queue)
13
+ queues = []
14
+ statuses = Job.statuses
15
+ queues_status_counts = queues_jobs.collect do |queue, jobs|
16
+ statuses_jobs = jobs.group_by(&:status)
17
+ statuses_job_counts = statuses_jobs.collect do |status, jobs|
18
+ next unless statuses.include?(status)
19
+ [status, jobs.length]
20
+ end
21
+ statuses_job_counts = Hash[statuses_job_counts.compact]
22
+ queues << queue unless queues.include?(queue)
23
+ { queue: queue }.merge(statuses_job_counts)
24
+ end
25
+ queues_status_counts = queues_status_counts.sort_by { |q| q[:queue] }
26
+ render json: {
27
+ queues_status_counts: queues_status_counts,
28
+ statuses: statuses
29
+ }
30
+ end
31
+
32
+ def custom_views
33
+ job = Job.find(params[:id])
34
+ render json: {}, status: 404 and return if job.blank?
35
+
36
+ views = CustomViews.for_job(job)
37
+ views = views.collect do |view|
38
+ {
39
+ name: view[:name],
40
+ html: render_to_string(view[:path], locals: {job: job, path: view[:path]})
41
+ }
42
+ end
43
+ render json: views, status: :ok
44
+ end
45
+
46
+ def retry
47
+ id = params[:id]
48
+ render json: {}, status: 404 and return if id.blank?
49
+
50
+ job = Job.find(id)
51
+ render json: {}, status: 404 and return if job.blank?
52
+
53
+ args = job.args
54
+ worker = job.class_name.constantize
55
+ worker.perform_async(*args)
56
+ render json: {}, status: :ok
57
+ end
58
+
59
+ def statuses
60
+ render json: Toro::Job.statuses, status: :ok
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,13 @@
1
+ module Toro
2
+ module Monitor
3
+ module Api
4
+ class ProcessesController < ActionController::Base
5
+ protect_from_forgery
6
+
7
+ def index
8
+ render json: ProcessesDatatable.new(view_context)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ module Toro
2
+ module Monitor
3
+ module Api
4
+ class QueuesController < ActionController::Base
5
+ protect_from_forgery
6
+
7
+ def show
8
+ queue = params[:queue]
9
+ render json: {}, status: 404 and return if queue.blank?
10
+
11
+ status_counts = Toro::Job.where(queue: queue).count(group: 'status')
12
+ ordered_status_counts = {}
13
+ Job.statuses.each do |status|
14
+ ordered_status_counts[status] = status_counts.has_key?(status) ? status_counts[status] : 0
15
+ end
16
+ response = {
17
+ status_counts: ordered_status_counts
18
+ }
19
+ render json: response.to_json, status: :ok
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ module Toro
2
+ module Monitor
3
+ class BaseController < ActionController::Base
4
+ protect_from_forgery
5
+
6
+ layout 'toro/monitor/layouts/application'
7
+
8
+ helper Toro::Monitor::ToroMonitorHelper
9
+ end
10
+ end
11
+ end