sidekiq 3.5.4 → 5.2.7

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 (175) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +61 -0
  3. data/{Contributing.md → .github/contributing.md} +0 -0
  4. data/.github/issue_template.md +11 -0
  5. data/.gitignore +3 -0
  6. data/.travis.yml +5 -10
  7. data/4.0-Upgrade.md +53 -0
  8. data/5.0-Upgrade.md +56 -0
  9. data/COMM-LICENSE +13 -11
  10. data/Changes.md +376 -1
  11. data/Ent-Changes.md +201 -2
  12. data/Gemfile +14 -18
  13. data/LICENSE +1 -1
  14. data/Pro-3.0-Upgrade.md +44 -0
  15. data/Pro-4.0-Upgrade.md +35 -0
  16. data/Pro-Changes.md +307 -2
  17. data/README.md +34 -22
  18. data/Rakefile +3 -3
  19. data/bin/sidekiq +0 -1
  20. data/bin/sidekiqctl +13 -86
  21. data/bin/sidekiqload +23 -27
  22. data/code_of_conduct.md +50 -0
  23. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +3 -3
  24. data/lib/generators/sidekiq/templates/worker_test.rb.erb +6 -6
  25. data/lib/sidekiq.rb +72 -25
  26. data/lib/sidekiq/api.rb +206 -73
  27. data/lib/sidekiq/cli.rb +145 -101
  28. data/lib/sidekiq/client.rb +42 -36
  29. data/lib/sidekiq/core_ext.rb +1 -105
  30. data/lib/sidekiq/ctl.rb +221 -0
  31. data/lib/sidekiq/delay.rb +42 -0
  32. data/lib/sidekiq/exception_handler.rb +4 -5
  33. data/lib/sidekiq/extensions/action_mailer.rb +1 -0
  34. data/lib/sidekiq/extensions/active_record.rb +1 -0
  35. data/lib/sidekiq/extensions/class_methods.rb +1 -0
  36. data/lib/sidekiq/extensions/generic_proxy.rb +8 -1
  37. data/lib/sidekiq/fetch.rb +36 -111
  38. data/lib/sidekiq/job_logger.rb +25 -0
  39. data/lib/sidekiq/job_retry.rb +262 -0
  40. data/lib/sidekiq/launcher.rb +129 -55
  41. data/lib/sidekiq/logging.rb +21 -3
  42. data/lib/sidekiq/manager.rb +83 -182
  43. data/lib/sidekiq/middleware/chain.rb +1 -0
  44. data/lib/sidekiq/middleware/i18n.rb +1 -0
  45. data/lib/sidekiq/middleware/server/active_record.rb +10 -0
  46. data/lib/sidekiq/paginator.rb +1 -0
  47. data/lib/sidekiq/processor.rb +221 -103
  48. data/lib/sidekiq/rails.rb +47 -27
  49. data/lib/sidekiq/redis_connection.rb +74 -7
  50. data/lib/sidekiq/scheduled.rb +87 -28
  51. data/lib/sidekiq/testing.rb +150 -19
  52. data/lib/sidekiq/testing/inline.rb +1 -0
  53. data/lib/sidekiq/util.rb +15 -17
  54. data/lib/sidekiq/version.rb +2 -1
  55. data/lib/sidekiq/web.rb +120 -184
  56. data/lib/sidekiq/web/action.rb +89 -0
  57. data/lib/sidekiq/web/application.rb +353 -0
  58. data/lib/sidekiq/{web_helpers.rb → web/helpers.rb} +123 -47
  59. data/lib/sidekiq/web/router.rb +100 -0
  60. data/lib/sidekiq/worker.rb +135 -18
  61. data/sidekiq.gemspec +8 -14
  62. data/web/assets/images/{status-sd8051fd480.png → status.png} +0 -0
  63. data/web/assets/javascripts/application.js +24 -20
  64. data/web/assets/javascripts/dashboard.js +33 -18
  65. data/web/assets/stylesheets/application-rtl.css +246 -0
  66. data/web/assets/stylesheets/application.css +401 -7
  67. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  68. data/web/assets/stylesheets/bootstrap.css +4 -8
  69. data/web/locales/ar.yml +81 -0
  70. data/web/locales/cs.yml +11 -1
  71. data/web/locales/de.yml +1 -1
  72. data/web/locales/en.yml +4 -0
  73. data/web/locales/es.yml +4 -3
  74. data/web/locales/fa.yml +80 -0
  75. data/web/locales/fr.yml +21 -12
  76. data/web/locales/he.yml +79 -0
  77. data/web/locales/ja.yml +24 -13
  78. data/web/locales/ru.yml +3 -0
  79. data/web/locales/ur.yml +80 -0
  80. data/web/views/_footer.erb +7 -9
  81. data/web/views/_job_info.erb +5 -1
  82. data/web/views/_nav.erb +5 -19
  83. data/web/views/_paging.erb +1 -1
  84. data/web/views/busy.erb +18 -9
  85. data/web/views/dashboard.erb +5 -5
  86. data/web/views/dead.erb +1 -1
  87. data/web/views/layout.erb +13 -5
  88. data/web/views/morgue.erb +16 -12
  89. data/web/views/queue.erb +12 -11
  90. data/web/views/queues.erb +5 -3
  91. data/web/views/retries.erb +19 -13
  92. data/web/views/retry.erb +2 -2
  93. data/web/views/scheduled.erb +4 -4
  94. data/web/views/scheduled_job_info.erb +1 -1
  95. metadata +45 -227
  96. data/lib/sidekiq/actor.rb +0 -39
  97. data/lib/sidekiq/middleware/server/logging.rb +0 -40
  98. data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -206
  99. data/test/config.yml +0 -9
  100. data/test/env_based_config.yml +0 -11
  101. data/test/fake_env.rb +0 -0
  102. data/test/fixtures/en.yml +0 -2
  103. data/test/helper.rb +0 -49
  104. data/test/test_api.rb +0 -493
  105. data/test/test_cli.rb +0 -335
  106. data/test/test_client.rb +0 -194
  107. data/test/test_exception_handler.rb +0 -55
  108. data/test/test_extensions.rb +0 -126
  109. data/test/test_fetch.rb +0 -104
  110. data/test/test_logging.rb +0 -34
  111. data/test/test_manager.rb +0 -168
  112. data/test/test_middleware.rb +0 -159
  113. data/test/test_processor.rb +0 -237
  114. data/test/test_rails.rb +0 -21
  115. data/test/test_redis_connection.rb +0 -126
  116. data/test/test_retry.rb +0 -325
  117. data/test/test_scheduled.rb +0 -114
  118. data/test/test_scheduling.rb +0 -49
  119. data/test/test_sidekiq.rb +0 -99
  120. data/test/test_testing.rb +0 -142
  121. data/test/test_testing_fake.rb +0 -268
  122. data/test/test_testing_inline.rb +0 -93
  123. data/test/test_util.rb +0 -16
  124. data/test/test_web.rb +0 -608
  125. data/test/test_web_helpers.rb +0 -53
  126. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  127. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  128. data/web/assets/images/status/active.png +0 -0
  129. data/web/assets/images/status/idle.png +0 -0
  130. data/web/assets/javascripts/locales/README.md +0 -27
  131. data/web/assets/javascripts/locales/jquery.timeago.ar.js +0 -96
  132. data/web/assets/javascripts/locales/jquery.timeago.bg.js +0 -18
  133. data/web/assets/javascripts/locales/jquery.timeago.bs.js +0 -49
  134. data/web/assets/javascripts/locales/jquery.timeago.ca.js +0 -18
  135. data/web/assets/javascripts/locales/jquery.timeago.cs.js +0 -18
  136. data/web/assets/javascripts/locales/jquery.timeago.cy.js +0 -20
  137. data/web/assets/javascripts/locales/jquery.timeago.da.js +0 -18
  138. data/web/assets/javascripts/locales/jquery.timeago.de.js +0 -18
  139. data/web/assets/javascripts/locales/jquery.timeago.el.js +0 -18
  140. data/web/assets/javascripts/locales/jquery.timeago.en-short.js +0 -20
  141. data/web/assets/javascripts/locales/jquery.timeago.en.js +0 -20
  142. data/web/assets/javascripts/locales/jquery.timeago.es.js +0 -18
  143. data/web/assets/javascripts/locales/jquery.timeago.et.js +0 -18
  144. data/web/assets/javascripts/locales/jquery.timeago.fa.js +0 -22
  145. data/web/assets/javascripts/locales/jquery.timeago.fi.js +0 -28
  146. data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +0 -16
  147. data/web/assets/javascripts/locales/jquery.timeago.fr.js +0 -17
  148. data/web/assets/javascripts/locales/jquery.timeago.he.js +0 -18
  149. data/web/assets/javascripts/locales/jquery.timeago.hr.js +0 -49
  150. data/web/assets/javascripts/locales/jquery.timeago.hu.js +0 -18
  151. data/web/assets/javascripts/locales/jquery.timeago.hy.js +0 -18
  152. data/web/assets/javascripts/locales/jquery.timeago.id.js +0 -18
  153. data/web/assets/javascripts/locales/jquery.timeago.it.js +0 -16
  154. data/web/assets/javascripts/locales/jquery.timeago.ja.js +0 -19
  155. data/web/assets/javascripts/locales/jquery.timeago.ko.js +0 -17
  156. data/web/assets/javascripts/locales/jquery.timeago.lt.js +0 -20
  157. data/web/assets/javascripts/locales/jquery.timeago.mk.js +0 -20
  158. data/web/assets/javascripts/locales/jquery.timeago.nl.js +0 -20
  159. data/web/assets/javascripts/locales/jquery.timeago.no.js +0 -18
  160. data/web/assets/javascripts/locales/jquery.timeago.pl.js +0 -31
  161. data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +0 -16
  162. data/web/assets/javascripts/locales/jquery.timeago.pt.js +0 -16
  163. data/web/assets/javascripts/locales/jquery.timeago.ro.js +0 -18
  164. data/web/assets/javascripts/locales/jquery.timeago.rs.js +0 -49
  165. data/web/assets/javascripts/locales/jquery.timeago.ru.js +0 -34
  166. data/web/assets/javascripts/locales/jquery.timeago.sk.js +0 -18
  167. data/web/assets/javascripts/locales/jquery.timeago.sl.js +0 -44
  168. data/web/assets/javascripts/locales/jquery.timeago.sv.js +0 -18
  169. data/web/assets/javascripts/locales/jquery.timeago.th.js +0 -20
  170. data/web/assets/javascripts/locales/jquery.timeago.tr.js +0 -16
  171. data/web/assets/javascripts/locales/jquery.timeago.uk.js +0 -34
  172. data/web/assets/javascripts/locales/jquery.timeago.uz.js +0 -19
  173. data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +0 -20
  174. data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +0 -20
  175. data/web/views/_poll_js.erb +0 -5
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ class WebAction
5
+ RACK_SESSION = 'rack.session'
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 erb(content, options = {})
43
+ if content.kind_of? Symbol
44
+ unless respond_to?(:"_erb_#{content}")
45
+ src = ERB.new(File.read("#{Web.settings.views}/#{content}.erb")).src
46
+ WebAction.class_eval("def _erb_#{content}\n#{src}\n end")
47
+ end
48
+ end
49
+
50
+ if @_erb
51
+ _erb(content, options[:locals])
52
+ else
53
+ @_erb = true
54
+ content = _erb(content, options[:locals])
55
+
56
+ _render { content }
57
+ end
58
+ end
59
+
60
+ def render(engine, content, options = {})
61
+ raise "Only erb templates are supported" if engine != :erb
62
+
63
+ erb(content, options)
64
+ end
65
+
66
+ def json(payload)
67
+ [200, { "Content-Type" => "application/json", "Cache-Control" => "no-cache" }, [Sidekiq.dump_json(payload)]]
68
+ end
69
+
70
+ def initialize(env, block)
71
+ @_erb = false
72
+ @env = env
73
+ @block = block
74
+ @@files ||= {}
75
+ end
76
+
77
+ private
78
+
79
+ def _erb(file, locals)
80
+ locals.each {|k, v| define_singleton_method(k){ v } unless (singleton_methods.include? k)} if locals
81
+
82
+ if file.kind_of?(String)
83
+ ERB.new(file).result(binding)
84
+ else
85
+ send(:"_erb_#{file}")
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,353 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ class WebApplication
5
+ extend WebRouter
6
+
7
+ CONTENT_LENGTH = "Content-Length"
8
+ CONTENT_TYPE = "Content-Type"
9
+ REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human)
10
+ CSP_HEADER = [
11
+ "default-src 'self' https: http:",
12
+ "child-src 'self'",
13
+ "connect-src 'self' https: http: wss: ws:",
14
+ "font-src 'self' https: http:",
15
+ "frame-src 'self'",
16
+ "img-src 'self' https: http: data:",
17
+ "manifest-src 'self'",
18
+ "media-src 'self'",
19
+ "object-src 'none'",
20
+ "script-src 'self' https: http: 'unsafe-inline'",
21
+ "style-src 'self' https: http: 'unsafe-inline'",
22
+ "worker-src 'self'",
23
+ "base-uri 'self'"
24
+ ].join('; ').freeze
25
+
26
+ def initialize(klass)
27
+ @klass = klass
28
+ end
29
+
30
+ def settings
31
+ @klass.settings
32
+ end
33
+
34
+ def self.settings
35
+ Sidekiq::Web.settings
36
+ end
37
+
38
+ def self.tabs
39
+ Sidekiq::Web.tabs
40
+ end
41
+
42
+ def self.set(key, val)
43
+ # nothing, backwards compatibility
44
+ end
45
+
46
+ get "/" do
47
+ @redis_info = redis_info.select{ |k, v| REDIS_KEYS.include? k }
48
+ stats_history = Sidekiq::Stats::History.new((params['days'] || 30).to_i)
49
+ @processed_history = stats_history.processed
50
+ @failed_history = stats_history.failed
51
+
52
+ erb(:dashboard)
53
+ end
54
+
55
+ get "/busy" do
56
+ erb(:busy)
57
+ end
58
+
59
+ post "/busy" do
60
+ if params['identity']
61
+ p = Sidekiq::Process.new('identity' => params['identity'])
62
+ p.quiet! if params['quiet']
63
+ p.stop! if params['stop']
64
+ else
65
+ processes.each do |pro|
66
+ pro.quiet! if params['quiet']
67
+ pro.stop! if params['stop']
68
+ end
69
+ end
70
+
71
+ redirect "#{root_path}busy"
72
+ end
73
+
74
+ get "/queues" do
75
+ @queues = Sidekiq::Queue.all
76
+
77
+ erb(:queues)
78
+ end
79
+
80
+ get "/queues/:name" do
81
+ @name = route_params[:name]
82
+
83
+ halt(404) unless @name
84
+
85
+ @count = (params['count'] || 25).to_i
86
+ @queue = Sidekiq::Queue.new(@name)
87
+ (@current_page, @total_size, @messages) = page("queue:#{@name}", params['page'], @count)
88
+ @messages = @messages.map { |msg| Sidekiq::Job.new(msg, @name) }
89
+
90
+ erb(:queue)
91
+ end
92
+
93
+ post "/queues/:name" do
94
+ Sidekiq::Queue.new(route_params[:name]).clear
95
+
96
+ redirect "#{root_path}queues"
97
+ end
98
+
99
+ post "/queues/:name/delete" do
100
+ name = route_params[:name]
101
+ Sidekiq::Job.new(params['key_val'], name).delete
102
+
103
+ redirect_with_query("#{root_path}queues/#{CGI.escape(name)}")
104
+ end
105
+
106
+ get '/morgue' do
107
+ @count = (params['count'] || 25).to_i
108
+ (@current_page, @total_size, @dead) = page("dead", params['page'], @count, reverse: true)
109
+ @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
110
+
111
+ erb(:morgue)
112
+ end
113
+
114
+ get "/morgue/:key" do
115
+ halt(404) unless key = route_params[:key]
116
+
117
+ @dead = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
118
+
119
+ if @dead.nil?
120
+ redirect "#{root_path}morgue"
121
+ else
122
+ erb(:dead)
123
+ end
124
+ end
125
+
126
+ post '/morgue' do
127
+ redirect(request.path) unless params['key']
128
+
129
+ params['key'].each do |key|
130
+ job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
131
+ retry_or_delete_or_kill job, params if job
132
+ end
133
+
134
+ redirect_with_query("#{root_path}morgue")
135
+ end
136
+
137
+ post "/morgue/all/delete" do
138
+ Sidekiq::DeadSet.new.clear
139
+
140
+ redirect "#{root_path}morgue"
141
+ end
142
+
143
+ post "/morgue/all/retry" do
144
+ Sidekiq::DeadSet.new.retry_all
145
+
146
+ redirect "#{root_path}morgue"
147
+ end
148
+
149
+ post "/morgue/:key" do
150
+ halt(404) unless key = route_params[:key]
151
+
152
+ job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first
153
+ retry_or_delete_or_kill job, params if job
154
+
155
+ redirect_with_query("#{root_path}morgue")
156
+ end
157
+
158
+ get '/retries' do
159
+ @count = (params['count'] || 25).to_i
160
+ (@current_page, @total_size, @retries) = page("retry", params['page'], @count)
161
+ @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
162
+
163
+ erb(:retries)
164
+ end
165
+
166
+ get "/retries/:key" do
167
+ @retry = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
168
+
169
+ if @retry.nil?
170
+ redirect "#{root_path}retries"
171
+ else
172
+ erb(:retry)
173
+ end
174
+ end
175
+
176
+ post '/retries' do
177
+ redirect(request.path) unless params['key']
178
+
179
+ params['key'].each do |key|
180
+ job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first
181
+ retry_or_delete_or_kill job, params if job
182
+ end
183
+
184
+ redirect_with_query("#{root_path}retries")
185
+ end
186
+
187
+ post "/retries/all/delete" do
188
+ Sidekiq::RetrySet.new.clear
189
+
190
+ redirect "#{root_path}retries"
191
+ end
192
+
193
+ post "/retries/all/retry" do
194
+ Sidekiq::RetrySet.new.retry_all
195
+
196
+ redirect "#{root_path}retries"
197
+ end
198
+
199
+ post "/retries/all/kill" do
200
+ Sidekiq::RetrySet.new.kill_all
201
+
202
+ redirect "#{root_path}retries"
203
+ end
204
+
205
+ post "/retries/:key" do
206
+ job = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first
207
+
208
+ retry_or_delete_or_kill job, params if job
209
+
210
+ redirect_with_query("#{root_path}retries")
211
+ end
212
+
213
+ get '/scheduled' do
214
+ @count = (params['count'] || 25).to_i
215
+ (@current_page, @total_size, @scheduled) = page("schedule", params['page'], @count)
216
+ @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
217
+
218
+ erb(:scheduled)
219
+ end
220
+
221
+ get "/scheduled/:key" do
222
+ @job = Sidekiq::ScheduledSet.new.fetch(*parse_params(route_params[:key])).first
223
+
224
+ if @job.nil?
225
+ redirect "#{root_path}scheduled"
226
+ else
227
+ erb(:scheduled_job_info)
228
+ end
229
+ end
230
+
231
+ post '/scheduled' do
232
+ redirect(request.path) unless params['key']
233
+
234
+ params['key'].each do |key|
235
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
236
+ delete_or_add_queue job, params if job
237
+ end
238
+
239
+ redirect_with_query("#{root_path}scheduled")
240
+ end
241
+
242
+ post "/scheduled/:key" do
243
+ halt(404) unless key = route_params[:key]
244
+
245
+ job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first
246
+ delete_or_add_queue job, params if job
247
+
248
+ redirect_with_query("#{root_path}scheduled")
249
+ end
250
+
251
+ get '/dashboard/stats' do
252
+ redirect "#{root_path}stats"
253
+ end
254
+
255
+ get '/stats' do
256
+ sidekiq_stats = Sidekiq::Stats.new
257
+ redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k }
258
+ json(
259
+ sidekiq: {
260
+ processed: sidekiq_stats.processed,
261
+ failed: sidekiq_stats.failed,
262
+ busy: sidekiq_stats.workers_size,
263
+ processes: sidekiq_stats.processes_size,
264
+ enqueued: sidekiq_stats.enqueued,
265
+ scheduled: sidekiq_stats.scheduled_size,
266
+ retries: sidekiq_stats.retry_size,
267
+ dead: sidekiq_stats.dead_size,
268
+ default_latency: sidekiq_stats.default_queue_latency
269
+ },
270
+ redis: redis_stats,
271
+ server_utc_time: server_utc_time
272
+ )
273
+ end
274
+
275
+ get '/stats/queues' do
276
+ json Sidekiq::Stats::Queues.new.lengths
277
+ end
278
+
279
+ def call(env)
280
+ action = self.class.match(env)
281
+ return [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass" }, ["Not Found"]] unless action
282
+
283
+ resp = catch(:halt) do
284
+ app = @klass
285
+ self.class.run_befores(app, action)
286
+ begin
287
+ resp = action.instance_exec env, &action.block
288
+ ensure
289
+ self.class.run_afters(app, action)
290
+ end
291
+
292
+ resp
293
+ end
294
+
295
+ resp = case resp
296
+ when Array
297
+ resp
298
+ else
299
+ headers = {
300
+ "Content-Type" => "text/html",
301
+ "Cache-Control" => "no-cache",
302
+ "Content-Language" => action.locale,
303
+ "Content-Security-Policy" => CSP_HEADER
304
+ }
305
+
306
+ [200, headers, [resp]]
307
+ end
308
+
309
+ resp[1] = resp[1].dup
310
+
311
+ resp[1][CONTENT_LENGTH] = resp[2].inject(0) { |l, p| l + p.bytesize }.to_s
312
+
313
+ resp
314
+ end
315
+
316
+ def self.helpers(mod=nil, &block)
317
+ if block_given?
318
+ WebAction.class_eval(&block)
319
+ else
320
+ WebAction.send(:include, mod)
321
+ end
322
+ end
323
+
324
+ def self.before(path=nil, &block)
325
+ befores << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
326
+ end
327
+
328
+ def self.after(path=nil, &block)
329
+ afters << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block]
330
+ end
331
+
332
+ def self.run_befores(app, action)
333
+ run_hooks(befores, app, action)
334
+ end
335
+
336
+ def self.run_afters(app, action)
337
+ run_hooks(afters, app, action)
338
+ end
339
+
340
+ def self.run_hooks(hooks, app, action)
341
+ hooks.select { |p,_| !p || p =~ action.env[WebRouter::PATH_INFO] }.
342
+ each {|_,b| action.instance_exec(action.env, app, &b) }
343
+ end
344
+
345
+ def self.befores
346
+ @befores ||= []
347
+ end
348
+
349
+ def self.afters
350
+ @afters ||= []
351
+ end
352
+ end
353
+ end
@@ -1,4 +1,8 @@
1
+ # frozen_string_literal: true
1
2
  require 'uri'
