rocketjob_mission_control 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/rocket_job_mission_control/dirmon_entries.js.coffee +18 -0
  3. data/app/assets/stylesheets/rocket_job_mission_control/base.scss +33 -59
  4. data/app/assets/stylesheets/rocket_job_mission_control/bootstrap_and_overrides.scss +12 -0
  5. data/app/assets/stylesheets/rocket_job_mission_control/callout.scss +8 -0
  6. data/app/assets/stylesheets/rocket_job_mission_control/jobs.scss +35 -0
  7. data/app/controllers/rocket_job_mission_control/application_controller.rb +0 -2
  8. data/app/controllers/rocket_job_mission_control/dirmon_entries_controller.rb +125 -0
  9. data/app/controllers/rocket_job_mission_control/jobs/failures_controller.rb +31 -0
  10. data/app/helpers/rocket_job_mission_control/application_helper.rb +7 -0
  11. data/app/helpers/rocket_job_mission_control/jobs_helper.rb +18 -11
  12. data/app/helpers/rocket_job_mission_control/pagination_helper.rb +7 -0
  13. data/app/helpers/rocket_job_mission_control/slices_helper.rb +9 -0
  14. data/app/models/job_failures.rb +30 -0
  15. data/app/views/layouts/rocket_job_mission_control/application.html.haml +3 -1
  16. data/app/views/layouts/rocket_job_mission_control/partials/_header.html.haml +2 -2
  17. data/app/views/layouts/rocket_job_mission_control/partials/_sidebar.html.haml +4 -0
  18. data/app/views/rocket_job_mission_control/dirmon_entries/_form.html.haml +36 -0
  19. data/app/views/rocket_job_mission_control/dirmon_entries/_list.html.haml +31 -0
  20. data/app/views/rocket_job_mission_control/dirmon_entries/_properties.html.haml +8 -0
  21. data/app/views/rocket_job_mission_control/dirmon_entries/_status.html.haml +23 -0
  22. data/app/views/rocket_job_mission_control/dirmon_entries/edit.html.haml +8 -0
  23. data/app/views/rocket_job_mission_control/dirmon_entries/index.html.haml +11 -0
  24. data/app/views/rocket_job_mission_control/dirmon_entries/new.html.haml +9 -0
  25. data/app/views/rocket_job_mission_control/dirmon_entries/show.html.haml +26 -0
  26. data/app/views/rocket_job_mission_control/jobs/_list.html.haml +34 -30
  27. data/app/views/rocket_job_mission_control/jobs/failures/_pagination.html.haml +16 -0
  28. data/app/views/rocket_job_mission_control/jobs/failures/index.html.haml +32 -0
  29. data/app/views/rocket_job_mission_control/jobs/index.html.haml +10 -8
  30. data/app/views/rocket_job_mission_control/jobs/running.html.haml +1 -1
  31. data/app/views/rocket_job_mission_control/jobs/show.html.haml +39 -35
  32. data/app/views/rocket_job_mission_control/workers/_actions.html.haml +4 -4
  33. data/app/views/rocket_job_mission_control/workers/index.html.haml +50 -45
  34. data/config/locales/en.yml +23 -1
  35. data/config/routes.rb +8 -0
  36. data/lib/rocket_job_mission_control/version.rb +1 -1
  37. data/spec/controllers/dirmon_entries_controller_spec.rb +451 -0
  38. data/spec/controllers/jobs/failures_controller_spec.rb +60 -0
  39. data/spec/dummy/config/environments/test.rb +1 -1
  40. data/spec/dummy/config/mongo.yml +15 -0
  41. data/spec/dummy/log/development.log +2 -0
  42. data/spec/dummy/log/test.log +52763 -2015
  43. data/spec/helpers/jobs_helper_spec.rb +27 -0
  44. data/spec/helpers/pagination_helper_spec.rb +21 -0
  45. data/spec/helpers/slices_helper_spec.rb +33 -0
  46. data/spec/models/job_failures_spec.rb +14 -0
  47. data/spec/views/workers/index.html.haml_spec.rb +23 -0
  48. metadata +35 -4
