resque-approve 0.0.5 → 0.0.13

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 28e9f67dae51237cce2245ee2c7343836ed0c6af
4
- data.tar.gz: 69de06a449ab4e709f56131005a57d304c2e59f2
2
+ SHA256:
3
+ metadata.gz: 6108fd7429dcb81c1a48bdeb59db2149491a181ec3a93a64ab9aa6a3c88ac2a3
4
+ data.tar.gz: a590d5bec1a4f952b58c411f54594209196da0907548b710a9fe3d87aff0a526
5
5
  SHA512:
6
- metadata.gz: 391a1cd9fab421d38cfe389721ac71cd81d3bc5fc90d4d81e0ce78716481d4543b21e92d33e15b58d2d1f74cfcf50a8ca6de26e2228102bc7d0132da5ec28529
7
- data.tar.gz: 10d233c67b06d08732531efe6f429e027b2b6ea9d9286cea0cdf3bfd7622fc13f1aae6c4a4c375ad7bb143e1d3b3252f3d1b56ecaa3bf25003bee00dae714e76
6
+ metadata.gz: 0a00f84951169130e3f14e0ade112f1536e0213a9a8c21299b6d8662cdb8edfc53cc87dbdca8acc4dead49b7cbf7b0d2830e325a8de0b63b931f69777e3c610f
7
+ data.tar.gz: 17c4f4013ab3d789657d7238879c4a47bbf2c93ef82bace60ffa17e0c598d7ed717161c9ecbdb221ac84dfd0ddbc9aa63e06c612f08ab150c64ccac257596e30
@@ -6,5 +6,7 @@ require File.expand_path(File.join("resque", "plugins", "approve", "pending_job"
6
6
  require File.expand_path(File.join("resque", "plugins", "approve", "pending_job_queue"), File.dirname(__FILE__))
7
7
  require File.expand_path(File.join("resque", "plugins", "approve", "approval_key_list"), File.dirname(__FILE__))
8
8
  require File.expand_path(File.join("resque", "plugins", "approve", "cleaner"), File.dirname(__FILE__))
9
+ require File.expand_path(File.join("resque", "plugins", "approve", "auto_approve_next"), File.dirname(__FILE__))
10
+ require File.expand_path(File.join("resque", "plugins", "approve", "compressable_auto_approve_next"), File.dirname(__FILE__))
9
11
 
10
12
  require File.expand_path(File.join("resque", "plugins", "approve"), File.dirname(__FILE__))
@@ -95,7 +95,7 @@ module Resque
95
95
 
96
96
  def add_static_files(base)
97
97
  base.class_eval do
98
- get %r{approve/public/([a-z_]+\.[a-z]+)} do
98
+ get %r{/approve/public/([a-z_]+\.[a-z]+)} do
99
99
  send_file Resque::ApproveServer.public_path(params[:captures]&.first)
100
100
  end
101
101
  end
@@ -107,6 +107,7 @@ module Resque
107
107
  delete_all_queues(base)
108
108
  approve_all_queues(base)
109
109
  delete_queue(base)
110
+ reset_running(base)
110
111
  delete_one_queue(base)
111
112
  approve_queue(base)
112
113
  approve_one_queue(base)
@@ -167,6 +168,16 @@ module Resque
167
168
  end
168
169
  end
169
170
 
171
+ def reset_running(base)
172
+ base.class_eval do
173
+ post "/approve/reset_running" do
174
+ Resque::Plugins::Approve::PendingJobQueue.new(params[:approval_key]).reset_running
175
+
176
+ redirect u("approve/job_list?#{{ approval_key: params[:approval_key] }.to_param}")
177
+ end
178
+ end
179
+ end
180
+
170
181
  def delete_one_queue(base)
171
182
  base.class_eval do
172
183
  post "/approve/delete_one_queue" do
@@ -11,6 +11,8 @@ module Resque
11
11
 
12
12
  included do
13
13
  self.auto_delete_approval_key = false
14
+ self.max_active_jobs = -1
15
+ self.default_queue_name = nil
14
16
  end
15
17
 
16
18
  class << self
@@ -46,20 +48,60 @@ module Resque
46
48
  @auto_delete_approval_key
47
49
  end
48
50
 
51
+ def max_active_jobs=(value)
52
+ @max_active_jobs = value
53
+ end
54
+
55
+ def max_active_jobs
56
+ @max_active_jobs
57
+ end
58
+
59
+ def default_queue_name=(value)
60
+ @default_queue_name = value
61
+ end
62
+
63
+ def default_queue_name
64
+ @default_queue_name || Resque.queue_from_class(self)
65
+ end
66
+
67
+ def approve
68
+ Resque::Plugins::Approve.approve(default_queue_name)
69
+ end
70
+
71
+ def approve_one
72
+ Resque::Plugins::Approve.approve_one(default_queue_name)
73
+ end
74
+
75
+ def approve_num(num_approve)
76
+ Resque::Plugins::Approve.approve_num(num_approve, default_queue_name)
77
+ end
78
+
79
+ def remove
80
+ Resque::Plugins::Approve.remove(default_queue_name)
81
+ end
82
+
83
+ def remove_one
84
+ Resque::Plugins::Approve.remove_one(default_queue_name)
85
+ end
86
+
49
87
  # It is possible to run a job immediately using `Resque.push`. This will bypass the queue and run
50
88
  # the job immediately. This will prevent such a job from enqueuing, and instead pause it for approval
51
89
  #
52
90
  # The primary reason for this is to prevent the job from receiving the approval parameters it is not
53
91
  # supposed to have when actually run/enqueued.
92
+ #
93
+ # NOTE: This does not validate running counts.
54
94
  def before_perform_approve(*args)
55
95
  # Check if the job needs to be approved, and if so, do not enqueue it.
56
96
  job = PendingJob.new(SecureRandom.uuid, class_name: name, args: args)
57
97
 
58
- if job.requires_approval?
98
+ if job.approval_keys? && !job.max_active_jobs?
59
99
  ApprovalKeyList.new.add_job(job)
60
100
 
61
101
  raise Resque::Job::DontPerform, "The job has not been approved yet."
62
102
  else
103
+ job.max_jobs_perform_args(args)
104
+
63
105
  true
64
106
  end
65
107
  end
@@ -69,9 +111,14 @@ module Resque
69
111
  job = PendingJob.new(SecureRandom.uuid, class_name: name, args: args)
70
112
 
71
113
  if job.requires_approval?
114
+ return false if job.compressable? && !job.compressed?(args)
115
+
72
116
  ApprovalKeyList.new.add_job(job)
117
+
73
118
  false
74
119
  else
120
+ job.max_jobs_perform_args(args)
121
+
75
122
  true
76
123
  end
77
124
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resque
4
+ module Plugins
5
+ module Approve
6
+ # This module adds a new class level perform method that overrides the jobs classes
7
+ # class method perform that is used by Resque.
8
+ #
9
+ # The new overloaded method uses super to perform the original perform functionality
10
+ # but ensures that when the job is complete that it approves the next job in the queue.
11
+ #
12
+ # The reason for this class is to manage the maximum number of jobs that are allowed to
13
+ # run at the same time for a particular job. When the maximum number of jobs is reached
14
+ # when a job completes, it will approve the next job in the queue automatically.
15
+ module AutoApproveNext
16
+ extend ActiveSupport::Concern
17
+
18
+ class_methods do
19
+ def perform(*args)
20
+ job = Resque::Plugins::Approve::PendingJob.new(SecureRandom.uuid, class_name: name, args: args)
21
+ dup_args = job.args
22
+ del_options = dup_args.extract_options!.with_indifferent_access
23
+ approval_key = del_options.delete(:approval_key) || job.approve_options[:approval_key]
24
+
25
+ dup_args << del_options if del_options.present?
26
+
27
+ begin
28
+ super(*dup_args)
29
+ ensure
30
+ Resque::Plugins::Approve::PendingJobQueue.new(approval_key).decrement_running
31
+ Resque::Plugins::Approve.approve_one approval_key
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resque
4
+ module Plugins
5
+ module Approve
6
+ # This module adds a new class level perform method that overrides the jobs classes
7
+ # class method perform that is used by Resque.
8
+ #
9
+ # The new overloaded method uses super to perform the original perform functionality
10
+ # but ensures that when the job is complete that it approves the next job in the queue.
11
+ #
12
+ # The reason for this class is to manage the maximum number of jobs that are allowed to
13
+ # run at the same time for a particular job. When the maximum number of jobs is reached
14
+ # when a job completes, it will approve the next job in the queue automatically.
15
+ module CompressableAutoApproveNext
16
+ def perform_with_auto_approve(*args)
17
+ job = Resque::Plugins::Approve::PendingJob.new(SecureRandom.uuid, class_name: name, args: args)
18
+ dup_args = job.uncompressed_args
19
+ del_options = dup_args.extract_options!.with_indifferent_access
20
+ approval_key = del_options.delete(:approval_key) || job.approve_options[:approval_key]
21
+
22
+ dup_args << del_options if del_options.present?
23
+
24
+ begin
25
+ perform_without_auto_approve(*dup_args)
26
+ ensure
27
+ Resque::Plugins::Approve::PendingJobQueue.new(approval_key).decrement_running
28
+ Resque::Plugins::Approve.approve_one approval_key
29
+ end
30
+ end
31
+
32
+ def self.extended(base)
33
+ class << base
34
+ alias_method :perform_without_auto_approve, :perform
35
+ alias_method :perform, :perform_with_auto_approve
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -12,31 +12,35 @@ module Resque
12
12
  # class_name - The name of the job class to be enqueued
13
13
  # args - The arguments for the job to be enqueued
14
14
  #
15
- # approval_key - The approval key for the pending job that will be used to release/approve the job.
16
- # This will default to nil.
17
- # approval_queue - The queue that the pending job will be enqueued into.
18
- # This will default to the queue for the job.
19
- # approval_at - The time when the job is to be enqueued.
20
- # This will call `enqueue_at` to enqueue the job, so this option will only work
21
- # properly if you use the `resque-scheduler` gem.
15
+ # requires_approval - If a class defines a default approval queue (similar to defining
16
+ # the queue for Resque), then instead of passing `approval_key: "key name"` in the
17
+ # parameters, you would pass `requires_approval: true`. This way the key name
18
+ # appears only in the class and not scattered through the code.
19
+ # approval_key - The approval key for the pending job that will be used to release/approve the job.
20
+ # This will default to nil.
21
+ # approval_queue - The queue that the pending job will be enqueued into.
22
+ # This will default to the queue for the job.
23
+ # approval_at - The time when the job is to be enqueued.
24
+ # This will call `enqueue_at` to enqueue the job, so this option will only work
25
+ # properly if you use the `resque-scheduler` gem.
22
26
  #
23
- # When using resque-scheduler, there are two ways to delay enqueue a job
24
- # and how you do this depends on your use case.
27
+ # When using resque-scheduler, there are two ways to delay enqueue a job
28
+ # and how you do this depends on your use case.
25
29
  #
26
- # Resque.enqueue YourJob, params, approval_key: "your key", approval_at: time
27
- # This will enqueue the job for approval, which will pause the job until it is approved.
28
- # Once approved, the job will delay enqueue for time, and will execute immediately or at
29
- # that time depending on if the time has passed.
30
+ # Resque.enqueue YourJob, params, approval_key: "your key", approval_at: time
31
+ # This will enqueue the job for approval, which will pause the job until it is approved.
32
+ # Once approved, the job will delay enqueue for time, and will execute immediately or at
33
+ # that time depending on if the time has passed.
30
34
  #
31
- # This is the recommended method to use as it will not run the job early, and it will allow
32
- # you to release it without knowing if it is still delayed or not.
35
+ # This is the recommended method to use as it will not run the job early, and it will allow
36
+ # you to release it without knowing if it is still delayed or not.
33
37
  #
34
- # You can also do:
35
- # Resque.enqueue_at time, YourJob, params, approval_key: "your key"
36
- # This will delay enqueue the job - because it has not been enqueued yet, the job
37
- # cannot be releaed until the time has passed and the job is actually enqueued.
38
- # Any time after that point, it can be released. Releasing the key before this
39
- # time has no effect on this job.
38
+ # You can also do:
39
+ # Resque.enqueue_at time, YourJob, params, approval_key: "your key"
40
+ # This will delay enqueue the job - because it has not been enqueued yet, the job
41
+ # cannot be releaed until the time has passed and the job is actually enqueued.
42
+ # Any time after that point, it can be released. Releasing the key before this
43
+ # time has no effect on this job.
40
44
  class PendingJob
41
45
  include Resque::Plugins::Approve::RedisAccess
42
46
  include Comparable
@@ -60,6 +64,10 @@ module Resque
60
64
  @class_name ||= stored_values[:class_name]
61
65
  end
62
66
 
67
+ def uncompressed_args
68
+ decompress_args(args)
69
+ end
70
+
63
71
  def args
64
72
  @args = if @args.present?
65
73
  @args
@@ -86,8 +94,12 @@ module Resque
86
94
  end
87
95
  end
88
96
 
97
+ def approval_keys?
98
+ @approval_keys ||= (approve_options.key?(:approval_key) || approve_options[:requires_approval])
99
+ end
100
+
89
101
  def requires_approval?
90
- @requires_approval ||= approve_options.key?(:approval_key) || approve_options[:requires_approval]
102
+ @requires_approval ||= approval_keys? && too_many_running?
91
103
  end
92
104
 
93
105
  def approval_key
@@ -98,6 +110,21 @@ module Resque
98
110
  @approval_queue ||= approve_options[:approval_queue] || Resque.queue_from_class(klass)
99
111
  end
100
112
 
113
+ def max_active_jobs?
114
+ return @max_active_jobs if defined?(@max_active_jobs)
115
+
116
+ @max_active_jobs = max_active_jobs.positive?
117
+ end
118
+
119
+ def max_active_jobs
120
+ max_val = ((klass.respond_to?(:max_active_jobs) && klass.max_active_jobs) ||
121
+ (klass.instance_variable_defined?(:@max_active_jobs) && klass.instance_variable_get(:@max_active_jobs)))
122
+
123
+ return -1 if max_val.blank?
124
+
125
+ max_val.to_i
126
+ end
127
+
101
128
  def approval_at
102
129
  @approval_at ||= approve_options[:approval_at]&.to_time
103
130
  end
@@ -108,9 +135,9 @@ module Resque
108
135
 
109
136
  def enqueue_job
110
137
  return_value = if approval_at.present?
111
- Resque.enqueue_at_with_queue approval_queue, approval_at, klass, *args
138
+ Resque.enqueue_at_with_queue approval_queue, approval_at, klass, *compressed_args(args)
112
139
  else
113
- Resque.enqueue_to approval_queue, klass, *args
140
+ Resque.enqueue_to approval_queue, klass, *compressed_args(args)
114
141
  end
115
142
 
116
143
  delete
@@ -132,10 +159,10 @@ module Resque
132
159
  # Make sure the job is loaded into memory so we can use it even though we are going to delete it.
133
160
  stored_values
134
161
 
135
- return if class_name.blank?
136
-
137
162
  redis.del(job_key)
138
163
 
164
+ return if class_name.blank?
165
+
139
166
  queue.remove_job(self)
140
167
  end
141
168
 
@@ -143,8 +170,43 @@ module Resque
143
170
  @queue ||= Resque::Plugins::Approve::PendingJobQueue.new(approval_key)
144
171
  end
145
172
 
173
+ def max_jobs_perform_args(args)
174
+ return compressed_max_jobs_perform_args(args) if compressed?(args)
175
+
176
+ return unless args.last.is_a?(Hash)
177
+
178
+ remove_options = args[-1]
179
+
180
+ %i[approval_queue approval_at requires_approval].each do |key|
181
+ remove_options.delete(key)
182
+ remove_options.delete(key.to_s)
183
+ end
184
+ end
185
+
186
+ def compressed?(basic_args)
187
+ return false unless compressable?
188
+
189
+ described_class.compressed?(basic_args)
190
+ end
191
+
192
+ def compressable?
193
+ !described_class.blank? &&
194
+ described_class.singleton_class.included_modules.map(&:name).include?("Resque::Plugins::Compressible")
195
+ end
196
+
146
197
  private
147
198
 
199
+ def compressed_max_jobs_perform_args(args)
200
+ decompressed = decompress_args(args)
201
+
202
+ max_jobs_perform_args(decompressed)
203
+ decompressed = compressed_args(decompressed)[0]
204
+
205
+ compress_arg = args[0]
206
+ compress_arg[:payload] = decompressed[:payload] if compress_arg.key?(:payload)
207
+ compress_arg["payload"] = decompressed[:payload] if compress_arg.key?("payload")
208
+ end
209
+
148
210
  def klass
149
211
  @klass ||= class_name.constantize
150
212
  end
@@ -158,15 +220,30 @@ module Resque
158
220
  end
159
221
 
160
222
  def extract_approve_options
161
- return if args.blank? || !args[-1].is_a?(Hash)
223
+ extract_args = uncompressed_args
224
+
225
+ return if extract_args.blank? || !extract_args[-1].is_a?(Hash)
162
226
 
163
- self.approve_options = args.pop
227
+ self.approve_options = extract_args.pop.with_indifferent_access
164
228
 
165
- options = approve_options.slice!(:approval_key, :approval_queue, :approval_at)
229
+ options = slice_approval_options
166
230
 
167
- args << options.to_hash if options.present?
231
+ extract_args << options.to_hash if options.present?
232
+ @args = compressed_args(extract_args)
168
233
  end
169
234
 
235
+ # rubocop:disable Layout/SpaceAroundOperators
236
+ def slice_approval_options
237
+ options = approve_options.slice!(:approval_key, :approval_queue, :approval_at, :requires_approval)
238
+ approve_options[:approval_key] ||= klass.default_queue_name if approve_options[:requires_approval]
239
+
240
+ options_approval_key(options)
241
+
242
+ options
243
+ end
244
+
245
+ # rubocop:enable Layout/SpaceAroundOperators
246
+
170
247
  def encode_args(*args)
171
248
  Resque.encode(args)
172
249
  end
@@ -180,7 +257,57 @@ module Resque
180
257
  def approve_options=(value)
181
258
  @approve_options = (value&.dup || {}).with_indifferent_access
182
259
  end
260
+
261
+ def too_many_running?
262
+ return true if queue.paused?
263
+ return true unless max_active_jobs?
264
+ return @too_many_running if defined?(@too_many_running)
265
+
266
+ num_running = queue.increment_running
267
+ max_jobs = max_active_jobs
268
+
269
+ queue.decrement_running if num_running > max_jobs
270
+ @too_many_running = num_running > max_jobs
271
+ end
272
+
273
+ def options_approval_key(options)
274
+ return unless max_active_jobs?
275
+ return if options.key?(:approval_key)
276
+
277
+ if compressable?
278
+ unless klass.singleton_class.included_modules.include?(Resque::Plugins::Approve::CompressableAutoApproveNext)
279
+ klass.extend Resque::Plugins::Approve::CompressableAutoApproveNext
280
+ end
281
+ else
282
+ klass.include Resque::Plugins::Approve::AutoApproveNext unless klass.included_modules.include?(Resque::Plugins::Approve::AutoApproveNext)
283
+ end
284
+
285
+ options[:approval_key] = approve_options.fetch(:approval_key) { klass.default_queue_name }
286
+ end
287
+
288
+ def described_class
289
+ return if class_name.blank?
290
+
291
+ class_name.constantize
292
+ rescue StandardError
293
+ nil
294
+ end
295
+
296
+ def compressed_args(compress_args)
297
+ return compress_args unless compressable?
298
+ return compress_args if described_class.compressed?(compress_args)
299
+
300
+ [{ :resque_compressed => true, :payload => described_class.compressed_args(compress_args) }]
301
+ end
302
+
303
+ def decompress_args(basic_args)
304
+ return basic_args unless compressable?
305
+ return basic_args unless described_class.compressed?(basic_args)
306
+
307
+ described_class.uncompressed_args(basic_args.first[:payload] || basic_args.first["payload"])
308
+ end
183
309
  end
310
+
184
311
  # rubocop:enable Metrics/ClassLength
185
312
  end
186
313
  end