3
+ require 'set'
4
+ require 'yaml'
5
+ require 'cgi'
2
6
 
3
7
  module Sidekiq
4
8
  # This is not a public API
@@ -11,18 +15,28 @@ module Sidekiq
11
15
  settings.locales.each_with_object({}) do |path, global|
12
16
  find_locale_files(lang).each do |file|
13
17
  strs = YAML.load(File.open(file))
14
- global.deep_merge!(strs[lang])
18
+ global.merge!(strs[lang])
15
19
  end
16
20
  end
17
21
  end
18
22
  end
19
23
 
24
+ def clear_caches
25
+ @@strings = nil
26
+ @@locale_files = nil
27
+ @@available_locales = nil
28
+ end
29
+
20
30
  def locale_files
21
- @@locale_files = settings.locales.flat_map do |path|
31
+ @@locale_files ||= settings.locales.flat_map do |path|
22
32
  Dir["#{path}/*.yml"]
23
33
  end
24
34
  end
25
35
 
36
+ def available_locales
37
+ @@available_locales ||= locale_files.map { |path| File.basename(path, '.yml') }.uniq
38
+ end
39
+
26
40
  def find_locale_files(lang)
27
41
  locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
28
42
  end
@@ -39,46 +53,75 @@ module Sidekiq
39
53
  # <meta .../>
