resque-stages 0.0.1

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 (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +103 -0
  5. data/.rubocop_todo.yml +34 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +6 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Gemfile +9 -0
  10. data/Gemfile.lock +172 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +250 -0
  13. data/Rakefile +8 -0
  14. data/bin/console +16 -0
  15. data/bin/setup +8 -0
  16. data/lib/resque-stages.rb +11 -0
  17. data/lib/resque/plugins/stages.rb +110 -0
  18. data/lib/resque/plugins/stages/cleaner.rb +36 -0
  19. data/lib/resque/plugins/stages/redis_access.rb +16 -0
  20. data/lib/resque/plugins/stages/staged_group.rb +181 -0
  21. data/lib/resque/plugins/stages/staged_group_list.rb +79 -0
  22. data/lib/resque/plugins/stages/staged_group_stage.rb +275 -0
  23. data/lib/resque/plugins/stages/staged_job.rb +271 -0
  24. data/lib/resque/plugins/stages/version.rb +9 -0
  25. data/lib/resque/server/public/stages.css +56 -0
  26. data/lib/resque/server/views/_group_stages_list_pagination.erb +67 -0
  27. data/lib/resque/server/views/_group_stages_list_table.erb +25 -0
  28. data/lib/resque/server/views/_stage_job_list_pagination.erb +72 -0
  29. data/lib/resque/server/views/_stage_job_list_table.erb +46 -0
  30. data/lib/resque/server/views/_staged_group_list_pagination.erb +67 -0
  31. data/lib/resque/server/views/_staged_group_list_table.erb +44 -0
  32. data/lib/resque/server/views/group_stages_list.erb +58 -0
  33. data/lib/resque/server/views/groups.erb +40 -0
  34. data/lib/resque/server/views/job_details.erb +91 -0
  35. data/lib/resque/server/views/stage.erb +64 -0
  36. data/lib/resque/stages_server.rb +240 -0
  37. data/read_me/groups_list.png +0 -0
  38. data/read_me/job.png +0 -0
  39. data/read_me/stage.png +0 -0
  40. data/read_me/stages.png +0 -0
  41. data/resque-stages.gemspec +49 -0
  42. data/spec/rails_helper.rb +40 -0
  43. data/spec/resque/plugins/stages/cleaner_spec.rb +82 -0
  44. data/spec/resque/plugins/stages/staged_group_list_spec.rb +96 -0
  45. data/spec/resque/plugins/stages/staged_group_spec.rb +226 -0
  46. data/spec/resque/plugins/stages/staged_group_stage_spec.rb +293 -0
  47. data/spec/resque/plugins/stages/staged_job_spec.rb +324 -0
  48. data/spec/resque/plugins/stages_spec.rb +369 -0
  49. data/spec/resque/server/public/stages.css_spec.rb +18 -0
  50. data/spec/resque/server/views/group_stages_list.erb_spec.rb +67 -0
  51. data/spec/resque/server/views/groups.erb_spec.rb +81 -0
  52. data/spec/resque/server/views/job_details.erb_spec.rb +100 -0
  53. data/spec/resque/server/views/stage.erb_spec.rb +68 -0
  54. data/spec/spec_helper.rb +104 -0
  55. data/spec/support/01_utils/fake_logger.rb +7 -0
  56. data/spec/support/config/redis-auth.yml +12 -0
  57. data/spec/support/fake_logger.rb +7 -0
  58. data/spec/support/jobs/basic_job.rb +17 -0
  59. data/spec/support/jobs/compressed_job.rb +18 -0
  60. data/spec/support/jobs/retry_job.rb +21 -0
  61. data/spec/support/purge_all.rb +15 -0
  62. metadata +297 -0
@@ -0,0 +1,369 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_helper"
4
+
5
+ RSpec.describe Resque::Plugins::Stages do
6
+ it "has a version number" do
7
+ expect(Resque::Plugins::Stages::VERSION).not_to be nil
8
+ end
9
+
10
+ context "full resque calls" do
11
+ around(:each) do |spec_proxy|
12
+ inline = Resque.inline?
13
+
14
+ begin
15
+ Resque.inline = false
16
+ spec_proxy.call
17
+ ensure
18
+ Resque.inline = inline
19
+ end
20
+ end
21
+
22
+ context BasicJob do
23
+ let(:group) { Resque::Plugins::Stages::StagedGroup.new(SecureRandom.uuid) }
24
+ let(:stage) { group.current_stage }
25
+ let(:job) { stage.enqueue(BasicJob, *options) }
26
+ let(:load_job) { Resque::Plugins::Stages::StagedJob.new(job.job_id) }
27
+ let(:worker) { Resque::Worker.new(BasicJob.queue) }
28
+ let(:options) { ["This", 1, "is", "an" => "arglist"] }
29
+
30
+ before(:each) do
31
+ worker.register_worker
32
+
33
+ allow(BasicJob).to receive(:perform_job).and_wrap_original do |orig_perform, *args|
34
+ new_job = orig_perform.call(*args)
35
+
36
+ allow(new_job).to receive(:notify_stage).and_return nil
37
+
38
+ new_job
39
+ end
40
+ end
41
+
42
+ it "records that the job is running" do
43
+ job.enqueue_job
44
+
45
+ allow(BasicJob).to receive(:perform) do
46
+ expect(load_job.status).to eq :running
47
+ end
48
+
49
+ worker_job = worker.reserve
50
+
51
+ worker.perform worker_job
52
+ worker.done_working
53
+ end
54
+
55
+ it "records that the job succeeded" do
56
+ job.enqueue_job
57
+
58
+ worker_job = worker.reserve
59
+
60
+ worker.perform worker_job
61
+ worker.done_working
62
+
63
+ expect(load_job.status).to eq :successful
64
+ expect(FakeLogger).
65
+ to have_received(:error).with "BasicJob.perform args", { "staged_job_id" => job.job_id }, "This", 1, "is", "an" => "arglist"
66
+ expect(FakeLogger).to have_received(:error).with "BasicJob.perform job.args", "This", 1, "is", "an" => "arglist"
67
+ expect(FakeLogger).to have_received(:error).with "BasicJob.perform job_id", job.job_id
68
+ end
69
+
70
+ it "records that the job failed" do
71
+ allow(FakeLogger).to receive(:error).and_raise "This is an error"
72
+
73
+ job.enqueue_job
74
+
75
+ worker_job = worker.reserve
76
+
77
+ worker.perform worker_job
78
+ worker.done_working
79
+
80
+ expect(load_job.status).to eq :failed
81
+ expect(FakeLogger).to have_received(:error).with "BasicJob.perform job_id", job.job_id
82
+ end
83
+ end
84
+
85
+ context CompressedJob do
86
+ let(:group) { Resque::Plugins::Stages::StagedGroup.new(SecureRandom.uuid) }
87
+ let(:stage) { group.current_stage }
88
+ let(:job) { stage.enqueue(CompressedJob, *options) }
89
+ let(:load_job) { Resque::Plugins::Stages::StagedJob.new(job.job_id) }
90
+ let(:worker) { Resque::Worker.new(CompressedJob.queue) }
91
+ let(:options) { ["This", 1, "is", "an" => "arglist"] }
92
+
93
+ before(:each) do
94
+ worker.register_worker
95
+
96
+ allow(CompressedJob).to receive(:perform_job).and_wrap_original do |orig_perform, *args|
97
+ new_job = orig_perform.call(*args)
98
+
99
+ allow(new_job).to receive(:notify_stage).and_return nil
100
+
101
+ new_job
102
+ end
103
+ end
104
+
105
+ it "records that the job is running" do
106
+ job.enqueue_job
107
+
108
+ allow(CompressedJob).to receive(:perform) do
109
+ expect(load_job.status).to eq :running
110
+ end
111
+
112
+ worker_job = worker.reserve
113
+
114
+ worker.perform worker_job
115
+ worker.done_working
116
+ end
117
+
118
+ it "records that the job succeeded" do
119
+ job.enqueue_job
120
+
121
+ worker_job = worker.reserve
122
+
123
+ worker.perform worker_job
124
+ worker.done_working
125
+
126
+ expect(load_job.status).to eq :successful
127
+ expect(FakeLogger).to have_received(:error).with "CompressedJob.perform args",
128
+ { "staged_job_id" => job.job_id },
129
+ "This",
130
+ 1,
131
+ "is",
132
+ "an" => "arglist"
133
+ expect(FakeLogger).to have_received(:error).with "CompressedJob.perform job.args",
134
+ "This",
135
+ 1,
136
+ "is",
137
+ "an" => "arglist"
138
+ expect(FakeLogger).to have_received(:error).with "CompressedJob.perform job_id", job.job_id
139
+ end
140
+
141
+ it "records that the job failed" do
142
+ allow(FakeLogger).to receive(:error).and_raise "This is an error"
143
+
144
+ job.enqueue_job
145
+
146
+ worker_job = worker.reserve
147
+
148
+ worker.perform worker_job
149
+ worker.done_working
150
+
151
+ expect(load_job.status).to eq :failed
152
+ expect(FakeLogger).to have_received(:error).with "CompressedJob.perform job_id", job.job_id
153
+ end
154
+ end
155
+
156
+ context RetryJob do
157
+ let(:group) { Resque::Plugins::Stages::StagedGroup.new(SecureRandom.uuid) }
158
+ let(:stage) { group.current_stage }
159
+ let(:job) { stage.enqueue(RetryJob, *options) }
160
+ let(:load_job) { Resque::Plugins::Stages::StagedJob.new(job.job_id) }
161
+ let(:worker) { Resque::Worker.new(RetryJob.queue) }
162
+ let(:options) { ["This", 1, "is", "an" => "arglist"] }
163
+
164
+ before(:each) do
165
+ worker.register_worker
166
+
167
+ allow(RetryJob).to receive(:perform_job).and_wrap_original do |orig_perform, *args|
168
+ new_job = orig_perform.call(*args)
169
+
170
+ allow(new_job).to receive(:notify_stage).and_return nil
171
+
172
+ new_job
173
+ end
174
+ end
175
+
176
+ it "records that the job succeeded" do
177
+ job.enqueue_job
178
+
179
+ worker_job = worker.reserve
180
+
181
+ worker.perform worker_job
182
+ worker.done_working
183
+
184
+ expect(load_job.status).to eq :successful
185
+ expect(FakeLogger).
186
+ to have_received(:error).with "RetryJob.perform args", { "staged_job_id" => job.job_id }, "This", 1, "is", "an" => "arglist"
187
+ expect(FakeLogger).to have_received(:error).with "RetryJob.perform job.args", "This", 1, "is", "an" => "arglist"
188
+ expect(FakeLogger).to have_received(:error).with "RetryJob.perform job_id", job.job_id
189
+ end
190
+
191
+ it "records that the job re-queued" do
192
+ allow(FakeLogger).to receive(:error).and_raise "This is an error"
193
+
194
+ job.enqueue_job
195
+
196
+ worker_job = worker.reserve
197
+
198
+ worker.perform worker_job
199
+ worker.done_working
200
+
201
+ expect(load_job.status).to eq :pending_re_run
202
+ expect(FakeLogger).to have_received(:error).with "RetryJob.perform job_id", job.job_id
203
+ end
204
+
205
+ it "records that the job failed" do
206
+ allow(FakeLogger).to receive(:error).and_raise "This is an error"
207
+
208
+ 6.times do
209
+ Resque.enqueue(*job.enqueue_args)
210
+
211
+ worker_job = worker.reserve
212
+
213
+ worker.perform worker_job
214
+ worker.done_working
215
+ end
216
+
217
+ expect(load_job.status).to eq :failed
218
+ expect(FakeLogger).to have_received(:error).exactly(6).times.with("RetryJob.perform job_id", job.job_id)
219
+ end
220
+ end
221
+ end
222
+
223
+ context "inline" do
224
+ context RetryJob do
225
+ let(:group) { Resque::Plugins::Stages::StagedGroup.new(SecureRandom.uuid) }
226
+ let(:stage) { group.current_stage }
227
+ let(:job) { stage.enqueue(RetryJob, *options) }
228
+ let(:load_job) { Resque::Plugins::Stages::StagedJob.new(job.job_id) }
229
+ let(:options) { ["This", 1, "is", "an" => "arglist"] }
230
+
231
+ before(:each) do
232
+ allow(RetryJob).to receive(:perform_job).and_wrap_original do |orig_perform, *args|
233
+ new_job = orig_perform.call(*args)
234
+
235
+ allow(new_job).to receive(:notify_stage).and_return nil
236
+
237
+ new_job
238
+ end
239
+ end
240
+
241
+ it "records that the job succeeded" do
242
+ job.enqueue_job
243
+
244
+ expect(load_job.status).to eq :successful
245
+ expect(FakeLogger).
246
+ to have_received(:error).with "RetryJob.perform args", { "staged_job_id" => job.job_id }, "This", 1, "is", "an" => "arglist"
247
+ expect(FakeLogger).to have_received(:error).with "RetryJob.perform job.args", "This", 1, "is", "an" => "arglist"
248
+ expect(FakeLogger).to have_received(:error).with "RetryJob.perform job_id", job.job_id
249
+ end
250
+
251
+ it "records that the job failed" do
252
+ allow(FakeLogger).to receive(:error).and_raise "This is an error"
253
+
254
+ expect { job.enqueue_job }.not_to raise_error
255
+
256
+ expect(load_job.status).to eq :failed
257
+ expect(FakeLogger).to have_received(:error).with "RetryJob.perform job_id", job.job_id
258
+ end
259
+ end
260
+
261
+ context CompressedJob do
262
+ let(:group) { Resque::Plugins::Stages::StagedGroup.new(SecureRandom.uuid) }
263
+ let(:stage) { group.current_stage }
264
+ let(:job) { stage.enqueue(CompressedJob, *options) }
265
+ let(:load_job) { Resque::Plugins::Stages::StagedJob.new(job.job_id) }
266
+ let(:options) { ["This", 1, "is", "an" => "arglist"] }
267
+
268
+ before(:each) do
269
+ allow(CompressedJob).to receive(:perform_job).and_wrap_original do |orig_perform, *args|
270
+ new_job = orig_perform.call(*args)
271
+
272
+ allow(new_job).to receive(:notify_stage).and_return nil
273
+
274
+ new_job
275
+ end
276
+ end
277
+
278
+ it "records that the job succeeded" do
279
+ job.enqueue_job
280
+
281
+ expect(load_job.status).to eq :successful
282
+ expect(FakeLogger).
283
+ to have_received(:error).with "CompressedJob.perform args", { "staged_job_id" => job.job_id }, "This", 1, "is", "an" => "arglist"
284
+ expect(FakeLogger).to have_received(:error).with "CompressedJob.perform job.args", "This", 1, "is", "an" => "arglist"
285
+ expect(FakeLogger).to have_received(:error).with "CompressedJob.perform job_id", job.job_id
286
+ end
287
+
288
+ it "records that the job failed" do
289
+ allow(FakeLogger).to receive(:error).and_raise "This is an error"
290
+
291
+ expect { job.enqueue_job }.not_to raise_error
292
+
293
+ expect(load_job.status).to eq :failed
294
+ expect(FakeLogger).to have_received(:error).with "CompressedJob.perform job_id", job.job_id
295
+ end
296
+ end
297
+ end
298
+
299
+ describe "perform_job" do
300
+ let(:stage) do
301
+ instance_double(Resque::Plugins::Stages::StagedGroupStage,
302
+ add_job: nil,
303
+ remove_job: nil,
304
+ group_stage_id: SecureRandom.uuid,
305
+ status: :pending,
306
+ "status=": nil,
307
+ job_completed: nil)
308
+ end
309
+
310
+ RSpec.shared_examples("it loads from args") do
311
+ it "loads the job by ID" do
312
+ expect(perform_job).to eq load_job
313
+ expect(perform_job.args).to eq ["This", 1, "is", "an" => "arglist"]
314
+ end
315
+
316
+ it "creates a new job if the id is deleted" do
317
+ job.delete
318
+
319
+ expect(perform_job).to eq load_job
320
+ expect(perform_job.args).to eq match_args
321
+ end
322
+
323
+ it "creates a new job if the first paramater is a hash but doesn't have an ID" do
324
+ perform_args[0].delete :staged_job_id
325
+ perform_args[0]["this_is"] = "silly"
326
+
327
+ expect(perform_job.args).to eq perform_args
328
+ end
329
+
330
+ it "creates a new job if there is no id" do
331
+ if perform_args.length > 1
332
+ perform_args.shift
333
+ else
334
+ perform_args[0].delete :staged_job_id
335
+ end
336
+
337
+ expect(perform_job.args).to eq perform_args
338
+ end
339
+ end
340
+
341
+ context "BasicJob" do
342
+ let(:job) { Resque::Plugins::Stages::StagedJob.create_job(stage, BasicJob, "This", 1, :is, an: "arglist") }
343
+ let(:load_job) { Resque::Plugins::Stages::StagedJob.new(job.job_id) }
344
+ let(:perform_args) { [{ staged_job_id: job.job_id }, "This", 1, "is", "an" => "arglist"] }
345
+ let(:match_args) { ["This", 1, "is", "an" => "arglist"] }
346
+ let(:perform_job) { BasicJob.perform_job(*perform_args) }
347
+
348
+ it_behaves_like "it loads from args"
349
+ end
350
+
351
+ context "CompressedJob" do
352
+ let(:job) { Resque::Plugins::Stages::StagedJob.create_job(stage, CompressedJob, "This", 1, :is, an: "arglist") }
353
+ let(:load_job) { Resque::Plugins::Stages::StagedJob.new(job.job_id) }
354
+ let(:perform_args) do
355
+ [{ "resque_compressed" => true,
356
+ "payload" => CompressedJob.compressed_args([{ "staged_job_id" => job.job_id }, "This", 1, :is, an: "arglist"]),
357
+ staged_job_id: job.job_id }]
358
+ end
359
+ let(:match_args) do
360
+ [{ "resque_compressed" => true,
361
+ "payload" => CompressedJob.compressed_args([{ "staged_job_id" => job.job_id }, "This", 1, :is, an: "arglist"]),
362
+ staged_job_id: job.job_id }]
363
+ end
364
+ let(:perform_job) { CompressedJob.perform_job(*perform_args) }
365
+
366
+ it_behaves_like "it loads from args"
367
+ end
368
+ end
369
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_helper"
4
+
5
+ RSpec.describe "approve.css" do
6
+ include Rack::Test::Methods
7
+
8
+ def app
9
+ @app ||= Resque::Server.new
10
+ end
11
+
12
+ it "fetches the CSS file" do
13
+ get "/stages/public/stages.css"
14
+
15
+ expect(last_response).to be_ok
16
+ expect(last_response.body).to be_include(".stages_pagination_block {")
17
+ end
18
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_helper"
4
+
5
+ RSpec.describe "groups.erb" do
6
+ let(:group) { Resque::Plugins::Stages::StagedGroup.new(SecureRandom.uuid) }
7
+ let(:numbers) { Array.new(5) { |index| index } }
8
+ let!(:stages) do
9
+ numbers.map do |number|
10
+ group.stage(number)
11
+ end
12
+ end
13
+
14
+ include Rack::Test::Methods
15
+
16
+ def app
17
+ @app ||= Resque::Server.new
18
+ end
19
+
20
+ context "actions" do
21
+ before(:each) do
22
+ allow(Resque::Plugins::Stages::StagedGroup).to receive(:new).and_return group
23
+ allow(group).to receive(:delete)
24
+ allow(group).to receive(:initiate)
25
+ end
26
+
27
+ it "should respond to /stages/initiate_group" do
28
+ post "/stages/initiate_group?group_id=#{group.group_id}"
29
+
30
+ expect(last_response).to be_redirect
31
+ expect(last_response.header["Location"]).to match(/stages$/)
32
+ expect(group).to have_received(:initiate)
33
+ end
34
+
35
+ it "should respond to /stages/delete_group" do
36
+ post "/stages/delete_group?group_id=#{group.group_id}"
37
+
38
+ expect(last_response).to be_redirect
39
+ expect(last_response.header["Location"]).to match(/stages$/)
40
+ expect(group).to have_received(:delete)
41
+ end
42
+ end
43
+
44
+ it "should respond to /stages/group_stages_list" do
45
+ get "/stages/group_stages_list?group_id=#{group.group_id}"
46
+
47
+ expect(last_response).to be_ok
48
+
49
+ expect(last_response.body).to match %r{action="/stages/delete_group\?group_id=#{group.group_id}"}
50
+ expect(last_response.body).to match %r{action="/stages/initiate_group\?group_id=#{group.group_id}"}
51
+
52
+ stages.each do |stage|
53
+ expect(last_response.body).to match %r{#{stage.number}\n +</a>}
54
+ expect(last_response.body).to match %r{/stages/stage\?#{{ group_stage_id: stage.group_stage_id }.to_param.gsub("+", "\\\\+")}}
55
+ end
56
+ end
57
+
58
+ it "pages queues" do
59
+ get "/stages/group_stages_list?group_id=#{group.group_id}&page_size=2"
60
+
61
+ expect(last_response).to be_ok
62
+
63
+ expect(last_response.body).to match(%r{href="/stages/group_stages_list?.*group_id=#{group.group_id}.*page_num=2})
64
+ expect(last_response.body).to match(%r{href="/stages/group_stages_list?.*group_id=#{group.group_id}.*page_num=3})
65
+ expect(last_response.body).not_to match(%r{href="/stages/group_stages_list?.*group_id=#{group.group_id}.*page_num=4})
66
+ end
67
+ end