resque-approve 0.0.5 → 0.0.13

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.
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