40
54
  # <% end %>
41
55
  #
42
- def add_to_head(&block)
56
+ def add_to_head
43
57
  @head_html ||= []
44
- @head_html << block if block_given?
58
+ @head_html << yield.dup if block_given?
45
59
  end
46
60
 
47
61
  def display_custom_head
48
- return unless defined?(@head_html)
49
- @head_html.map { |block| capture(&block) }.join
62
+ @head_html.join if defined?(@head_html)
63
+ end
64
+
65
+ def poll_path
66
+ if current_path != '' && params['poll']
67
+ root_path + current_path
68
+ else
69
+ ""
70
+ end
71
+ end
72
+
73
+ def text_direction
74
+ get_locale['TextDirection'] || 'ltr'
75
+ end
76
+
77
+ def rtl?
78
+ text_direction == 'rtl'
50
79
  end
51
80
 
52
- # Simple capture method for erb templates. The origin was
53
- # capture method from sinatra-contrib library.
54
- def capture(&block)
55
- block.call
56
- eval('', block.binding)
81
+ # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
82
+ def user_preferred_languages
83
+ languages = env['HTTP_ACCEPT_LANGUAGE']
84
+ languages.to_s.downcase.gsub(/\s+/, '').split(',').map do |language|
85
+ locale, quality = language.split(';q=', 2)
86
+ locale = nil if locale == '*' # Ignore wildcards
87
+ quality = quality ? quality.to_f : 1.0
88
+ [locale, quality]
89
+ end.sort do |(_, left), (_, right)|
90
+ right <=> left
91
+ end.map(&:first).compact
57
92
  end
