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 +5 -5
- data/lib/resque-approve.rb +2 -0
- data/lib/resque/approve_server.rb +12 -1
- data/lib/resque/plugins/approve.rb +48 -1
- data/lib/resque/plugins/approve/auto_approve_next.rb +38 -0
- data/lib/resque/plugins/approve/compressable_auto_approve_next.rb +41 -0
- data/lib/resque/plugins/approve/pending_job.rb +157 -30
- data/lib/resque/plugins/approve/pending_job_queue.rb +27 -1
- data/lib/resque/plugins/version.rb +1 -1
- data/lib/resque/server/views/_approval_key_rows.erb +4 -1
- data/lib/resque/server/views/_job_list_table.erb +1 -1
- data/lib/resque/server/views/approval_keys.erb +6 -0
- data/lib/resque/server/views/job_details.erb +4 -1
- data/lib/resque/server/views/job_list.erb +27 -13
- data/spec/approve/pending_job_queue_spec.rb +21 -1
- data/spec/approve/pending_job_spec.rb +264 -6
- data/spec/approve_spec.rb +53 -0
- data/spec/examples.txt +182 -151
- data/spec/server/views/job_list.erb_spec.rb +12 -0
- data/spec/support/jobs/auto_delete_approval_key_job.rb +4 -0
- data/spec/support/jobs/default_approval_queue_job.rb +5 -0
- data/spec/support/jobs/max_active_job.rb +9 -0
- metadata +18 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6108fd7429dcb81c1a48bdeb59db2149491a181ec3a93a64ab9aa6a3c88ac2a3
|
4
|
+
data.tar.gz: a590d5bec1a4f952b58c411f54594209196da0907548b710a9fe3d87aff0a526
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0a00f84951169130e3f14e0ade112f1536e0213a9a8c21299b6d8662cdb8edfc53cc87dbdca8acc4dead49b7cbf7b0d2830e325a8de0b63b931f69777e3c610f
|
7
|
+
data.tar.gz: 17c4f4013ab3d789657d7238879c4a47bbf2c93ef82bace60ffa17e0c598d7ed717161c9ecbdb221ac84dfd0ddbc9aa63e06c612f08ab150c64ccac257596e30
|
data/lib/resque-approve.rb
CHANGED
@@ -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.
|
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
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
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
|
-
#
|
24
|
-
#
|
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
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
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
|
-
#
|
32
|
-
#
|
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
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
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 ||=
|
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
|
-
|
223
|
+
extract_args = uncompressed_args
|
224
|
+
|
225
|
+
return if extract_args.blank? || !extract_args[-1].is_a?(Hash)
|
162
226
|
|
163
|
-
self.approve_options =
|
227
|
+
self.approve_options = extract_args.pop.with_indifferent_access
|
164
228
|
|
165
|
-
options =
|
229
|
+
options = slice_approval_options
|
166
230
|
|
167
|
-
|
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
|