resque-approve 0.0.4 → 0.0.12
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 +1 -0
- data/lib/resque/approve_server.rb +12 -1
- data/lib/resque/plugins/approve.rb +45 -1
- data/lib/resque/plugins/approve/approval_key_list.rb +1 -1
- data/lib/resque/plugins/approve/auto_approve_next.rb +38 -0
- data/lib/resque/plugins/approve/pending_job.rb +94 -28
- data/lib/resque/plugins/approve/pending_job_queue.rb +31 -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/approval_keys.erb +6 -0
- data/lib/resque/server/views/job_details.erb +3 -0
- data/lib/resque/server/views/job_list.erb +27 -13
- data/spec/approve/pending_job_queue_spec.rb +80 -2
- data/spec/approve/pending_job_spec.rb +222 -6
- data/spec/approve_spec.rb +53 -0
- data/spec/examples.txt +181 -149
- data/spec/server/views/job_list.erb_spec.rb +12 -0
- data/spec/support/jobs/default_approval_queue_job.rb +5 -0
- data/spec/support/jobs/max_active_job.rb +5 -0
- metadata +16 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4964a615615665834296d65c85866378409435eb0eba93bc5932978334d7ac3b
|
4
|
+
data.tar.gz: 900f8341e08a2da8fa893016a8f36c4f8bd47996c6848a314ba47586df8cb47a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0942156347f37b046f148de6e941a7c6ffa2fada7659201f56889f04cf0bcf3ddbb03d3e469aa07c2fbb6f27f0486cee2aebb4d9b91d86ecc349c8ee7686ba74'
|
7
|
+
data.tar.gz: 53575d650f15b4c51477e85b321c420edf783fbc6a45380a47b91149a11b365ce068b33098435c0c73b25464bd770dd784e606dece377c718daaced99ff0bada
|
data/lib/resque-approve.rb
CHANGED
@@ -6,5 +6,6 @@ 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__))
|
9
10
|
|
10
11
|
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
|
@@ -72,6 +114,8 @@ module Resque
|
|
72
114
|
ApprovalKeyList.new.add_job(job)
|
73
115
|
false
|
74
116
|
else
|
117
|
+
job.max_jobs_perform_args(args)
|
118
|
+
|
75
119
|
true
|
76
120
|
end
|
77
121
|
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
|
@@ -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
|
@@ -86,8 +90,12 @@ module Resque
|
|
86
90
|
end
|
87
91
|
end
|
88
92
|
|
93
|
+
def approval_keys?
|
94
|
+
@approval_keys ||= (approve_options.key?(:approval_key) || approve_options[:requires_approval])
|
95
|
+
end
|
96
|
+
|
89
97
|
def requires_approval?
|
90
|
-
@requires_approval ||=
|
98
|
+
@requires_approval ||= approval_keys? && too_many_running?
|
91
99
|
end
|
92
100
|
|
93
101
|
def approval_key
|
@@ -98,6 +106,21 @@ module Resque
|
|
98
106
|
@approval_queue ||= approve_options[:approval_queue] || Resque.queue_from_class(klass)
|
99
107
|
end
|
100
108
|
|
109
|
+
def max_active_jobs?
|
110
|
+
return @max_active_jobs if defined?(@max_active_jobs)
|
111
|
+
|
112
|
+
@max_active_jobs = max_active_jobs.positive?
|
113
|
+
end
|
114
|
+
|
115
|
+
def max_active_jobs
|
116
|
+
max_val = ((klass.respond_to?(:max_active_jobs) && klass.max_active_jobs) ||
|
117
|
+
(klass.instance_variable_defined?(:@max_active_jobs) && klass.instance_variable_get(:@max_active_jobs)))
|
118
|
+
|
119
|
+
return -1 if max_val.blank?
|
120
|
+
|
121
|
+
max_val.to_i
|
122
|
+
end
|
123
|
+
|
101
124
|
def approval_at
|
102
125
|
@approval_at ||= approve_options[:approval_at]&.to_time
|
103
126
|
end
|
@@ -132,10 +155,10 @@ module Resque
|
|
132
155
|
# Make sure the job is loaded into memory so we can use it even though we are going to delete it.
|
133
156
|
stored_values
|
134
157
|
|
135
|
-
return if class_name.blank?
|
136
|
-
|
137
158
|
redis.del(job_key)
|
138
159
|
|
160
|
+
return if class_name.blank?
|
161
|
+
|
139
162
|
queue.remove_job(self)
|
140
163
|
end
|
141
164
|
|
@@ -143,6 +166,16 @@ module Resque
|
|
143
166
|
@queue ||= Resque::Plugins::Approve::PendingJobQueue.new(approval_key)
|
144
167
|
end
|
145
168
|
|
169
|
+
def max_jobs_perform_args(args)
|
170
|
+
remove_options = args.extract_options!.with_indifferent_access
|
171
|
+
|
172
|
+
return if remove_options.blank?
|
173
|
+
|
174
|
+
options = remove_options.slice!(:approval_queue, :approval_at, :requires_approval)
|
175
|
+
|
176
|
+
args << options if options.present?
|
177
|
+
end
|
178
|
+
|
146
179
|
private
|
147
180
|
|
148
181
|
def klass
|
@@ -158,15 +191,27 @@ module Resque
|
|
158
191
|
end
|
159
192
|
|
160
193
|
def extract_approve_options
|
161
|
-
return if args.blank? ||
|
194
|
+
return if args.blank? || !@args[-1].is_a?(Hash)
|
195
|
+
|
196
|
+
self.approve_options = @args.pop
|
197
|
+
|
198
|
+
options = slice_approval_options
|
162
199
|
|
163
|
-
|
200
|
+
@args << options.to_hash if options.present?
|
201
|
+
end
|
202
|
+
|
203
|
+
# rubocop:disable Layout/SpaceAroundOperators
|
204
|
+
def slice_approval_options
|
205
|
+
options = approve_options.slice!(:approval_key, :approval_queue, :approval_at, :requires_approval)
|
206
|
+
approve_options[:approval_key] ||= klass.default_queue_name if approve_options[:requires_approval]
|
164
207
|
|
165
|
-
options
|
208
|
+
options_approval_key(options)
|
166
209
|
|
167
|
-
|
210
|
+
options
|
168
211
|
end
|
169
212
|
|
213
|
+
# rubocop:enable Layout/SpaceAroundOperators
|
214
|
+
|
170
215
|
def encode_args(*args)
|
171
216
|
Resque.encode(args)
|
172
217
|
end
|
@@ -180,6 +225,27 @@ module Resque
|
|
180
225
|
def approve_options=(value)
|
181
226
|
@approve_options = (value&.dup || {}).with_indifferent_access
|
182
227
|
end
|
228
|
+
|
229
|
+
def too_many_running?
|
230
|
+
return true if queue.paused?
|
231
|
+
return true unless max_active_jobs?
|
232
|
+
return @too_many_running if defined?(@too_many_running)
|
233
|
+
|
234
|
+
num_running = queue.increment_running
|
235
|
+
max_jobs = max_active_jobs
|
236
|
+
|
237
|
+
queue.decrement_running if num_running > max_jobs
|
238
|
+
@too_many_running = num_running > max_jobs
|
239
|
+
end
|
240
|
+
|
241
|
+
def options_approval_key(options)
|
242
|
+
return unless max_active_jobs?
|
243
|
+
return if options.key?(:approval_key)
|
244
|
+
|
245
|
+
klass.include Resque::Plugins::Approve::AutoApproveNext unless klass.included_modules.include?(Resque::Plugins::Approve::AutoApproveNext)
|
246
|
+
|
247
|
+
options[:approval_key] = approve_options.fetch(:approval_key) { klass.default_queue_name }
|
248
|
+
end
|
183
249
|
end
|
184
250
|
# rubocop:enable Metrics/ClassLength
|
185
251
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# rubocop:disable Metrics/ClassLength
|
4
|
+
|
3
5
|
module Resque
|
4
6
|
module Plugins
|
5
7
|
module Approve
|
@@ -15,6 +17,24 @@ module Resque
|
|
15
17
|
@approval_key = approval_key
|
16
18
|
end
|
17
19
|
|
20
|
+
def num_running
|
21
|
+
redis.get(running_key)
|
22
|
+
end
|
23
|
+
|
24
|
+
def reset_running
|
25
|
+
redis.set(running_key, 0)
|
26
|
+
end
|
27
|
+
|
28
|
+
def increment_running
|
29
|
+
Resque::Plugins::Approve::ApprovalKeyList.new.add_key approval_key
|
30
|
+
redis.incr(running_key)
|
31
|
+
end
|
32
|
+
|
33
|
+
def decrement_running
|
34
|
+
Resque::Plugins::Approve::ApprovalKeyList.new.add_key approval_key
|
35
|
+
redis.decr(running_key)
|
36
|
+
end
|
37
|
+
|
18
38
|
def pause
|
19
39
|
redis.set(pause_key, true)
|
20
40
|
redis.set(paused_count_key, 0)
|
@@ -89,6 +109,10 @@ module Resque
|
|
89
109
|
delete_job(id)
|
90
110
|
end
|
91
111
|
|
112
|
+
def remove_num(num_approve)
|
113
|
+
num_approve.times { remove_one }
|
114
|
+
end
|
115
|
+
|
92
116
|
def remove_all
|
93
117
|
true while remove_one
|
94
118
|
end
|
@@ -161,10 +185,16 @@ module Resque
|
|
161
185
|
@pause_key ||= "approve.job_queue.#{approval_key}.paused"
|
162
186
|
end
|
163
187
|
|
188
|
+
def running_key
|
189
|
+
@running_key ||= "approve.job_queue.#{approval_key}.running"
|
190
|
+
end
|
191
|
+
|
164
192
|
def paused_count_key
|
165
|
-
@
|
193
|
+
@paused_count_key ||= "approve.job_queue.#{approval_key}.paused.count"
|
166
194
|
end
|
167
195
|
end
|
168
196
|
end
|
169
197
|
end
|
170
198
|
end
|
199
|
+
|
200
|
+
# rubocop:enable Metrics/ClassLength
|
@@ -2,12 +2,15 @@
|
|
2
2
|
<tr>
|
3
3
|
<td>
|
4
4
|
<a href="<%= u("approve") %>/job_list?<%= { approval_key: pending_job.approval_key }.to_param %>">
|
5
|
-
<%= pending_job.approval_key %>
|
5
|
+
<%= pending_job.approval_key.presence || " ".html_safe %>
|
6
6
|
</a>
|
7
7
|
</td>
|
8
8
|
<td>
|
9
9
|
<%= pending_job.num_jobs.to_i %>
|
10
10
|
</td>
|
11
|
+
<td>
|
12
|
+
<%= pending_job.num_running.to_i %>
|
13
|
+
</td>
|
11
14
|
<td>
|
12
15
|
<% if pending_job.first_enqueued.present? %>
|
13
16
|
<%= time_ago_in_words(pending_job.first_enqueued) %> ago
|