58
93
 
59
- # Given a browser request Accept-Language header like
60
- # "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2", this function
61
- # will return "fr" since that's the first code with a matching
62
- # locale in web/locales
94
+ # Given an Accept-Language header like "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2"
95
+ # this method will try to best match the available locales to the user's preferred languages.
96
+ #
97
+ # Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb
63
98
  def locale
64
99
  @locale ||= begin
65
- locale = 'en'.freeze
66
- languages = request.env['HTTP_ACCEPT_LANGUAGE'.freeze] || 'en'.freeze
67
- languages.downcase.split(','.freeze).each do |lang|
68
- next if lang == '*'.freeze
69
- lang = lang.split(';'.freeze)[0]
70
- break locale = lang if find_locale_files(lang).any?
71
- end
72
- locale
100
+ matched_locale = user_preferred_languages.map do |preferred|
101
+ preferred_language = preferred.split('-', 2).first
102
+
103
+ lang_group = available_locales.select do |available|
104
+ preferred_language == available.split('-', 2).first
105
+ end
106
+
107
+ lang_group.find { |lang| lang == preferred } || lang_group.min_by(&:length)
108
+ end.compact.first
109
+
110
+ matched_locale || 'en'
73
111
  end
74
112
  end
75
113
 
114
+ # mperham/sidekiq#3243
115
+ def unfiltered?
116
+ yield unless env['PATH_INFO'].start_with?("/filter/")
117
+ end
118
+
76
119
  def get_locale
