rocketjob_mission_control 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +26 -0
  4. data/app/assets/javascripts/rocket_job_mission_control/application.js +18 -0
  5. data/app/assets/javascripts/rocket_job_mission_control/base.js.coffee +24 -0
  6. data/app/assets/javascripts/rocket_job_mission_control/jobs.js.coffee +11 -0
  7. data/app/assets/stylesheets/rocket_job_mission_control/application.scss +18 -0
  8. data/app/assets/stylesheets/rocket_job_mission_control/base.scss +468 -0
  9. data/app/assets/stylesheets/rocket_job_mission_control/bootstrap_and_overrides.scss +6 -0
  10. data/app/assets/stylesheets/rocket_job_mission_control/callout.scss +40 -0
  11. data/app/assets/stylesheets/rocket_job_mission_control/jobs.scss +16 -0
  12. data/app/assets/stylesheets/rocket_job_mission_control/jquery.bootstrap-touchspin.scss +45 -0
  13. data/app/controllers/rocket_job_mission_control/application_controller.rb +15 -0
  14. data/app/controllers/rocket_job_mission_control/jobs/inputs_controller.rb +9 -0
  15. data/app/controllers/rocket_job_mission_control/jobs/slices_controller.rb +10 -0
  16. data/app/controllers/rocket_job_mission_control/jobs_controller.rb +97 -0
  17. data/app/controllers/rocket_job_mission_control/workers_controller.rb +79 -0
  18. data/app/helpers/rocket_job_mission_control/application_helper.rb +16 -0
  19. data/app/helpers/rocket_job_mission_control/jobs_helper.rb +57 -0
  20. data/app/helpers/rocket_job_mission_control/workers_helper.rb +14 -0
  21. data/app/views/layouts/rocket_job_mission_control/application.html.haml +43 -0
  22. data/app/views/layouts/rocket_job_mission_control/partials/_header.html.haml +16 -0
  23. data/app/views/layouts/rocket_job_mission_control/partials/_sidebar.html.haml +20 -0
  24. data/app/views/rocket_job_mission_control/jobs/_list.html.haml +33 -0
  25. data/app/views/rocket_job_mission_control/jobs/_status.html.haml +39 -0
  26. data/app/views/rocket_job_mission_control/jobs/index.html.haml +8 -0
  27. data/app/views/rocket_job_mission_control/jobs/running.html.haml +37 -0
  28. data/app/views/rocket_job_mission_control/jobs/show.html.haml +50 -0
  29. data/app/views/rocket_job_mission_control/workers/_actions.html.haml +9 -0
  30. data/app/views/rocket_job_mission_control/workers/index.html.haml +51 -0
  31. data/config/locales/en.yml +57 -0
  32. data/config/routes.rb +28 -0
  33. data/lib/rocket_job_mission_control/engine.rb +15 -0
  34. data/lib/rocket_job_mission_control/version.rb +3 -0
  35. data/lib/rocketjob_mission_control.rb +4 -0
  36. data/lib/tasks/rocket_job_mission_control_tasks.rake +4 -0
  37. data/spec/controllers/application_controller_spec.rb +47 -0
  38. data/spec/controllers/jobs_controller_spec.rb +217 -0
  39. data/spec/controllers/workers_controller_spec.rb +125 -0
  40. data/spec/dummy/README.rdoc +28 -0
  41. data/spec/dummy/Rakefile +6 -0
  42. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  43. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  44. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  45. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  46. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  47. data/spec/dummy/bin/bundle +3 -0
  48. data/spec/dummy/bin/rails +4 -0
  49. data/spec/dummy/bin/rake +4 -0
  50. data/spec/dummy/config.ru +4 -0
  51. data/spec/dummy/config/application.rb +25 -0
  52. data/spec/dummy/config/boot.rb +5 -0
  53. data/spec/dummy/config/database.yml +25 -0
  54. data/spec/dummy/config/environment.rb +5 -0
  55. data/spec/dummy/config/environments/development.rb +34 -0
  56. data/spec/dummy/config/environments/test.rb +39 -0
  57. data/spec/dummy/config/initializers/assets.rb +8 -0
  58. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  59. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  60. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  61. data/spec/dummy/config/initializers/inflections.rb +16 -0
  62. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  63. data/spec/dummy/config/initializers/session_store.rb +3 -0
  64. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  65. data/spec/dummy/config/locales/en.yml +23 -0
  66. data/spec/dummy/config/routes.rb +4 -0
  67. data/spec/dummy/config/secrets.yml +22 -0
  68. data/spec/dummy/log/test.log +2459 -0
  69. data/spec/dummy/public/404.html +67 -0
  70. data/spec/dummy/public/422.html +67 -0
  71. data/spec/dummy/public/500.html +66 -0
  72. data/spec/dummy/public/favicon.ico +0 -0
  73. data/spec/helpers/jobs_helper_spec.rb +100 -0
  74. data/spec/rails_helper.rb +47 -0
  75. data/spec/spec_helper.rb +81 -0
  76. data/spec/views/workers/index.html.haml_spec.rb +17 -0
  77. data/vendor/assets/javascripts/jquery.bootstrap-touchspin.js +686 -0
  78. data/vendor/assets/javascripts/prism.js +6 -0
  79. data/vendor/assets/stylesheets/jquery.bootstrap-touchspin.css +45 -0
  80. data/vendor/assets/stylesheets/prism.scss +160 -0
  81. metadata +319 -0
