resque-approve 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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +30 -0
- data/lib/resque-approve.rb +10 -0
- data/lib/resque/approve_server.rb +232 -0
- data/lib/resque/plugins/approve.rb +77 -0
- data/lib/resque/plugins/approve/approval_key_list.rb +100 -0
- data/lib/resque/plugins/approve/cleaner.rb +44 -0
- data/lib/resque/plugins/approve/pending_job.rb +187 -0
- data/lib/resque/plugins/approve/pending_job_queue.rb +128 -0
- data/lib/resque/plugins/approve/redis_access.rb +16 -0
- data/lib/resque/plugins/version.rb +9 -0
- data/lib/resque/server/public/approve.css +56 -0
- data/lib/resque/server/views/_approval_key_list_pagination.erb +67 -0
- data/lib/resque/server/views/_approval_key_rows.erb +18 -0
- data/lib/resque/server/views/_job_list_table.erb +30 -0
- data/lib/resque/server/views/_job_pagination.erb +67 -0
- data/lib/resque/server/views/approval_keys.erb +66 -0
- data/lib/resque/server/views/job_details.erb +82 -0
- data/lib/resque/server/views/job_list.erb +58 -0
- data/lib/tasks/resque-approve_tasks.rake +6 -0
- data/spec/approve/approval_key_list_spec.rb +289 -0
- data/spec/approve/cleaner_spec.rb +96 -0
- data/spec/approve/pending_job_queue_spec.rb +219 -0
- data/spec/approve/pending_job_spec.rb +326 -0
- data/spec/approve_spec.rb +188 -0
- data/spec/examples.txt +135 -0
- data/spec/rails_helper.rb +35 -0
- data/spec/server/public/approve.css_spec.rb +18 -0
- data/spec/server/views/approval_keys.erb_spec.rb +105 -0
- data/spec/server/views/job_details.erb_spec.rb +133 -0
- data/spec/server/views/job_list.erb_spec.rb +108 -0
- data/spec/spec_helper.rb +101 -0
- data/spec/support/config/redis-auth.yml +12 -0
- data/spec/support/jobs/01_basic_job.rb +13 -0
- data/spec/support/jobs/auto_delete_approval_key_job.rb +5 -0
- data/spec/support/purge_all.rb +15 -0
- metadata +292 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3dff2c5624ce3cd6a6890ca1637e18f883d43818
|
4
|
+
data.tar.gz: 4b85968da335eeb487665b6e192d3694f37e3566
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0a66cf31313bd8759eb15ac36ac59a09610bbe625529a87ec5fa3325967beb03ab0a689bce3a1ff51a9db8ede966a107bef0a54c2fece77b6944e380e5a230bf
|
7
|
+
data.tar.gz: d9636599c8dcfb360b192855182d2438a1ed4c2d1957cdf82db948664a1bab2900702eef52b6911e1b08d87dfd6bab72e6fac5ce28deadce50fffe00d15be9c8
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 RealNobody
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "bundler/setup"
|
5
|
+
rescue LoadError
|
6
|
+
puts "You must `gem install bundler` and `bundle install` to run rake tasks"
|
7
|
+
end
|
8
|
+
|
9
|
+
require "rdoc/task"
|
10
|
+
|
11
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
12
|
+
rdoc.rdoc_dir = "rdoc"
|
13
|
+
rdoc.title = "ResqueApprove"
|
14
|
+
rdoc.options << "--line-numbers"
|
15
|
+
rdoc.rdoc_files.include("README.rdoc")
|
16
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
17
|
+
end
|
18
|
+
|
19
|
+
Bundler::GemHelper.install_tasks
|
20
|
+
|
21
|
+
require "rake/testtask"
|
22
|
+
|
23
|
+
Rake::TestTask.new(:test) do |t|
|
24
|
+
t.libs << "lib"
|
25
|
+
t.libs << "rspec"
|
26
|
+
t.pattern = "spec/**/*_spec.rb"
|
27
|
+
t.verbose = false
|
28
|
+
end
|
29
|
+
|
30
|
+
task default: :test
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "resque"
|
4
|
+
require File.expand_path(File.join("resque", "plugins", "approve", "redis_access"), File.dirname(__FILE__))
|
5
|
+
require File.expand_path(File.join("resque", "plugins", "approve", "pending_job"), File.dirname(__FILE__))
|
6
|
+
require File.expand_path(File.join("resque", "plugins", "approve", "pending_job_queue"), File.dirname(__FILE__))
|
7
|
+
require File.expand_path(File.join("resque", "plugins", "approve", "approval_key_list"), File.dirname(__FILE__))
|
8
|
+
require File.expand_path(File.join("resque", "plugins", "approve", "cleaner"), File.dirname(__FILE__))
|
9
|
+
|
10
|
+
require File.expand_path(File.join("resque", "plugins", "approve"), File.dirname(__FILE__))
|
@@ -0,0 +1,232 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "resque"
|
4
|
+
require "resque/server"
|
5
|
+
require "resque-approve"
|
6
|
+
require "action_view/helpers/output_safety_helper"
|
7
|
+
require "action_view/helpers/capture_helper"
|
8
|
+
require "action_view/helpers/date_helper"
|
9
|
+
|
10
|
+
# rubocop:disable Metrics/ModuleLength
|
11
|
+
|
12
|
+
module Resque
|
13
|
+
# Extends Resque Web Based UI.
|
14
|
+
# Structure has been borrowed from ResqueHistory.
|
15
|
+
module ApproveServer
|
16
|
+
include ActionView::Helpers::DateHelper
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def erb_path(filename)
|
20
|
+
File.join(File.dirname(__FILE__), "server", "views", filename)
|
21
|
+
end
|
22
|
+
|
23
|
+
def public_path(filename)
|
24
|
+
File.join(File.dirname(__FILE__), "server", "public", filename)
|
25
|
+
end
|
26
|
+
|
27
|
+
def included(base)
|
28
|
+
add_page_views(base)
|
29
|
+
add_button_callbacks(base)
|
30
|
+
add_static_files(base)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def add_page_views(base)
|
36
|
+
approval_keys(base)
|
37
|
+
job_list(base)
|
38
|
+
job_details(base)
|
39
|
+
end
|
40
|
+
|
41
|
+
def approval_keys(base)
|
42
|
+
approval_key_params(base)
|
43
|
+
|
44
|
+
base.class_eval do
|
45
|
+
get "/approve" do
|
46
|
+
set_approval_key_params
|
47
|
+
|
48
|
+
erb File.read(Resque::ApproveServer.erb_path("approval_keys.erb"))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def approval_key_params(base)
|
54
|
+
base.class_eval do
|
55
|
+
def set_approval_key_params
|
56
|
+
@sort_by = params[:sort] || "approval_key"
|
57
|
+
@sort_order = params[:order] || "desc"
|
58
|
+
@page_num = (params[:page_num] || 1).to_i
|
59
|
+
@page_size = (params[:page_size] || 20).to_i
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def job_list(base)
|
65
|
+
job_list_params(base)
|
66
|
+
|
67
|
+
base.class_eval do
|
68
|
+
get "/approve/job_list" do
|
69
|
+
set_job_list_params_params
|
70
|
+
|
71
|
+
erb File.read(Resque::ApproveServer.erb_path("job_list.erb"))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def job_list_params(base)
|
77
|
+
base.class_eval do
|
78
|
+
def set_job_list_params_params
|
79
|
+
@approval_key = params[:approval_key]
|
80
|
+
@page_num = (params[:page_num] || 1).to_i
|
81
|
+
@page_size = (params[:page_size] || 20).to_i
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def job_details(base)
|
87
|
+
base.class_eval do
|
88
|
+
get "/approve/job_details" do
|
89
|
+
@job_id = params[:job_id]
|
90
|
+
|
91
|
+
erb File.read(Resque::ApproveServer.erb_path("job_details.erb"))
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def add_static_files(base)
|
97
|
+
base.class_eval do
|
98
|
+
get %r{approve/public/([a-z_]+\.[a-z]+)} do
|
99
|
+
send_file Resque::ApproveServer.public_path(params[:captures]&.first)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def add_button_callbacks(base)
|
105
|
+
audit_jobs(base)
|
106
|
+
audit_queues(base)
|
107
|
+
delete_all_queues(base)
|
108
|
+
approve_all_queues(base)
|
109
|
+
delete_queue(base)
|
110
|
+
delete_one_queue(base)
|
111
|
+
approve_queue(base)
|
112
|
+
approve_one_queue(base)
|
113
|
+
delete_job(base)
|
114
|
+
approve_job(base)
|
115
|
+
end
|
116
|
+
|
117
|
+
def audit_jobs(base)
|
118
|
+
base.class_eval do
|
119
|
+
post "/approve/audit_jobs" do
|
120
|
+
Resque::Plugins::Approve::Cleaner.cleanup_jobs
|
121
|
+
|
122
|
+
redirect u("approve")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def audit_queues(base)
|
128
|
+
base.class_eval do
|
129
|
+
post "/approve/audit_queues" do
|
130
|
+
Resque::Plugins::Approve::Cleaner.cleanup_queues
|
131
|
+
|
132
|
+
redirect u("approve")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def delete_all_queues(base)
|
138
|
+
base.class_eval do
|
139
|
+
post "/approve/delete_all_queues" do
|
140
|
+
Resque::Plugins::Approve::ApprovalKeyList.new.delete_all
|
141
|
+
|
142
|
+
redirect u("approve")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def approve_all_queues(base)
|
148
|
+
base.class_eval do
|
149
|
+
post "/approve/approve_all_queues" do
|
150
|
+
Resque::Plugins::Approve::ApprovalKeyList.new.approve_all
|
151
|
+
|
152
|
+
redirect u("approve")
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def delete_queue(base)
|
158
|
+
base.class_eval do
|
159
|
+
post "/approve/delete_queue" do
|
160
|
+
Resque::Plugins::Approve::PendingJobQueue.new(params[:approval_key]).delete
|
161
|
+
Resque::Plugins::Approve::ApprovalKeyList.new.remove_key(params[:approval_key])
|
162
|
+
|
163
|
+
redirect u("approve")
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def delete_one_queue(base)
|
169
|
+
base.class_eval do
|
170
|
+
post "/approve/delete_one_queue" do
|
171
|
+
Resque::Plugins::Approve::PendingJobQueue.new(params[:approval_key]).remove_one
|
172
|
+
|
173
|
+
redirect u("approve/job_list?#{{ approval_key: params[:approval_key] }.to_param}")
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def approve_queue(base)
|
179
|
+
base.class_eval do
|
180
|
+
post "/approve/approve_queue" do
|
181
|
+
Resque::Plugins::Approve::PendingJobQueue.new(params[:approval_key]).approve_all
|
182
|
+
|
183
|
+
redirect u("approve/job_list?#{{ approval_key: params[:approval_key] }.to_param}")
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def approve_one_queue(base)
|
189
|
+
base.class_eval do
|
190
|
+
post "/approve/approve_one_queue" do
|
191
|
+
Resque::Plugins::Approve::PendingJobQueue.new(params[:approval_key]).approve_one
|
192
|
+
|
193
|
+
redirect u("approve/job_list?#{{ approval_key: params[:approval_key] }.to_param}")
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def delete_job(base)
|
199
|
+
base.class_eval do
|
200
|
+
post "/approve/delete_job" do
|
201
|
+
job = Resque::Plugins::Approve::PendingJob.new(params[:job_id])
|
202
|
+
|
203
|
+
job.approval_key
|
204
|
+
job.delete
|
205
|
+
|
206
|
+
redirect u("approve/job_list?#{{ approval_key: job.approval_key }.to_param}")
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def approve_job(base)
|
212
|
+
base.class_eval do
|
213
|
+
post "/approve/approve_job" do
|
214
|
+
job = Resque::Plugins::Approve::PendingJob.new(params[:job_id])
|
215
|
+
|
216
|
+
job.enqueue_job
|
217
|
+
|
218
|
+
redirect u("approve/job_list?#{{ approval_key: job.approval_key }.to_param}")
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
Resque::Server.tabs << "Approve"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
Resque::Server.class_eval do
|
229
|
+
include Resque::ApproveServer
|
230
|
+
end
|
231
|
+
|
232
|
+
# rubocop:enable Metrics/ModuleLength
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
require "active_support/core_ext/numeric/time"
|
5
|
+
|
6
|
+
module Resque
|
7
|
+
module Plugins
|
8
|
+
# Include in a Resque Job to keep allow the hooks to run.
|
9
|
+
module Approve
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
included do
|
13
|
+
self.auto_delete_approval_key = false
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def approve(approval_key)
|
18
|
+
PendingJobQueue.new(approval_key).approve_all
|
19
|
+
end
|
20
|
+
|
21
|
+
def approve_one(approval_key)
|
22
|
+
PendingJobQueue.new(approval_key).approve_one
|
23
|
+
end
|
24
|
+
|
25
|
+
def remove(approval_key)
|
26
|
+
PendingJobQueue.new(approval_key).remove_all
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove_one(approval_key)
|
30
|
+
PendingJobQueue.new(approval_key).remove_one
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# The class methods added to the job class that is being enqueued to determine if it should be
|
35
|
+
# shunted to the list of pending jobs, or enqueued.
|
36
|
+
module ClassMethods
|
37
|
+
def auto_delete_approval_key=(value)
|
38
|
+
@auto_delete_approval_key = value
|
39
|
+
end
|
40
|
+
|
41
|
+
def auto_delete_approval_key
|
42
|
+
@auto_delete_approval_key
|
43
|
+
end
|
44
|
+
|
45
|
+
# It is possible to run a job immediately using `Resque.push`. This will bypass the queue and run
|
46
|
+
# the job immediately. This will prevent such a job from enqueuing, and instead pause it for approval
|
47
|
+
#
|
48
|
+
# The primary reason for this is to prevent the job from receiving the approval parameters it is not
|
49
|
+
# supposed to have when actually run/enqueued.
|
50
|
+
def before_perform_approve(*args)
|
51
|
+
# Check if the job needs to be approved, and if so, do not enqueue it.
|
52
|
+
job = PendingJob.new(SecureRandom.uuid, class_name: name, args: args)
|
53
|
+
|
54
|
+
if job.requires_approval?
|
55
|
+
ApprovalKeyList.new.add_job(job)
|
56
|
+
|
57
|
+
raise Resque::Job::DontPerform, "The job has not been approved yet."
|
58
|
+
else
|
59
|
+
true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Check if the job needs to be approved, and if so, do not enqueue it.
|
64
|
+
def before_enqueue_approve(*args)
|
65
|
+
job = PendingJob.new(SecureRandom.uuid, class_name: name, args: args)
|
66
|
+
|
67
|
+
if job.requires_approval?
|
68
|
+
ApprovalKeyList.new.add_job(job)
|
69
|
+
false
|
70
|
+
else
|
71
|
+
true
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Resque
|
4
|
+
module Plugins
|
5
|
+
module Approve
|
6
|
+
# A class representing a queue of Pending job_queues.
|
7
|
+
#
|
8
|
+
# The queue is named with the approval_key for all of the job_queues in the queue and contains a list of the job_queues.
|
9
|
+
class ApprovalKeyList
|
10
|
+
include Resque::Plugins::Approve::RedisAccess
|
11
|
+
|
12
|
+
def order_param(sort_option, current_sort, current_order)
|
13
|
+
current_order ||= "asc"
|
14
|
+
|
15
|
+
if sort_option == current_sort
|
16
|
+
current_order == "asc" ? "desc" : "asc"
|
17
|
+
else
|
18
|
+
"asc"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def remove_key(key)
|
23
|
+
redis.srem(list_key, key)
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_key(key)
|
27
|
+
redis.sadd(list_key, key)
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_job(job)
|
31
|
+
add_key(job.approval_key)
|
32
|
+
|
33
|
+
Resque::Plugins::Approve::PendingJobQueue.new(job.approval_key).add_job(job)
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete_all
|
37
|
+
queues.each do |queue|
|
38
|
+
queue.delete
|
39
|
+
remove_key(queue.approval_key)
|
40
|
+
end
|
41
|
+
|
42
|
+
redis.del(list_key)
|
43
|
+
end
|
44
|
+
|
45
|
+
def approve_all
|
46
|
+
queues.each(&:approve_all)
|
47
|
+
end
|
48
|
+
|
49
|
+
def queues(sort_key = :approval_key,
|
50
|
+
sort_order = "asc",
|
51
|
+
page_num = 1,
|
52
|
+
queue_page_size = 20)
|
53
|
+
queue_page_size = queue_page_size.to_i
|
54
|
+
queue_page_size = 20 if queue_page_size < 1
|
55
|
+
|
56
|
+
job_queues = sorted_job_queues(sort_key)
|
57
|
+
|
58
|
+
page_start = (page_num - 1) * queue_page_size
|
59
|
+
page_start = 0 if page_start > job_queues.length || page_start.negative?
|
60
|
+
|
61
|
+
(sort_order == "desc" ? job_queues.reverse : job_queues)[page_start..(page_start + queue_page_size - 1)]
|
62
|
+
end
|
63
|
+
|
64
|
+
def job_queues
|
65
|
+
@job_queues ||= queue_keys.map { |approval_key| Resque::Plugins::Approve::PendingJobQueue.new(approval_key) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def queue_keys
|
69
|
+
@queue_keys ||= redis.smembers(list_key)
|
70
|
+
end
|
71
|
+
|
72
|
+
def num_queues
|
73
|
+
queue_keys.length
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def list_key
|
79
|
+
@list_key ||= "approve.approval_key_list"
|
80
|
+
end
|
81
|
+
|
82
|
+
def sorted_job_queues(sort_key)
|
83
|
+
job_queues.sort_by do |job_queue|
|
84
|
+
approval_key_sort_value(job_queue, sort_key)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def approval_key_sort_value(job_queue, sort_key)
|
89
|
+
case sort_key.to_sym
|
90
|
+
when :approval_key,
|
91
|
+
:num_jobs
|
92
|
+
job_queue.public_send(sort_key)
|
93
|
+
when :first_enqueued
|
94
|
+
job_queue.public_send(sort_key).to_s
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|