77
120
  strings(locale)
78
121
  end
79
122
 
80
123
  def t(msg, options={})
81
- string = get_locale[msg] || msg
124
+ string = get_locale[msg] || strings('en')[msg] || msg
82
125
  if options.empty?
83
126
  string
84
127
  else
@@ -104,28 +147,19 @@ module Sidekiq
104
147
  end.map { |msg| Sidekiq.load_json(msg) }
105
148
  end
106
149
 
107
- def location
108
- Sidekiq.redis { |conn| conn.client.location }
109
- end
110
-
111
150
  def redis_connection
112
- Sidekiq.redis { |conn| conn.client.id }
151
+ Sidekiq.redis do |conn|
152
+ c = conn.connection
153
+ "redis://#{c[:location]}/#{c[:db]}"
154
+ end
113
155
  end
114
156
 
115
157
  def namespace
116
- @@ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
158
+ @ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
117
159
  end
118
160
 
119
161
  def redis_info
120
- Sidekiq.redis do |conn|
121
- # admin commands can't go through redis-namespace starting
122
- # in redis-namespace 2.0
123
- if conn.respond_to?(:namespace)
124
- conn.redis.info
125
- else
126
- conn.info
127
- end
128
- end
162
+ Sidekiq.redis_info
129
163
  end
130
164
 
