rocketjob_mission_control 1.1.0 → 1.2.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.
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)