@@ -0,0 +1,451 @@
1
+ require 'rails_helper'
2
+
3
+ class FakeButGoodJob < RocketJob::Job
4
+
5
+ def perform(id)
6
+ id
7
+ end
8
+ end
9
+
10
+ module RocketJobMissionControl
11
+ RSpec.describe DirmonEntriesController do
12
+ routes { Engine.routes }
13
+
14
+ let(:dirmon_list) { spy(sort: []) }
15
+
16
+ before do
17
+ allow(RocketJob::DirmonEntry).to receive(:limit).and_return(dirmon_list)
18
+ end
19
+
20
+ describe 'PATCH #enable' do
21
+ before do
22
+ patch :enable, id: existing_dirmon.id
23
+ end
24
+
25
+ let(:existing_dirmon) do
26
+ RocketJob::DirmonEntry.create!(
27
+ name: 'Test',
28
+ job_class_name: 'FakeButGoodJob',
29
+ pattern: 'the_path',
30
+ arguments: [ 42 ].to_json,
31
+ state: starting_state,
32
+ )
33
+ end
34
+
35
+ context 'when transition is allowed' do
36
+ let(:starting_state) { 'pending' }
37
+
38
+ it { expect(response).to redirect_to(dirmon_entry_path(existing_dirmon.id)) }
39
+
40
+ it 'changes the state to enabled' do
41
+ expect(existing_dirmon.reload.state).to eq(:enabled)
42
+ end
43
+ end
44
+
45
+ context 'when transition is not allowed' do
46
+ let(:starting_state) { 'enabled' }
47
+
48
+ it { expect(response).to render_template(:show) }
49
+
50
+ it 'alerts the user' do
51
+ expect(flash[:alert]).to eq(I18n.t(:failure, scope: [:dirmon_entry, :enable]))
52
+ end
53
+ end
54
+ end
55
+
56
+ describe 'PATCH #disable' do
57
+ let(:existing_dirmon) do
58
+ RocketJob::DirmonEntry.create!(
59
+ name: 'Test',
60
+ job_class_name: 'FakeButGoodJob',
61
+ pattern: 'the_path',
62
+ arguments: [ 42 ].to_json,
63
+ state: starting_state,
64
+ )
65
+ end
66
+
67
+ before do
68
+ patch :disable, id: existing_dirmon.id
69
+ end
70
+
71
+ context 'when transition is allowed' do
72
+ let(:starting_state) { :enabled }
73
+
74
+ it { expect(response).to redirect_to(dirmon_entry_path(existing_dirmon.id)) }
75
+
76
+ it "changes the state to disabled" do
77
+ expect(existing_dirmon.reload.state).to eq(:disabled)
78
+ end
79
+ end
80
+
81
+ context 'when transition is not allowed' do
82
+ let(:starting_state) { :disabled }
83
+
84
+ it { expect(response).to render_template(:show) }
85
+
86
+ it 'alerts the user' do
87
+ expect(flash[:alert]).to eq(I18n.t(:failure, scope: [:dirmon_entry, :disable]))
88
+ end
89
+ end
90
+ end
91
+
92
+ describe 'GET #new' do
93
+ before do
94
+ get :new
95
+ end
96
+
97
+ it { expect(response.status).to eq(200) }
98
+
99
+ it 'assigns a new entry' do
100
+ expect(assigns(:dirmon_entry)).to be_present
101
+ expect(assigns(:dirmon_entry)).to_not be_persisted
102
+ end
103
+ end
104
+
105
+ describe 'PATCH #update' do
106
+ let(:existing_dirmon) do
107
+ RocketJob::DirmonEntry.create!(
108
+ name: 'Test',
109
+ job_class_name: 'FakeButGoodJob',
110
+ pattern: 'the_path',
111
+ arguments: [ 42 ].to_json
112
+ )
113
+ end
114
+
115
+ before do
116
+ patch :update, id: existing_dirmon.id, rocket_job_dirmon_entry: dirmon_params
117
+ end
118
+
119
+ context 'with valid parameters' do
120
+ let(:dirmon_params) do
121
+ {
122
+ pattern: 'the_path2',
123
+ job_class_name: 'FakeButGoodJob',
124
+ arguments: [ 42 ].to_json
125
+ }
126
+ end
127
+
128
+ it 'redirects to the updated entry' do
129
+ expect(response).to redirect_to(dirmon_entry_path(existing_dirmon))
130
+ end
131
+
132
+ it 'updates the entry' do
133
+ expect(existing_dirmon.reload.pattern).to eq('the_path2')
134
+ end
135
+
136
+ it 'displays a success message' do
137
+ expect(flash[:success]).to eq(I18n.t(:success, scope: [:dirmon_entry, :update]))
138
+ end
139
+ end
140
+
141
+ context 'with invalid parameters' do
142
+ let(:dirmon_params) do
143
+ {
144
+ job_class_name: 'FakeAndBadJob',
145
+ }
146
+ end
147
+
148
+ it 'renders the edit template' do
149
+ expect(response.status).to eq(200)
150
+ expect(response).to render_template(:edit)
151
+ end
152
+
153
+ it 'has errors on the entry' do
154
+ expect(assigns(:dirmon_entry)).to_not be_valid
155
+ end
156
+
157
+ it 'loads the other entries' do
158
+ expect(dirmon_list).to have_received(:sort)
159
+ end
160
+
161
+ context 'with invalid arguments json' do
162
+ let(:dirmon_params) do
163
+ {
164
+ name: 'Test',
165
+ job_class_name: 'FakeButGoodJob',
166
+ arguments: "['42']",
167
+ }
168
+ end
169
+
170
+ it 'renders the new template' do
171
+ expect(response.status).to eq(200)
172
+ expect(response).to render_template(:edit)
173
+ end
174
+
175
+ it 'has errors on arguments' do
176
+ expect(assigns(:dirmon_entry).errors[:arguments]).to be_present
177
+ end
178
+
179
+ it 'loads the other entries' do
180
+ expect(dirmon_list).to have_received(:sort)
181
+ end
182
+ end
183
+ end
184
+ end
185
+
186
+ describe 'POST #create' do
187
+ context 'with valid parameters' do
188
+ let(:dirmon_params) do
189
+ {
190
+ name: 'Test',
191
+ pattern: '/files/',
192
+ job_class_name: 'FakeButGoodJob',
193
+ arguments: [ 42 ].to_json,
194
+ properties: { description: '', priority: 42 },
195
+ }
196
+ end
197
+
198
+ before do
199
+ post :create, rocket_job_dirmon_entry: dirmon_params
200
+ end
201
+
202
+ it 'creates the entry' do
203
+ expect(assigns(:dirmon_entry)).to be_persisted
204
+ end
205
+
206
+ it 'has no errors' do
207
+ expect(assigns(:dirmon_entry).errors.messages).to be_empty
208
+ end
209
+
210
+ it 'redirects to created entry' do
211
+ expect(response).to redirect_to(dirmon_entry_path(assigns(:dirmon_entry)))
212
+ end
213
+
214
+ it 'does not load all entries' do
215
+ expect(dirmon_list).to_not have_received(:sort)
216
+ end
217
+
218
+ it 'does not save blank properties' do
219
+ expect(assigns(:dirmon_entry).properties[:description]).to eq(nil)
220
+ end
221
+
222
+ it 'saves properties' do
223
+ expect(assigns(:dirmon_entry).properties[:priority]).to eq('42')
224
+ end
225
+
226
+ [:name, :pattern, :job_class_name].each do |attribute|
227
+ it "assigns the correct value for #{attribute}" do
228
+ expect(assigns(:dirmon_entry)[attribute]).to eq(dirmon_params[attribute])
229
+ end
230
+ end
231
+
232
+ it 'persists arguments correctly' do
233
+ expect(assigns(:dirmon_entry).arguments).to eq([42])
234
+ end
235
+ end
236
+
237
+ context 'with invalid parameters' do
238
+ let(:dirmon_params) do
239
+ {
240
+ name: 'Test',
241
+ job_class_name: 'FakeAndBadJob',
242
+ arguments: [ 42 ].to_json,
243
+ }
244
+ end
245
+
246
+ before do
247
+ post :create, rocket_job_dirmon_entry: dirmon_params
248
+ end
249
+
250
+ context 'on model attributes' do
251
+ it 'renders the new template' do
252
+ expect(response.status).to eq(200)
253
+ expect(response).to render_template(:new)
254
+ end
255
+
256
+ it 'has errors on the entry' do
257
+ expect(assigns(:dirmon_entry)).to_not be_valid
258
+ end
259
+
260
+ it 'loads the other entries' do
261
+ expect(dirmon_list).to have_received(:sort)
262
+ end
263
+ end
264
+
265
+ context 'with invalid arguments json' do
266
+ let(:dirmon_params) do
267
+ {
268
+ name: 'Test',
269
+ job_class_name: 'FakeButGoodJob',
270
+ arguments: "['42']",
271
+ }
272
+ end
273
+
274
+ it 'renders the new template' do
275
+ expect(response.status).to eq(200)
276
+ expect(response).to render_template(:new)
277
+ end
278
+
279
+ it 'has errors on arguments' do
280
+ expect(assigns(:dirmon_entry).errors[:arguments]).to be_present
281
+ end
282
+
283
+ it 'loads the other entries' do
284
+ expect(dirmon_list).to have_received(:sort)
285
+ end
286
+ end
287
+ end
288
+ end
289
+
290
+ describe 'GET #edit' do
291
+ before do
292
+ @entry = RocketJob::DirmonEntry.create(
293
+ name: 'Test',
294
+ pattern: '/files/',
295
+ job_class_name: 'FakeButGoodJob',
296
+ arguments: [ 42 ]
297
+ )
298
+ get :edit, id: @entry.id
299
+ end
300
+
301
+ it { expect(response.status).to eq(200) }
302
+
303
+ it 'assigns the entry' do
304
+ expect(assigns(:dirmon_entry)).to be_present
305
+ expect(assigns(:dirmon_entry)).to eq(@entry)
306
+ end
307
+ end
308
+
309
+ describe 'GET #show' do
310
+ describe "with an invalid id" do
311
+ before do
312
+ allow(RocketJob::DirmonEntry).to receive(:find).and_return(nil)
313
+ get :show, id: 42
314
+ end
315
+
316
+ it "redirects" do
317
+ expect(response).to redirect_to(dirmon_entries_path)
318
+ end
319
+
320
+ it "adds a flash alert message" do
321
+ expect(flash[:alert]).to eq(I18n.t(:failure, scope: [:dirmon_entry, :find], id: 42))
322
+ end
323
+ end
324
+
325
+ describe "with a valid id" do
326
+ before do
327
+ allow(RocketJob::DirmonEntry).to receive(:find).and_return('entry')
328
+ get :show, id: 42
329
+ end
330
+
331
+ it "succeeds" do
332
+ expect(response.status).to be(200)
333
+ end
334
+
335
+ it "assigns the entry" do
336
+ expect(assigns(:dirmon_entry)).to be_present
337
+ end
338
+
339
+ it "assigns the entries" do
340
+ expect(assigns(:dirmons)).to eq([])
341
+ end
342
+
343
+ it "grabs a sorted list" do
344
+ expect(dirmon_list).to have_received(:sort).with(created_at: :desc)
345
+ end
346
+ end
347
+ end
348
+
349
+ describe 'DELETE #destroy' do
350
+ let(:existing_dirmon) do
351
+ RocketJob::DirmonEntry.create!(
352
+ name: 'Test',
353
+ job_class_name: 'FakeButGoodJob',
354
+ pattern: 'the_path',
355
+ arguments: [ 42 ].to_json
356
+ )
357
+ end
358
+
359
+ describe 'with a valid id' do
360
+ before { delete :destroy, id: existing_dirmon.id }
361
+
362
+ it 'redirects to index' do
363
+ expect(response).to redirect_to(dirmon_entries_path)
364
+ end
365
+
366
+ it 'displays a success message' do
367
+ expect(flash[:success]).to eq(I18n.t(:success, scope: [:dirmon_entry, :destroy]))
368
+ end
369
+
370
+ it 'deletes the entry' do
371
+ expect(RocketJob::DirmonEntry.find(existing_dirmon.id)).to eq(nil)
372
+ end
373
+ end
374
+ end
375
+
376
+ describe 'GET #index' do
377
+ describe "with no entries" do
378
+ before do
379
+ get :index
380
+ end
381
+
382
+ it "succeeds" do
383
+ expect(response.status).to be(200)
384
+ end
385
+
386
+ it "grabs a sorted list of entries" do
387
+ expect(dirmon_list).to have_received(:sort).with(created_at: :desc)
388
+ end
389
+
390
+ it "returns no entries" do
391
+ expect(assigns(:dirmons)).to eq([])
392
+ end
393
+ end
394
+
395
+ describe "with jobs" do
396
+ let(:dirmon_list) { spy(sort: dirmons) }
397
+ let(:dirmons) { ['fake_dirmon1', 'fake_dirmon2'] }
398
+
399
+ describe "with no parameters" do
400
+ before { get :index }
401
+
402
+ it "succeeds" do
403
+ expect(response.status).to be(200)
404
+ end
405
+
406
+ it "grabs a sorted list of entries" do
407
+ expect(dirmon_list).to have_received(:sort).with(created_at: :desc)
408
+ end
409
+
410
+ it "returns the entries" do
411
+ expect(assigns(:dirmons)).to match_array(dirmons)
412
+ end
413
+ end
414
+
415
+ describe "with a state filter" do
416
+ before { get :index, states: states}
417
+
418
+ context "that is empty" do
419
+ let(:states) { [] }
420
+
421
+ it { expect(response.status).to be(200) }
422
+
423
+ it "grabs a sorted list" do
424
+ expect(dirmon_list).to have_received(:sort).with(created_at: :desc)
425
+ end
426
+
427
+ it "returns the entries" do
428
+ expect(assigns(:dirmons)).to match_array(dirmons)
429
+ end
430
+ end
431
+
432
+ context "with a state" do
433
+ let(:query_spy) { spy(where: dirmons) }
434
+ let(:dirmon_list) { spy(sort: query_spy) }
435
+ let(:states) { ['enabled'] }
436
+
437
+ it { expect(response.status).to be(200) }
438
+
439
+ it "grabs a filtered list" do
440
+ expect(query_spy).to have_received(:where).with(state: ['enabled'])
441
+ end
442
+
443
+ it "returns the entries" do
444
+ expect(assigns(:dirmons)).to match_array(dirmons)
445
+ end
446
+ end
447
+ end
448
+ end
449
+ end
450
+ end
451
+ end
@@ -0,0 +1,60 @@
1
+ require 'rails_helper'
2
+
3
+ module RocketJobMissionControl
4
+ RSpec.describe Jobs::FailuresController do
5
+ routes { Engine.routes }
6
+
7
+ describe "GET #index" do
8
+ describe "with a failed job" do
9
+ let(:job) { spy(failed?: true, id: 42) }
10
+ let(:slice_errors) do
11
+ [
12
+ {
13
+ '_id' =>
14
+ {
15
+ 'error_class' => 'BoomError',
16
+ },
17
+ 'message' => ['boom'],
18
+ 'count' => '1337',
19
+ },
20
+ ]
21
+ end
22
+ let(:selected_exception) { spy(count: 1337, first: current_failure) }
23
+ let(:current_failure) { {'exception' => 'Doh! Something blew up!'} }
24
+
25
+ before do
26
+ allow(RocketJob::Job).to receive(:find).and_return(job)
27
+ allow(job).to receive_message_chain('input.collection.aggregate') { slice_errors }
28
+ allow(job).to receive_message_chain('input.collection.find.limit') { selected_exception }
29
+ get :index, job_id: job.id
30
+ end
31
+
32
+ it 'succeeds' do
33
+ expect(response).to be_success
34
+ end
35
+ it 'returns the job' do
36
+ expect(assigns(:job)).to eq(job)
37
+ end
38
+ it 'returns the errors' do
39
+ expect(assigns(:slice_errors)).to eq(slice_errors)
40
+ end
41
+ it 'returns the first exception' do
42
+ expect(assigns(:failure_exception)).to eq(current_failure['exception'])
43
+ end
44
+ end
45
+
46
+ describe "with a job that is not failed" do
47
+ let(:job) { spy(failed?: false, id: 42) }
48
+
49
+ before do
50
+ allow(RocketJob::Job).to receive(:find).and_return(job)
51
+ get :index, job_id: job.id
52
+ end
53
+
54
+ it "redirects to the job" do
55
+ expect(response).to redirect_to(job_path(job.id))
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -13,7 +13,7 @@ Rails.application.configure do
13
13
  config.eager_load = false
14
14
 
15
15
  # Configure static asset server for tests with Cache-Control for performance.
16
- config.serve_static_assets = true
16
+ config.serve_static_files = true
17
17
  config.static_cache_control = 'public, max-age=3600'
18
18
 
19
19
  # Show full error reports and disable caching.
@@ -0,0 +1,15 @@
1
+ default_options: &default_options
2
+ :w: 1
3
+ :pool_size: 5
4
+ :pool_timeout: 5
5
+ :connect_timeout: 5
6
+ :reconnect_attempts: 53
7
+ :reconnect_retry_seconds: 0.1
8
+ :reconnect_retry_multiplier: 2
9
+ :reconnect_max_retry_seconds: 5
10
+ :read: :nearest
11
+
12
+ test:
13
+ uri: mongodb://localhost:27017/rocket_job_mission_control
14
+ options:
15
+ <<: *default_options
@@ -0,0 +1,2 @@
1
+ MONGODB [DEBUG] Logging level is currently :debug which could negatively impact client-side performance. You should set your logging level no lower than :info in production.
2
+ MONGODB (0.5ms) admin['$cmd'].find({:isMaster=>1}).limit(-1)