@@ -0,0 +1,16 @@
1
+ #header
2
+ %a#menu-toggle.btn.btn-default.btn-blank.first{href: ""}
3
+ %i.fa.fa-bars
4
+ Menu
5
+
6
+ = link_to main_app.root_path, class: 'btn btn-default btn-blank last' do
7
+ %i.fa.fa-reply
8
+ Back Home
9
+
10
+ .clearfix
11
+
12
+ .brand
13
+ = link_to root_path, class: 'text-center' do
14
+ %i.fa.fa-rocket
15
+ rocket
16
+ .color job
@@ -0,0 +1,20 @@
1
+ #sidebar-wrapper
2
+ %ul.sidebar-nav
3
+ %li.sidebar-brand
4
+ %a#menu-close.menu-close
5
+ %i.fa.fa-times
6
+
7
+ .clearfix
8
+
9
+ %li
10
+ = link_to jobs_path, class: active_page(jobs_path) do
11
+ %i.fa.fa-home
12
+ Jobs
13
+ %li
14
+ = link_to workers_path, class: active_page(workers_path) do
15
+ %i.fa.fa-database
16
+ Workers
17
+ %li
18
+ = link_to running_jobs_path, class: active_page(running_jobs_path) do
19
+ %i.fa.fa-circle-o-notch
20
+ Running
@@ -0,0 +1,33 @@
1
+ .col-md-4.job-list
2
+ .filter.btn-group.btn-group-justified{ style: 'padding: 1px 1px 1px 1px;', data: { toggle: 'buttons' } }
3
+ - %w( queued running failed completed ).each do |state|
4
+ %label.btn.btn-default.state-toggle{ class: ('active' if @states.include?(state.to_s) || @states.empty?), title: state.capitalize}
5
+ = check_box_tag(state, state, (@states.include?(state.to_s) || @states.empty?))
6
+ %i.fa{class: job_state_icon(state.to_sym)}
7
+ .clearfix
8
+ .list
9
+ - if jobs.empty?
10
+ .card.inner
11
+ .title
12
+ .lead.text-center No Jobs Found.
13
+ - else
14
+ - jobs.each do |job|
15
+ = link_to job_path(id: job.id, states: @states), class: job_card_class(job) do
16
+ .inner
17
+ .title
18
+ .lead{class: job_selected_class(job, @job)}
19
+ %i.fa{class: job_state_icon(job.state), style: 'font-size: 75%;', title: job.state}
20
+ = job_title(job)
21
+
22
+ .description= job.description.try(:truncate, 50)
23
+ .info
24
+ .duration.pull-left
25
+ - if job.failed?
26
+ %i.fa.fa-bomb
27
+ failed
28
+ - else
29
+ %i.fa.fa-clock-o
30
+ = job.duration
31
+ - if job.running?
32
+ .progress.pull-right
33
+ .progress-bar{ style: "width: #{job.percent_complete}%;", title: "#{job.percent_complete}% percent complete."}
@@ -0,0 +1,39 @@
1
+ - status = job.status(Time.zone)
2
+ .id
3
+ %label ID:
4
+ = status.delete('id')
5
+ .status-message
6
+ %label Description:
7
+ = status.delete('description')
8
+ .status-message
9
+ %label Duration:
10
+ = status.delete('duration')
11
+ .status-message
12
+ %label Record Count:
13
+ = status.delete('record_count')
14
+ .created_at
15
+ %label Created At:
16
+ = status.delete('created_at')
17
+ .status-message
18
+ %label Started At:
19
+ = status.delete('started_at')
20
+ .status-message
21
+ %label % Complete:
22
+ = status.delete('percent_complete')
23
+
24
+ - remaining = {}
25
+ - status.each_pair do |key, value|
26
+ .status-message
27
+ - if ['state', '_type'].include?(key)
28
+ - elsif value.kind_of?(Hash) || value.kind_of?(Array)
29
+ - remaining[key] = value
30
+ - else
31
+ %label= "#{key.to_s.titleize}:"
32
+ = value
33
+ .clearfix
34
+
35
+ - remaining.each_pair do |key, value|
36
+ .status-message
37
+ %label= "#{key.to_s.titleize}:"
38
+ %pre
39
+ %code= pretty_print_array_or_hash(value)
@@ -0,0 +1,8 @@
1
+ #queue
2
+ = render partial: 'list', locals: { jobs: @jobs }
3
+
4
+ .col-md-8.job-status
5
+ #job
6
+ .welcome_screen
7
+ %h1 Welcome to RocketJob!
8
+ %p High volume, priority based, Enterprise Batch Processing solution for Ruby
@@ -0,0 +1,37 @@
1
+ .col-md-10.col-md-offset-1
2
+ .jumbotron
3
+ %h3 There are currently #{@jobs.count} jobs running.
4
+ #accordion.panel-group
5
+ - @jobs.each do |job|
6
+ .panel.panel-default
7
+ .panel-heading
8
+ %h4.panel-title
9
+ %a.collapsed{href: "##{job.id}", data: {toggle: 'collapse', parent: '#accordion'} }
10
+ %i.fa.fa-plus-square-o{ style: 'font-size: 80%;'}
11
+ = job_title(job)
12
+ .panel-collapse.collapse{ id: job.id }
13
+ .panel-body
14
+ .info
15
+ .progress
16
+ .progress-bar{ style: "width: #{job.percent_complete}%; min-width: 1em;", title: "#{job.percent_complete}% percent complete."}
17
+ - if job.kind_of?(RocketJob::SlicedJob)
18
+ .slice_count
19
+ %label Slices Running:
20
+ = job.input.find_all { |s| s.state == :running }.count
21
+ - else
22
+ .worker
23
+ %label Worker:
24
+ = job.worker_name
25
+
26
+ = render partial: 'status', locals: { job: job }
27
+ - if job.kind_of?(RocketJob::SlicedJob)
28
+ %table.table
29
+ %tr
30
+ %th worker
31
+ %th parameters
32
+ - job.input.each('state' => 'running') do |slice|
33
+ %tr
34
+ %td= slice.worker_name
35
+ %td
36
+ %pre
37
+ %code= pretty_print_array_or_hash(slice.to_a)
@@ -0,0 +1,50 @@
1
+ #queue
2
+ = render partial: 'list', locals: { jobs: @jobs }
3
+
4
+ .col-md-8.job-status
5
+ #job
6
+ .lead= job_title(@job)
7
+
8
+ .job-state.inline-block
9
+ .left state
10
+ .right{ class: job_class(@job) }
11
+ = @job.state
12
+
13
+ .priority.pull-right
14
+ = form_for(@job, as: :job, url: job_path(@job), html: {method: :patch}) do |f|
15
+
16
+ .input-group.spinner{ "data-id" => @job.id }
17
+ = f.text_field(:priority, class: 'form-control')
18
+
19
+ .input-group-btn-vertical
20
+ %button#increase_priority.btn.btn-default.up{ type: 'button' }
21
+ %i.fa.fa-caret-up
22
+
23
+ %button#decrease_priority.btn.btn-default.down{ type: 'button' }
24
+ %i.fa.fa-caret-down
25
+
26
+ = submit_tag "set", class: "btn btn-sm btn-primary priority-set"
27
+
28
+ .job-retry.btn-group.pull-right{ style: 'margin-right: 1em;' }
29
+ - valid_events = @job.aasm.events.collect{ |e| e.name }
30
+ - if valid_events.include?(:abort)
31
+ = link_to "abort", abort_job_path(@job), method: :patch, class: 'btn btn-default'
32
+
33
+ - if @job.completed? || @job.aborted?
34
+ = link_to "destroy", job_path(@job), method: :delete, class: 'btn btn-default'
35
+
36
+ - if valid_events.include?(:fail)
37
+ = link_to "fail", fail_job_path(@job), method: :patch, class: 'btn btn-default'
38
+
39
+ - if valid_events.include?(:pause)
40
+ = link_to "pause", pause_job_path(@job), method: :patch, class: 'btn btn-default'
41
+
42
+ - if valid_events.include?(:resume)
43
+ = link_to "resume", resume_job_path(@job), method: :patch, class: 'btn btn-default'
44
+
45
+ - if valid_events.include?(:retry)
46
+ = link_to "retry", retry_job_path(@job), method: :patch, class: 'btn btn-default'
47
+
48
+ .clearfix
49
+
50
+ = render partial: 'status', locals: { job: @job }
@@ -0,0 +1,9 @@
1
+ - if worker.stopping?
2
+ %p Worker is stopping...
3
+ = link_to "destroy", worker_path(worker), method: :delete, class: 'btn btn-danger'
4
+ -else
5
+ = link_to "stop", stop_worker_path(worker), method: :patch, class: 'btn btn-danger'
6
+ - if worker.paused?
7
+ = link_to "resume", resume_worker_path(worker), method: :patch, class: 'btn btn-default'
8
+ - else
9
+ = link_to "pause", pause_worker_path(worker), method: :patch, class: 'btn btn-default'
@@ -0,0 +1,51 @@
1
+ #workers
2
+ - if @workers.present?
3
+ .col-lg-1
4
+ .dropdown
5
+ %button.btn.btn-primary.dropdown-toggle{ data: {toggle: 'dropdown'} }
6
+ Actions
7
+ %span.caret
8
+ %ul.dropdown-menu
9
+ %li= link_to("Stop All", update_all_workers_path(worker_action: :stop), method: :patch)
10
+ %li= link_to("Pause All", update_all_workers_path(worker_action: :pause), method: :patch)
11
+ %li= link_to("Resume All", update_all_workers_path(worker_action: :resume), method: :patch)
12
+
13
+ .grid
14
+ - @workers.each do |worker|
15
+ .card.col-xs-12.col-sm-6.col-md-3.col-lg-3
16
+ .inner.callout-top{ class: worker_card_class(worker) }
17
+ .icon
18
+ %i.fa.fa-database
19
+
20
+ .title
21
+ %h3
22
+ = worker.name
23
+
24
+ .state
25
+ %b State:
26
+ = worker.state
27
+
28
+ .threads.max_threads
29
+ %b Max Threads:
30
+ = worker.max_threads
31
+
32
+ .threads.current_threads
33
+ %b Current Threads:
34
+ = worker.heartbeat.current_threads.present? ? worker.heartbeat.current_threads : 0
35
+
36
+ .time
37
+ %b Started:
38
+ = worker.started_at.strftime("%b, %d %Y at %l:%M %p")
39
+
40
+ .actions
41
+ = render partial: 'actions', locals: { worker: worker }
42
+
43
+ - else
44
+ .no-workers
45
+ There are currently no workers.
46
+ %br
47
+ You can start a worker with the following command:
48
+
49
+ .code-block.col-md-6.col-md-offset-3
50
+ %pre
51
+ %code.language-bash bin/rocketjob
@@ -0,0 +1,57 @@
1
+ en:
2
+ # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()
3
+ datetime:
4
+ distance_in_words:
5
+ half_a_minute: "half a minute"
6
+ less_than_x_seconds:
7
+ one: "1 second" # default was: "less than 1 second"
8
+ other: "%{count} seconds" # default was: "less than %{count} seconds"
9
+ x_seconds:
10
+ one: "1 second"
11
+ other: "%{count} seconds"
12
+ less_than_x_minutes:
13
+ one: "a minute" # default was: "less than a minute"
14
+ other: "%{count} minutes" # default was: "less than %{count} minutes"
15
+ x_minutes:
16
+ one: "1 minute"
17
+ other: "%{count} minutes"
18
+ about_x_hours:
19
+ one: "1 hour" # default was: "about 1 hour"
20
+ other: "%{count} hours" # default was: "about %{count} hours"
21
+ x_days:
22
+ one: "1 day"
23
+ other: "%{count} days"
24
+ about_x_months:
25
+ one: "1 month" # default was: "about 1 month"
26
+ other: "%{count} months" # default was: "about %{count} months"
27
+ x_months:
28
+ one: "1 month"
29
+ other: "%{count} months"
30
+ about_x_years:
31
+ one: "1 year" # default was: "about 1 year"
32
+ other: "%{count} years" # default was: "about %{count} years"
33
+ over_x_years:
34
+ one: "1 year" # default was: "over 1 year"
35
+ other: "%{count} years" # default was: "over %{count} years"
36
+ almost_x_years:
37
+ one: "1 year" # default was: "almost 1 year"
38
+ other: "%{count} years" # default was: "almost %{count} years"
39
+ worker:
40
+ stop:
41
+ success: "Worker successfully stopped."
42
+ failure: "Error attempting to stop worker."
43
+ destroy:
44
+ success: "Worker successfully destroyed."
45
+ failure: "Error attempting to destroy worker."
46
+ pause:
47
+ success: "Worker successfully paused."
48
+ failure: "Error attempting to pause worker."
49
+ resume:
50
+ success: "Worker successfully resumed."
51
+ failure: "Error attempting to resume worker."
52
+ update_all:
53
+ success: "Workers have been %{worker_action}."
54
+ invalid: "Action not allowed."
55
+ job:
56
+ find:
57
+ :failure: "Could not find job with id: %{id}!"
@@ -0,0 +1,28 @@
1
+ RocketJobMissionControl::Engine.routes.draw do
2
+
3
+ resources :jobs, only: [:index, :show, :update, :destroy] do
4
+ member do
5
+ patch :abort
6
+ patch :fail
7
+ patch :pause
8
+ patch :resume
9
+ patch :retry
10
+ end
11
+ collection do
12
+ get :running
13
+ end
14
+ end
15
+
16
+ resources :workers, only: [:index, :destroy] do
17
+ member do
18
+ patch :stop
19
+ patch :pause
20
+ patch :resume
21
+ end
22
+ collection do
23
+ patch :update_all
24
+ end
25
+ end
26
+
27
+ root to: "jobs#index"
28
+ end
@@ -0,0 +1,15 @@
1
+ module RocketJobMissionControl
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace RocketJobMissionControl
4
+
5
+ require 'rocketjob'
6
+ require 'jquery-rails'
7
+ require 'haml'
8
+ require 'mongo'
9
+ require 'mongo_mapper'
10
+ require 'mongo_ha'
11
+ require 'bootstrap-sass'
12
+ require 'sass-rails'
13
+ require 'coffee-rails'
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module RocketJobMissionControl
2
+ VERSION = "0.9.2"
3
+ end
@@ -0,0 +1,4 @@
1
+ require "rocket_job_mission_control/engine"
2
+
3
+ module RocketJobMissionControl
4
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :rocket_job_mission_control do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,47 @@
1
+ require 'rails_helper'
2
+
3
+ module RocketJobMissionControl
4
+ RSpec.describe ApplicationController, type: :controller do
5
+ controller do
6
+ def index
7
+ @time_zone = Time.zone
8
+
9
+ render text: 'Time Zoned'
10
+ end
11
+ end
12
+
13
+ describe "#with_time_zone" do
14
+ let(:session_params) { {} }
15
+ let(:expected_time_zone) { 'UTC' }
16
+
17
+ before do
18
+ get :index, {}, session_params
19
+ end
20
+
21
+ context "with a session present" do
22
+ context "that contains a time zone" do
23
+ let(:expected_time_zone) { 'America/Los_Angeles' }
24
+ let(:session_params) { {'time_zone' => expected_time_zone} }
25
+
26
+ it "sets the time zone correctly" do
27
+ expect(assigns(:time_zone).name).to eq(expected_time_zone)
28
+ end
29
+ end
30
+
31
+ context 'that does not contain a time zone' do
32
+ let(:session_params) { { 'user_id' => '42' } }
33
+
34
+ it 'sets the time zone correctly' do
35
+ expect(assigns(:time_zone).name).to eq(expected_time_zone)
36
+ end
37
+ end
38
+ end
39
+
40
+ context "with no session present" do
41
+ it "sets the time zone correctly" do
42
+ expect(assigns(:time_zone).name).to eq(expected_time_zone)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,217 @@
1
+ require 'rails_helper'
2
+
3
+ module RocketJobMissionControl
4
+ RSpec.shared_examples "a jobs update controller" do
5
+ describe "with an invalid job id" do
6
+ before do
7
+ allow(RocketJob::Job).to receive(:find).and_return(nil)
8
+ do_action
9
+ end
10
+
11
+ it "redirects" do
12
+ expect(response).to redirect_to(jobs_path)
13
+ end
14
+
15
+ it "adds a flash alert message" do
16
+ expect(flash[:alert]).to eq(I18n.t(:failure, scope: [:job, :find], id: 42))
17
+ end
18
+ end
19
+ end
20
+
21
+ RSpec.describe JobsController do
22
+ routes { Engine.routes }
23
+
24
+ [:pause, :resume, :abort, :retry, :fail].each do |state|
25
+ describe "PATCH ##{state}" do
26
+ it_behaves_like "a jobs update controller" do
27
+ let(:do_action) { patch state, id: 42, job: {id: 42, priority: 12} }
28
+ end
29
+
30
+ describe "with a valid job id" do
31
+ let(:job) { spy(id: 42, to_param: 42) }
32
+
33
+ before do
34
+ allow(RocketJob::Job).to receive(:find).and_return(job)
35
+ patch state, id: 42, job: {id: 42, priority: 12}
36
+ end
37
+
38
+ it "redirects to the job" do
39
+ expect(response).to redirect_to(job_path(42))
40
+ end
41
+
42
+ it "transitions the job" do
43
+ expect(job).to have_received("#{state}!".to_sym)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ describe "PATCH #update" do
50
+ it_behaves_like "a jobs update controller" do
51
+ let(:do_action) { patch :update, id: 42, job: {id: 42, priority: 12} }
52
+ end
53
+
54
+ describe "with a valid job id" do
55
+ let(:job) { spy(id: 42, to_param: 42) }
56
+
57
+ before do
58
+ allow(RocketJob::Job).to receive(:find).and_return(job)
59
+ patch :update, id: 42, job: {id: 42, priority: 12}
60
+ end
61
+
62
+ it "redirects to the job" do
63
+ expect(response).to redirect_to(job_path(42))
64
+ end
65
+
66
+ it "updates the job correctly" do
67
+ expect(job).to have_received(:update_attributes!).with('priority' => '12')
68
+ end
69
+ end
70
+ end
71
+
72
+ describe "GET #show" do
73
+ let(:result) { spy(sort: []) }
74
+
75
+ before do
76
+ allow(RocketJob::Job).to receive(:limit).and_return(result)
77
+ end
78
+
79
+ describe "with an invalid job id" do
80
+ before do
81
+ allow(RocketJob::Job).to receive(:find).and_return(nil)
82
+ get :show, id: 42
83
+ end
84
+
85
+ it "redirects" do
86
+ expect(response).to redirect_to(jobs_path)
87
+ end
88
+
89
+ it "adds a flash alert message" do
90
+ expect(flash[:alert]).to eq(I18n.t(:failure, scope: [:job, :find], id: 42))
91
+ end
92
+ end
93
+
94
+ describe "with a valid job id" do
95
+ before do
96
+ allow(RocketJob::Job).to receive(:find).and_return('job')
97
+ get :show, id: 42
98
+ end
99
+
100
+ it "succeeds" do
101
+ expect(response.status).to be(200)
102
+ end
103
+
104
+ it "assigns the job" do
105
+ expect(assigns(:job)).to be_present
106
+ end
107
+
108
+ it "assigns the jobs" do
109
+ expect(assigns(:jobs)).to eq([])
110
+ end
111
+
112
+ it "grabs a sorted list of rocket jobs" do
113
+ expect(result).to have_received(:sort).with(created_at: :desc)
114
+ end
115
+ end
116
+ end
117
+
118
+ describe "GET #running" do
119
+ before do
120
+ allow(RocketJob::Job).to receive(:where).and_return([])
121
+ get :running
122
+ end
123
+
124
+ it { expect(response.status).to be(200) }
125
+
126
+ it "queries for running jobs" do
127
+ expect(RocketJob::Job).to have_received(:where).with(state: 'running')
128
+ end
129
+
130
+ it "returns expected jobs" do
131
+ expect(assigns[:jobs]).to eq([])
132
+ end
133
+ end
134
+
135
+ describe "GET #index" do
136
+ describe "with no jobs" do
137
+ let(:result) { spy(sort: []) }
138
+
139
+ before do
140
+ allow(RocketJob::Job).to receive(:limit).and_return(result)
141
+ get :index
142
+ end
143
+
144
+ it "succeeds" do
145
+ expect(response.status).to be(200)
146
+ end
147
+
148
+ it "grabs a sorted list of rocket jobs" do
149
+ expect(result).to have_received(:sort).with(created_at: :desc)
150
+ end
151
+
152
+ it "returns no jobs" do
153
+ expect(assigns(:jobs)).to eq([])
154
+ end
155
+ end
156
+
157
+ describe "with jobs" do
158
+ let(:result) { spy(sort: jobs) }
159
+ let(:jobs) { ['fake_job1', 'fake_job2'] }
160
+
161
+ before do
162
+ allow(RocketJob::Job).to receive(:limit).and_return(result)
163
+ end
164
+
165
+ describe "with no parameters" do
166
+ before { get :index }
167
+
168
+ it "succeeds" do
169
+ expect(response.status).to be(200)
170
+ end
171
+
172
+ it "grabs a sorted list of rocket jobs" do
173
+ expect(result).to have_received(:sort).with(created_at: :desc)
174
+ end
175
+
176
+ it "returns the jobs" do
177
+ expect(assigns(:jobs)).to match_array(jobs)
178
+ end
179
+ end
180
+
181
+ describe "with a state filter" do
182
+ before { get :index, states: states}
183
+
184
+ context "that is empty" do
185
+ let(:states) { [] }
186
+
187
+ it { expect(response.status).to be(200) }
188
+
189
+ it "grabs a sorted list of rocket jobs" do
190
+ expect(result).to have_received(:sort).with(created_at: :desc)
191
+ end
192
+
193
+ it "returns the jobs" do
194
+ expect(assigns(:jobs)).to match_array(jobs)
195
+ end
196
+ end
197
+
198
+ context "with a state" do
199
+ let(:query_spy) { spy(where: jobs) }
200
+ let(:result) { spy(sort: query_spy) }
201
+ let(:states) { ['completed', 'running'] }
202
+
203
+ it { expect(response.status).to be(200) }
204
+
205
+ it "grabs a filtered list of rocket jobs" do
206
+ expect(query_spy).to have_received(:where).with(state: states)
207
+ end
208
+
209
+ it "returns the jobs" do
210
+ expect(assigns(:jobs)).to match_array(jobs)
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end