sidekiq 2.15.1 → 4.2.10

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq might be problematic. Click here for more details.

Files changed (187) hide show
  1. checksums.yaml +7 -0
  2. data/.github/contributing.md +32 -0
  3. data/.github/issue_template.md +9 -0
  4. data/.gitignore +1 -0
  5. data/.travis.yml +16 -17
  6. data/3.0-Upgrade.md +70 -0
  7. data/4.0-Upgrade.md +53 -0
  8. data/COMM-LICENSE +56 -44
  9. data/Changes.md +644 -1
  10. data/Ent-Changes.md +173 -0
  11. data/Gemfile +27 -0
  12. data/LICENSE +1 -1
  13. data/Pro-2.0-Upgrade.md +138 -0
  14. data/Pro-3.0-Upgrade.md +44 -0
  15. data/Pro-Changes.md +457 -3
  16. data/README.md +46 -29
  17. data/Rakefile +6 -3
  18. data/bin/sidekiq +4 -0
  19. data/bin/sidekiqctl +41 -20
  20. data/bin/sidekiqload +154 -0
  21. data/code_of_conduct.md +50 -0
  22. data/lib/generators/sidekiq/templates/worker.rb.erb +9 -0
  23. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +6 -0
  24. data/lib/generators/sidekiq/templates/worker_test.rb.erb +8 -0
  25. data/lib/generators/sidekiq/worker_generator.rb +49 -0
  26. data/lib/sidekiq.rb +141 -29
  27. data/lib/sidekiq/api.rb +540 -106
  28. data/lib/sidekiq/cli.rb +131 -71
  29. data/lib/sidekiq/client.rb +168 -96
  30. data/lib/sidekiq/core_ext.rb +36 -8
  31. data/lib/sidekiq/exception_handler.rb +20 -28
  32. data/lib/sidekiq/extensions/action_mailer.rb +25 -5
  33. data/lib/sidekiq/extensions/active_record.rb +8 -4
  34. data/lib/sidekiq/extensions/class_methods.rb +9 -5
  35. data/lib/sidekiq/extensions/generic_proxy.rb +1 -0
  36. data/lib/sidekiq/fetch.rb +45 -101
  37. data/lib/sidekiq/launcher.rb +144 -30
  38. data/lib/sidekiq/logging.rb +69 -12
  39. data/lib/sidekiq/manager.rb +90 -140
  40. data/lib/sidekiq/middleware/chain.rb +18 -5
  41. data/lib/sidekiq/middleware/i18n.rb +9 -2
  42. data/lib/sidekiq/middleware/server/active_record.rb +1 -1
  43. data/lib/sidekiq/middleware/server/logging.rb +11 -11
  44. data/lib/sidekiq/middleware/server/retry_jobs.rb +98 -44
  45. data/lib/sidekiq/paginator.rb +20 -8
  46. data/lib/sidekiq/processor.rb +157 -96
  47. data/lib/sidekiq/rails.rb +109 -5
  48. data/lib/sidekiq/redis_connection.rb +70 -24
  49. data/lib/sidekiq/scheduled.rb +122 -50
  50. data/lib/sidekiq/testing.rb +171 -31
  51. data/lib/sidekiq/testing/inline.rb +1 -0
  52. data/lib/sidekiq/util.rb +31 -5
  53. data/lib/sidekiq/version.rb +2 -1
  54. data/lib/sidekiq/web.rb +136 -263
  55. data/lib/sidekiq/web/action.rb +93 -0
  56. data/lib/sidekiq/web/application.rb +336 -0
  57. data/lib/sidekiq/web/helpers.rb +278 -0
  58. data/lib/sidekiq/web/router.rb +100 -0
  59. data/lib/sidekiq/worker.rb +40 -7
  60. data/sidekiq.gemspec +18 -14
  61. data/web/assets/images/favicon.ico +0 -0
  62. data/web/assets/images/{status-sd8051fd480.png → status.png} +0 -0
  63. data/web/assets/javascripts/application.js +67 -19
  64. data/web/assets/javascripts/dashboard.js +138 -29
  65. data/web/assets/stylesheets/application.css +267 -406
  66. data/web/assets/stylesheets/bootstrap.css +4 -8
  67. data/web/locales/cs.yml +78 -0
  68. data/web/locales/da.yml +9 -1
  69. data/web/locales/de.yml +18 -9
  70. data/web/locales/el.yml +68 -0
  71. data/web/locales/en.yml +19 -4
  72. data/web/locales/es.yml +10 -1
  73. data/web/locales/fa.yml +79 -0
  74. data/web/locales/fr.yml +50 -32
  75. data/web/locales/hi.yml +75 -0
  76. data/web/locales/it.yml +27 -18
  77. data/web/locales/ja.yml +27 -12
  78. data/web/locales/ko.yml +8 -3
  79. data/web/locales/{no.yml → nb.yml} +19 -5
  80. data/web/locales/nl.yml +8 -3
  81. data/web/locales/pl.yml +0 -1
  82. data/web/locales/pt-br.yml +11 -4
  83. data/web/locales/pt.yml +8 -1
  84. data/web/locales/ru.yml +39 -21
  85. data/web/locales/sv.yml +68 -0
  86. data/web/locales/ta.yml +75 -0
  87. data/web/locales/uk.yml +76 -0
  88. data/web/locales/zh-cn.yml +68 -0
  89. data/web/locales/zh-tw.yml +68 -0
  90. data/web/views/_footer.erb +17 -0
  91. data/web/views/_job_info.erb +72 -60
  92. data/web/views/_nav.erb +58 -25
  93. data/web/views/_paging.erb +5 -5
  94. data/web/views/_poll_link.erb +7 -0
  95. data/web/views/_summary.erb +20 -14
  96. data/web/views/busy.erb +94 -0
  97. data/web/views/dashboard.erb +34 -21
  98. data/web/views/dead.erb +34 -0
  99. data/web/views/layout.erb +8 -30
  100. data/web/views/morgue.erb +75 -0
  101. data/web/views/queue.erb +37 -30
  102. data/web/views/queues.erb +26 -20
  103. data/web/views/retries.erb +60 -47
  104. data/web/views/retry.erb +23 -19
  105. data/web/views/scheduled.erb +39 -35
  106. data/web/views/scheduled_job_info.erb +2 -1
  107. metadata +152 -195
  108. data/Contributing.md +0 -29
  109. data/config.ru +0 -18
  110. data/lib/sidekiq/actor.rb +0 -7
  111. data/lib/sidekiq/capistrano.rb +0 -54
  112. data/lib/sidekiq/yaml_patch.rb +0 -21
  113. data/test/config.yml +0 -11
  114. data/test/env_based_config.yml +0 -11
  115. data/test/fake_env.rb +0 -0
  116. data/test/helper.rb +0 -42
  117. data/test/test_api.rb +0 -341
  118. data/test/test_cli.rb +0 -326
  119. data/test/test_client.rb +0 -211
  120. data/test/test_exception_handler.rb +0 -124
  121. data/test/test_extensions.rb +0 -105
  122. data/test/test_fetch.rb +0 -44
  123. data/test/test_manager.rb +0 -83
  124. data/test/test_middleware.rb +0 -135
  125. data/test/test_processor.rb +0 -160
  126. data/test/test_redis_connection.rb +0 -97
  127. data/test/test_retry.rb +0 -306
  128. data/test/test_scheduled.rb +0 -86
  129. data/test/test_scheduling.rb +0 -47
  130. data/test/test_sidekiq.rb +0 -37
  131. data/test/test_testing.rb +0 -82
  132. data/test/test_testing_fake.rb +0 -265
  133. data/test/test_testing_inline.rb +0 -92
  134. data/test/test_util.rb +0 -18
  135. data/test/test_web.rb +0 -372
  136. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  137. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  138. data/web/assets/images/status/active.png +0 -0
  139. data/web/assets/images/status/idle.png +0 -0
  140. data/web/assets/javascripts/locales/README.md +0 -27
  141. data/web/assets/javascripts/locales/jquery.timeago.ar.js +0 -96
  142. data/web/assets/javascripts/locales/jquery.timeago.bg.js +0 -18
  143. data/web/assets/javascripts/locales/jquery.timeago.bs.js +0 -49
  144. data/web/assets/javascripts/locales/jquery.timeago.ca.js +0 -18
  145. data/web/assets/javascripts/locales/jquery.timeago.cy.js +0 -20
  146. data/web/assets/javascripts/locales/jquery.timeago.cz.js +0 -18
  147. data/web/assets/javascripts/locales/jquery.timeago.da.js +0 -18
  148. data/web/assets/javascripts/locales/jquery.timeago.de.js +0 -18
  149. data/web/assets/javascripts/locales/jquery.timeago.el.js +0 -18
  150. data/web/assets/javascripts/locales/jquery.timeago.en-short.js +0 -20
  151. data/web/assets/javascripts/locales/jquery.timeago.en.js +0 -20
  152. data/web/assets/javascripts/locales/jquery.timeago.es.js +0 -18
  153. data/web/assets/javascripts/locales/jquery.timeago.et.js +0 -18
  154. data/web/assets/javascripts/locales/jquery.timeago.fa.js +0 -22
  155. data/web/assets/javascripts/locales/jquery.timeago.fi.js +0 -28
  156. data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +0 -16
  157. data/web/assets/javascripts/locales/jquery.timeago.fr.js +0 -17
  158. data/web/assets/javascripts/locales/jquery.timeago.he.js +0 -18
  159. data/web/assets/javascripts/locales/jquery.timeago.hr.js +0 -49
  160. data/web/assets/javascripts/locales/jquery.timeago.hu.js +0 -18
  161. data/web/assets/javascripts/locales/jquery.timeago.hy.js +0 -18
  162. data/web/assets/javascripts/locales/jquery.timeago.id.js +0 -18
  163. data/web/assets/javascripts/locales/jquery.timeago.it.js +0 -16
  164. data/web/assets/javascripts/locales/jquery.timeago.ja.js +0 -19
  165. data/web/assets/javascripts/locales/jquery.timeago.ko.js +0 -17
  166. data/web/assets/javascripts/locales/jquery.timeago.lt.js +0 -20
  167. data/web/assets/javascripts/locales/jquery.timeago.mk.js +0 -20
  168. data/web/assets/javascripts/locales/jquery.timeago.nl.js +0 -20
  169. data/web/assets/javascripts/locales/jquery.timeago.no.js +0 -18
  170. data/web/assets/javascripts/locales/jquery.timeago.pl.js +0 -31
  171. data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +0 -16
  172. data/web/assets/javascripts/locales/jquery.timeago.pt.js +0 -16
  173. data/web/assets/javascripts/locales/jquery.timeago.ro.js +0 -18
  174. data/web/assets/javascripts/locales/jquery.timeago.rs.js +0 -49
  175. data/web/assets/javascripts/locales/jquery.timeago.ru.js +0 -34
  176. data/web/assets/javascripts/locales/jquery.timeago.sk.js +0 -18
  177. data/web/assets/javascripts/locales/jquery.timeago.sl.js +0 -44
  178. data/web/assets/javascripts/locales/jquery.timeago.sv.js +0 -18
  179. data/web/assets/javascripts/locales/jquery.timeago.th.js +0 -20
  180. data/web/assets/javascripts/locales/jquery.timeago.tr.js +0 -16
  181. data/web/assets/javascripts/locales/jquery.timeago.uk.js +0 -34
  182. data/web/assets/javascripts/locales/jquery.timeago.uz.js +0 -19
  183. data/web/assets/javascripts/locales/jquery.timeago.zh-CN.js +0 -20
  184. data/web/assets/javascripts/locales/jquery.timeago.zh-TW.js +0 -20
  185. data/web/views/_poll.erb +0 -14
  186. data/web/views/_workers.erb +0 -29
  187. data/web/views/index.erb +0 -16
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ class WebAction
5
+ RACK_SESSION = 'rack.session'.freeze
6
+
7
+ attr_accessor :env, :block, :type
8
+
9
+ def settings
10
+ Web.settings
11
+ end
12
+
13
+ def request
14
+ @request ||= ::Rack::Request.new(env)
15
+ end
16
+
17
+ def halt(res)
18
+ throw :halt, res
19
+ end
20
+
21
+ def redirect(location)
22
+ throw :halt, [302, { "Location" => "#{request.base_url}#{location}" }, []]
23
+ end
24
+
25
+ def params
26
+ indifferent_hash = Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
27
+
28
+ indifferent_hash.merge! request.params
29
+ route_params.each {|k,v| indifferent_hash[k.to_s] = v }
30
+
31
+ indifferent_hash
32
+ end
33
+
34
+ def route_params
35
+ env[WebRouter::ROUTE_PARAMS]
36
+ end
37
+
38
+ def session
39
+ env[RACK_SESSION]
40
+ end
41
+
42
+ def content_type(type)
43
+ @type = type
44
+ end
45
+
46
+ def erb(content, options = {})
47
+ if content.kind_of? Symbol
48
+ unless respond_to?(:"_erb_#{content}")
49
+ src = ERB.new(File.read("#{Web.settings.views}/#{content}.erb")).src
50
+ WebAction.class_eval("def _erb_#{content}\n#{src}\n end")
51
+ end
52
+ end
53
+
54
+ if @_erb
55
+ _erb(content, options[:locals])
56
+ else
57
+ @_erb = true
58
+ content = _erb(content, options[:locals])
59
+
60
+ _render { content }
61
+ end
62
+ end
63
+
64
+ def render(engine, content, options = {})
65
+ raise "Only erb templates are supported" if engine != :erb
66
+
67
+ erb(content, options)
68
+ end
69
+
70
+ def json(payload)
71
+ [200, { "Content-Type" => "application/json", "Cache-Control" => "no-cache" }, [Sidekiq.dump_json(payload)]]
72
+ end
73
+
74
+ def initialize(env, block)
75
+ @_erb = false
76
+ @env = env
77
+ @block = block
78
+ @@files ||= {}
79
+ end
80
+
81
+ private
82
+
83
+ def _erb(file, locals)
84
+ locals.each {|k, v| define_singleton_method(k){ v } } if locals
85
+
86
+ if file.kind_of?(String)
87
+ ERB.new(file).result(binding)
88
+ else
89
+ send(:"_erb_#{file}")
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,336 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ class WebApplication
5
+ extend WebRouter
6
+
7
+ CONTENT_LENGTH = "Content-Length".freeze
8
+ CONTENT_TYPE = "Content-Type".freeze
9
+ REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human)
10
+
11
+ def initialize(klass)
12
+ @klass = klass
13
+ end
14
+
15
+ def settings
16
+ @klass.settings
17
+ end
18
+
19
+ def self.settings
20
+ Sidekiq::Web.settings
21
+ end
22
+
23
+ def self.tabs
24
+ Sidekiq::Web.tabs
25
+ end
26
+
27
+ def self.set(key, val)
28
+ # nothing, backwards compatibility
29
+ end
30
+
31
+ get "/" do
32
+ @redis_info = redis_info.select{ |k, v| REDIS_KEYS.include? k }
33
+ stats_history = Sidekiq::Stats::History.new((params['days'] || 30).to_i)
34
+ @processed_history = stats_history.processed
35
+ @failed_history = stats_history.failed
36
+
37
+ erb(:dashboard)
38
+ end
39
+
40
+ get "/busy" do
41
+ erb(:busy)
42
+ end
43
+
44
+ post "/busy" do
45
+ if params['identity']
46
+ p = Sidekiq::Process.new('identity' => params['identity'])
47
+ p.quiet! if params['quiet']
48
+ p.stop! if params['stop']
49
+ else
50
+ processes.each do |pro|
51
+ pro.quiet! if params['quiet']
52
+ pro.stop! if params['stop']
53
+ end
54
+ end
55
+
56
+ redirect "#{root_path}busy"
57
+ end
58
+
59
+ get "/queues" do
60
+ @queues = Sidekiq::Queue.all
61
+
62
+ erb(:queues)
63
+ end
64
+
65
+ get "/queues/:name" do
66
+ @name = route_params[:name]
67
+
68
+ halt(404) unless @name
69
+
70
+ @count = (params['count'] || 25).to_i
71
+ @queue = Sidekiq::Queue.new(@name)
72
+ (@current_page, @total_size, @messages) = page("queue:#{@name}", params['page'], @count)
73
+ @messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) }
74
+
75
+ erb(:queue)
76
+ end
77
+
78
+ post "/queues/:name" do
79
+ Sidekiq::Queue.new(route_params[:name]).clear
80
+
81
+ redirect "#{root_path}queues"
82
+ end
83
+
84
+ post "/queues/:name/delete" do
85
+ name = route_params[:name]
86
+ Sidekiq::Job.new(params['key_val'], name).delete
87
+
88
+ redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
89
+ end
90
+
91
+ get '/morgue' do
92
+ @count = (params['count'] || 25).to_i
93
+ (@current_page, @total_size, @dead) = page("dead", params['page'], @count, reverse: true)
94
+ @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
95
+
96
+ erb(:morgue)
97
+ end
98
+
99
+ get "/morgue/:key" do
100
+ halt(404) unless key = route_params[:key]
101
+
102
+ @dead = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
103
+
104
+ if @dead.nil?
105
+ redirect "#{root_path}morgue"
106
+ else
107
+ erb(:dead)
108
+ end
109
+ end
110
+
111
+ post '/morgue' do
112
+ redirect(request.path) unless params['key']
113
+
114
+ params['key'].each do |key|
115
+ job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
116
+ retry_or_delete_or_kill job, params if job
117
+ end
118
+
119
+ redirect_with_query("#{root_path}morgue")
120
+ end
121
+
122
+ post "/morgue/all/delete" do
123
+ Sidekiq::DeadSet.new.clear
124
+
125
+ redirect "#{root_path}morgue"
126
+ end
127
+
128
+ post "/morgue/all/retry" do
129
+ Sidekiq::DeadSet.new.retry_all
130
+
131
+ redirect "#{root_path}morgue"
132
+ end
133
+
134
+ post "/morgue/:key" do
135
+ halt(404) unless key = route_params[:key]
136
+
137
+ job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
138
+ retry_or_delete_or_kill job, params if job
139
+
140
+ redirect_with_query("#{root_path}morgue")
141
+ end
142
+
143
+ get '/retries' do
144
+ @count = (params['count'] || 25).to_i
145
+ (@current_page, @total_size, @retries) = page("retry", params['page'], @count)
146
+ @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
147
+
148
+ erb(:retries)
149
+ end
150
+
151
+ get "/retries/:key" do
152
+ @retry = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
153
+
154
+ if @retry.nil?
155
+ redirect "#{root_path}retries"
156
+ else
157
+ erb(:retry)
158
+ end
159
+ end
160
+
161
+ post '/retries' do
162
+ redirect(request.path) unless params['key']
163
+
164
+ params['key'].each do |key|
165
+ job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first
166
+ retry_or_delete_or_kill job, params if job
167
+ end
168
+
169
+ redirect_with_query("#{root_path}retries")
170
+ end
171
+
172
+ post "/retries/all/delete" do
173
+ Sidekiq::RetrySet.new.clear
174
+
175
+ redirect "#{root_path}retries"
176
+ end
177
+
178
+ post "/retries/all/retry" do
179
+ Sidekiq::RetrySet.new.retry_all
180
+
181
+ redirect "#{root_path}retries"
182
+ end
183
+
184
+ post "/retries/:key" do
185
+ job = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
186
+
187
+ retry_or_delete_or_kill job, params if job
188
+
189
+ redirect_with_query("#{root_path}retries")
190
+ end
191
+
192
+ get '/scheduled' do
193
+ @count = (params['count'] || 25).to_i
194
+ (@current_page, @total_size, @scheduled) = page("schedule", params['page'], @count)
195
+ @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
196
+
197
+ erb(:scheduled)
198
+ end
199
+
200
+ get "/scheduled/:key" do
201
+ @job = Sidekiq::ScheduledSet.new.fetch(*parse_params(route_params[:key])).first
202
+
203
+ if @job.nil?
204
+ redirect "#{root_path}scheduled"
205
+ else
206
+ erb(:scheduled_job_info)
207
+ end
208
+ end
209
+
210
+ post '/scheduled' do
211
+ redirect(request.path) unless params['key']
212
+
213
+ params['key'].each do |key|
214
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
215
+ delete_or_add_queue job, params if job
216
+ end
217
+
218
+ redirect_with_query("#{root_path}scheduled")
219
+ end
220
+
221
+ post "/scheduled/:key" do
222
+ halt(404) unless key = route_params[:key]
223
+
224
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
225
+ delete_or_add_queue job, params if job
226
+
227
+ redirect_with_query("#{root_path}scheduled")
228
+ end
229
+
230
+ get '/dashboard/stats' do
231
+ redirect "#{root_path}stats"
232
+ end
233
+
234
+ get '/stats' do
235
+ sidekiq_stats = Sidekiq::Stats.new
236
+ redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
237
+
238
+ json(
239
+ sidekiq: {
240
+ processed: sidekiq_stats.processed,
241
+ failed: sidekiq_stats.failed,
242
+ busy: sidekiq_stats.workers_size,
243
+ processes: sidekiq_stats.processes_size,
244
+ enqueued: sidekiq_stats.enqueued,
245
+ scheduled: sidekiq_stats.scheduled_size,
246
+ retries: sidekiq_stats.retry_size,
247
+ dead: sidekiq_stats.dead_size,
248
+ default_latency: sidekiq_stats.default_queue_latency
249
+ },
250
+ redis: redis_stats
251
+ )
252
+ end
253
+
254
+ get '/stats/queues' do
255
+ json Sidekiq::Stats::Queues.new.lengths
256
+ end
257
+
258
+ def call(env)
259
+ action = self.class.match(env)
260
+ return [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass" }, ["Not Found"]] unless action
261
+
262
+ resp = catch(:halt) do
263
+ app = @klass
264
+ self.class.run_befores(app, action)
265
+ begin
266
+ resp = action.instance_exec env, &action.block
267
+ ensure
268
+ self.class.run_afters(app, action)
269
+ end
270
+
271
+ resp
272
+ end
273
+
274
+ resp = case resp
275
+ when Array
276
+ resp
277
+ when Integer
278
+ [resp, {}, []]
279
+ else
280
+ type_header = case action.type
281
+ when :json
282
+ { "Content-Type" => "application/json", "Cache-Control" => "no-cache" }
283
+ when String
284
+ { "Content-Type" => (action.type || "text/html"), "Cache-Control" => "no-cache" }
285
+ else
286
+ { "Content-Type" => "text/html", "Cache-Control" => "no-cache" }
287
+ end
288
+
289
+ [200, type_header, [resp]]
290
+ end
291
+
292
+ resp[1] = resp[1].dup
293
+
294
+ resp[1][CONTENT_LENGTH] = resp[2].inject(0) { |l, p| l + p.bytesize }.to_s
295
+
296
+ resp
297
+ end
298
+
299
+ def self.helpers(mod=nil, &block)
300
+ if block_given?
301
+ WebAction.class_eval(&block)
302
+ else
303
+ WebAction.send(:include, mod)
304
+ end
305
+ end
306
+
307
+ def self.before(path=nil, &block)
308
+ befores << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
309
+ end
310
+
311
+ def self.after(path=nil, &block)
312
+ afters << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
313
+ end
314
+
315
+ def self.run_befores(app, action)
316
+ run_hooks(befores, app, action)
317
+ end
318
+
319
+ def self.run_afters(app, action)
320
+ run_hooks(afters, app, action)
321
+ end
322
+
323
+ def self.run_hooks(hooks, app, action)
324
+ hooks.select { |p,_| !p || p =~ action.env[WebRouter::PATH_INFO] }.
325
+ each {|_,b| action.instance_exec(action.env, app, &b) }
326
+ end
327
+
328
+ def self.befores
329
+ @befores ||= []
330
+ end
331
+
332
+ def self.afters
333
+ @afters ||= []
334
+ end
335
+ end
336
+ end
@@ -0,0 +1,278 @@
1
+ # frozen_string_literal: true
2
+ require 'uri'
3
+ require 'set'
4
+ require 'yaml'
5
+ require 'cgi'
6
+
7
+ module Sidekiq
8
+ # This is not a public API
9
+ module WebHelpers
10
+ def strings(lang)
11
+ @@strings ||= {}
12
+ @@strings[lang] ||= begin
13
+ # Allow sidekiq-web extensions to add locale paths
14
+ # so extensions can be localized
15
+ settings.locales.each_with_object({}) do |path, global|
16
+ find_locale_files(lang).each do |file|
17
+ strs = YAML.load(File.open(file))
18
+ global.deep_merge!(strs[lang])
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ def clear_caches
25
+ @@strings = nil
26
+ @@locale_files = nil
27
+ end
28
+
29
+ def locale_files
30
+ @@locale_files ||= settings.locales.flat_map do |path|
31
+ Dir["#{path}/*.yml"]
32
+ end
33
+ end
34
+
35
+ def find_locale_files(lang)
36
+ locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
37
+ end
38
+
39
+ # This is a hook for a Sidekiq Pro feature. Please don't touch.
40
+ def filtering(*)
41
+ end
42
+
43
+ # This view helper provide ability display you html code in
44
+ # to head of page. Example:
45
+ #
46
+ # <% add_to_head do %>
47
+ # <link rel="stylesheet" .../>
48
+ # <meta .../>
49
+ # <% end %>
50
+ #
51
+ def add_to_head
52
+ @head_html ||= []
53
+ @head_html << yield.dup if block_given?
54
+ end
55
+
56
+ def display_custom_head
57
+ @head_html.join if defined?(@head_html)
58
+ end
59
+
60
+ def poll_path
61
+ if current_path != '' && params['poll']
62
+ root_path + current_path
63
+ else
64
+ ""
65
+ end
66
+ end
67
+
68
+ # Given a browser request Accept-Language header like
69
+ # "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2", this function
70
+ # will return "fr" since that's the first code with a matching
71
+ # locale in web/locales
72
+ def locale
73
+ @locale ||= begin
74
+ locale = 'en'.freeze
75
+ languages = env['HTTP_ACCEPT_LANGUAGE'.freeze] || 'en'.freeze
76
+ languages.downcase.split(','.freeze).each do |lang|
77
+ next if lang == '*'.freeze
78
+ lang = lang.split(';'.freeze)[0]
79
+ break locale = lang if find_locale_files(lang).any?
80
+ end
81
+ locale
82
+ end
83
+ end
84
+
85
+ # mperham/sidekiq#3243
86
+ def unfiltered?
87
+ yield unless env['PATH_INFO'].start_with?("/filter/")
88
+ end
89
+
90
+ def get_locale
91
+ strings(locale)
92
+ end
93
+
94
+ def t(msg, options={})
95
+ string = get_locale[msg] || msg
96
+ if options.empty?
97
+ string
98
+ else
99
+ string % options
100
+ end
101
+ end
102
+
103
+ def workers
104
+ @workers ||= Sidekiq::Workers.new
105
+ end
106
+
107
+ def processes
108
+ @processes ||= Sidekiq::ProcessSet.new
109
+ end
110
+
111
+ def stats
112
+ @stats ||= Sidekiq::Stats.new
113
+ end
114
+
115
+ def retries_with_score(score)
116
+ Sidekiq.redis do |conn|
117
+ conn.zrangebyscore('retry', score, score)
118
+ end.map { |msg| Sidekiq.load_json(msg) }
119
+ end
120
+
121
+ def redis_connection
122
+ Sidekiq.redis { |conn| conn.client.id }
123
+ end
124
+
125
+ def namespace
126
+ @@ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
127
+ end
128
+
129
+ def redis_info
130
+ Sidekiq.redis_info
131
+ end
132
+
133
+ def root_path
134
+ "#{env['SCRIPT_NAME']}/"
135
+ end
136
+
137
+ def current_path
138
+ @current_path ||= request.path_info.gsub(/^\//,'')
139
+ end
140
+
141
+ def current_status
142
+ workers.size == 0 ? 'idle' : 'active'
143
+ end
144
+
145
+ def relative_time(time)
146
+ stamp = time.getutc.iso8601
147
+ %{<time title="#{stamp}" datetime="#{stamp}">#{time}</time>}
148
+ end
149
+
150
+ def job_params(job, score)
151
+ "#{score}-#{job['jid']}"
152
+ end
153
+
154
+ def parse_params(params)
155
+ score, jid = params.split("-")
156
+ [score.to_f, jid]
157
+ end
158
+
159
+ SAFE_QPARAMS = %w(page poll)
160
+
161
+ # Merge options with current params, filter safe params, and stringify to query string
162
+ def qparams(options)
163
+ options = options.stringify_keys
164
+ params.merge(options).map do |key, value|
165
+ SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
166
+ end.compact.join("&")
167
+ end
168
+
169
+ def truncate(text, truncate_after_chars = 2000)
170
+ truncate_after_chars && text.size > truncate_after_chars ? "#{text[0..truncate_after_chars]}..." : text
171
+ end
172
+
173
+ def display_args(args, truncate_after_chars = 2000)
174
+ args.map do |arg|
175
+ h(truncate(to_display(arg), truncate_after_chars))
176
+ end.join(", ")
177
+ end
178
+
179
+ def csrf_tag
180
+ "<input type='hidden' name='authenticity_token' value='#{session[:csrf]}'/>"
181
+ end
182
+
183
+ def to_display(arg)
184
+ begin
185
+ arg.inspect
186
+ rescue
187
+ begin
188
+ arg.to_s
189
+ rescue => ex
190
+ "Cannot display argument: [#{ex.class.name}] #{ex.message}"
191
+ end
192
+ end
193
+ end
194
+
195
+ RETRY_JOB_KEYS = Set.new(%w(
196
+ queue class args retry_count retried_at failed_at
197
+ jid error_message error_class backtrace
198
+ error_backtrace enqueued_at retry wrapped
199
+ created_at
200
+ ))
201
+
202
+ def retry_extra_items(retry_job)
203
+ @retry_extra_items ||= {}.tap do |extra|
204
+ retry_job.item.each do |key, value|
205
+ extra[key] = value unless RETRY_JOB_KEYS.include?(key)
206
+ end
207
+ end
208
+ end
209
+
210
+ def number_with_delimiter(number)
211
+ begin
212
+ Float(number)
213
+ rescue ArgumentError, TypeError
214
+ return number
215
+ end
216
+
217
+ options = {delimiter: ',', separator: '.'}
218
+ parts = number.to_s.to_str.split('.')
219
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
220
+ parts.join(options[:separator])
221
+ end
222
+
223
+ def h(text)
224
+ ::Rack::Utils.escape_html(text)
225
+ rescue ArgumentError => e
226
+ raise unless e.message.eql?('invalid byte sequence in UTF-8')
227
+ text.encode!('UTF-16', 'UTF-8', invalid: :replace, replace: '').encode!('UTF-8', 'UTF-16')
228
+ retry
229
+ end
230
+
231
+ # Any paginated list that performs an action needs to redirect
232
+ # back to the proper page after performing that action.
233
+ def redirect_with_query(url)
234
+ r = request.referer
235
+ if r && r =~ /\?/
236
+ ref = URI(r)
237
+ redirect("#{url}?#{ref.query}")
238
+ else
239
+ redirect url
240
+ end
241
+ end
242
+
243
+ def environment_title_prefix
244
+ environment = Sidekiq.options[:environment] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
245
+
246
+ "[#{environment.upcase}] " unless environment == "production"
247
+ end
248
+
249
+ def product_version
250
+ "Sidekiq v#{Sidekiq::VERSION}"
251
+ end
252
+
253
+ def redis_connection_and_namespace
254
+ @redis_connection_and_namespace ||= begin
255
+ namespace_suffix = namespace == nil ? '' : "##{namespace}"
256
+ "#{redis_connection}#{namespace_suffix}"
257
+ end
258
+ end
259
+
260
+ def retry_or_delete_or_kill(job, params)
261
+ if params['retry']
262
+ job.retry
263
+ elsif params['delete']
264
+ job.delete
265
+ elsif params['kill']
266
+ job.kill
267
+ end
268
+ end
269
+
270
+ def delete_or_add_queue(job, params)
271
+ if params['delete']
272
+ job.delete
273
+ elsif params['add_to_queue']
274
+ job.add_to_queue
275
+ end
276
+ end
277
+ end
278
+ end