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