resque-approve 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +30 -0
  4. data/lib/resque-approve.rb +10 -0
  5. data/lib/resque/approve_server.rb +232 -0
  6. data/lib/resque/plugins/approve.rb +77 -0
  7. data/lib/resque/plugins/approve/approval_key_list.rb +100 -0
  8. data/lib/resque/plugins/approve/cleaner.rb +44 -0
  9. data/lib/resque/plugins/approve/pending_job.rb +187 -0
  10. data/lib/resque/plugins/approve/pending_job_queue.rb +128 -0
  11. data/lib/resque/plugins/approve/redis_access.rb +16 -0
  12. data/lib/resque/plugins/version.rb +9 -0
  13. data/lib/resque/server/public/approve.css +56 -0
  14. data/lib/resque/server/views/_approval_key_list_pagination.erb +67 -0
  15. data/lib/resque/server/views/_approval_key_rows.erb +18 -0
  16. data/lib/resque/server/views/_job_list_table.erb +30 -0
  17. data/lib/resque/server/views/_job_pagination.erb +67 -0
  18. data/lib/resque/server/views/approval_keys.erb +66 -0
  19. data/lib/resque/server/views/job_details.erb +82 -0
  20. data/lib/resque/server/views/job_list.erb +58 -0
  21. data/lib/tasks/resque-approve_tasks.rake +6 -0
  22. data/spec/approve/approval_key_list_spec.rb +289 -0
  23. data/spec/approve/cleaner_spec.rb +96 -0
  24. data/spec/approve/pending_job_queue_spec.rb +219 -0
  25. data/spec/approve/pending_job_spec.rb +326 -0
  26. data/spec/approve_spec.rb +188 -0
  27. data/spec/examples.txt +135 -0
  28. data/spec/rails_helper.rb +35 -0
  29. data/spec/server/public/approve.css_spec.rb +18 -0
  30. data/spec/server/views/approval_keys.erb_spec.rb +105 -0
  31. data/spec/server/views/job_details.erb_spec.rb +133 -0
  32. data/spec/server/views/job_list.erb_spec.rb +108 -0
  33. data/spec/spec_helper.rb +101 -0
  34. data/spec/support/config/redis-auth.yml +12 -0
  35. data/spec/support/jobs/01_basic_job.rb +13 -0
  36. data/spec/support/jobs/auto_delete_approval_key_job.rb +5 -0
  37. data/spec/support/purge_all.rb +15 -0
  38. metadata +292 -0
@@ -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
@@ -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.
@@ -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