131
165
  def root_path
@@ -141,7 +175,8 @@ module Sidekiq
141
175
  end
142
176
 
143
177
  def relative_time(time)
144
- %{<time datetime="#{time.getutc.iso8601}">#{time}</time>}
178
+ stamp = time.getutc.iso8601
179
+ %{<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>}
145
180
  end
146
181
 
147
182
  def job_params(job, score)
@@ -149,7 +184,7 @@ module Sidekiq
149
184
  end
150
185
 
151
186
  def parse_params(params)
152
- score, jid = params.split("-")
187
+ score, jid = params.split("-", 2)
153
188
  [score.to_f, jid]
154
189
  end
155
190
 
@@ -157,10 +192,14 @@ module Sidekiq
157
192
 
158
193
  # Merge options with current params, filter safe params, and stringify to query string
159
194
  def qparams(options)
160
- options = options.stringify_keys
195
+ # stringify
196
+ options.keys.each do |key|
197
+ options[key.to_s] = options.delete(key)
198
+ end
199
+
161
200
  params.merge(options).map do |key, value|
162
- SAFE_QPARAMS.include?(key) ? "#{key}=#{value}" : next
163
- end.join("&")
201
+ SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
202
+ end.compact.join("&")
164
203
  end
165
204
 
166
205
  def truncate(text, truncate_after_chars = 2000)
@@ -168,9 +207,16 @@ module Sidekiq
168
207
  end
169
208
 
170
209
  def display_args(args, truncate_after_chars = 2000)
171
- args.map do |arg|
172
- h(truncate(to_display(arg)))
173
- end.join(", ")
210
+ return "Invalid job payload, args is nil" if args == nil
211
+ return "Invalid job payload, args must be an Array, not #{args.class.name}" if !args.is_a?(Array)
212
+
213
+ begin
214
+ args.map do |arg|
215
+ h(truncate(to_display(arg), truncate_after_chars))
216
+ end.join(", ")
217
+ rescue
218
+ "Illegal job arguments: #{h args.inspect}"
219
+ end
174
220
  end
175
221
 
176
222
  def csrf_tag
@@ -193,6 +239,7 @@ module Sidekiq
193
239
  queue class args retry_count retried_at failed_at
194
240
  jid error_message error_class backtrace
195
241
  error_backtrace enqueued_at retry wrapped
242
+ created_at
196
243
  ))
197
244
 
198
245
  def retry_extra_items(retry_job)
@@ -245,5 +292,34 @@ module Sidekiq
245
292
  def product_version
246
293
  "Sidekiq v#{Sidekiq::VERSION}"
247
294
  end
295
+
296
+ def server_utc_time
297
+ Time.now.utc.strftime('%H:%M:%S UTC')
298
+ end
299
+
300
+ def redis_connection_and_namespace
301
+ @redis_connection_and_namespace ||= begin
302
+ namespace_suffix = namespace == nil ? '' : "##{namespace}"
303
+ "#{redis_connection}#{namespace_suffix}"
304
+ end
305
+ end
306
+
307
+ def retry_or_delete_or_kill(job, params)
308
+ if params['retry']
309
+ job.retry
310
+ elsif params['delete']
311
+ job.delete
312
+ elsif params['kill']
313
+ job.kill
314
+ end
315
+ end
316
+
317
+ def delete_or_add_queue(job, params)
318
+ if params['delete']
319
+ job.delete
320
+ elsif params['add_to_queue']
321
+ job.add_to_queue
322
+ end
323
+ end
248
324
  end
249
325
  end