sidekiq 4.1.4 → 6.5.6

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 (213) hide show
  1. checksums.yaml +5 -5
  2. data/Changes.md +666 -0
  3. data/LICENSE +3 -3
  4. data/README.md +27 -35
  5. data/bin/sidekiq +27 -3
  6. data/bin/sidekiqload +78 -84
  7. data/bin/sidekiqmon +8 -0
  8. data/lib/generators/sidekiq/job_generator.rb +57 -0
  9. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  10. data/lib/generators/sidekiq/templates/job_spec.rb.erb +6 -0
  11. data/lib/generators/sidekiq/templates/job_test.rb.erb +8 -0
  12. data/lib/sidekiq/api.rb +583 -288
  13. data/lib/sidekiq/cli.rb +255 -218
  14. data/lib/sidekiq/client.rb +86 -83
  15. data/lib/sidekiq/component.rb +65 -0
  16. data/lib/sidekiq/delay.rb +43 -0
  17. data/lib/sidekiq/extensions/action_mailer.rb +13 -22
  18. data/lib/sidekiq/extensions/active_record.rb +13 -10
  19. data/lib/sidekiq/extensions/class_methods.rb +14 -11
  20. data/lib/sidekiq/extensions/generic_proxy.rb +13 -5
  21. data/lib/sidekiq/fetch.rb +50 -40
  22. data/lib/sidekiq/job.rb +13 -0
  23. data/lib/sidekiq/job_logger.rb +51 -0
  24. data/lib/sidekiq/job_retry.rb +282 -0
  25. data/lib/sidekiq/job_util.rb +71 -0
  26. data/lib/sidekiq/launcher.rb +192 -85
  27. data/lib/sidekiq/logger.rb +156 -0
  28. data/lib/sidekiq/manager.rb +44 -45
  29. data/lib/sidekiq/metrics/deploy.rb +47 -0
  30. data/lib/sidekiq/metrics/query.rb +153 -0
  31. data/lib/sidekiq/metrics/shared.rb +94 -0
  32. data/lib/sidekiq/metrics/tracking.rb +134 -0
  33. data/lib/sidekiq/middleware/chain.rb +102 -46
  34. data/lib/sidekiq/middleware/current_attributes.rb +63 -0
  35. data/lib/sidekiq/middleware/i18n.rb +7 -7
  36. data/lib/sidekiq/middleware/modules.rb +21 -0
  37. data/lib/sidekiq/monitor.rb +133 -0
  38. data/lib/sidekiq/paginator.rb +20 -16
  39. data/lib/sidekiq/processor.rb +178 -78
  40. data/lib/sidekiq/rails.rb +56 -27
  41. data/lib/sidekiq/redis_client_adapter.rb +154 -0
  42. data/lib/sidekiq/redis_connection.rb +123 -47
  43. data/lib/sidekiq/ring_buffer.rb +29 -0
  44. data/lib/sidekiq/scheduled.rb +97 -40
  45. data/lib/sidekiq/sd_notify.rb +149 -0
  46. data/lib/sidekiq/systemd.rb +24 -0
  47. data/lib/sidekiq/testing/inline.rb +6 -5
  48. data/lib/sidekiq/testing.rb +83 -56
  49. data/lib/sidekiq/transaction_aware_client.rb +45 -0
  50. data/lib/sidekiq/version.rb +2 -1
  51. data/lib/sidekiq/web/action.rb +93 -0
  52. data/lib/sidekiq/web/application.rb +379 -0
  53. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  54. data/lib/sidekiq/web/helpers.rb +365 -0
  55. data/lib/sidekiq/web/router.rb +104 -0
  56. data/lib/sidekiq/web.rb +108 -213
  57. data/lib/sidekiq/worker.rb +288 -42
  58. data/lib/sidekiq.rb +188 -80
  59. data/sidekiq.gemspec +24 -22
  60. data/web/assets/images/apple-touch-icon.png +0 -0
  61. data/web/assets/images/{status-sd8051fd480.png → status.png} +0 -0
  62. data/web/assets/javascripts/application.js +130 -75
  63. data/web/assets/javascripts/chart.min.js +13 -0
  64. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  65. data/web/assets/javascripts/dashboard.js +70 -91
  66. data/web/assets/javascripts/graph.js +16 -0
  67. data/web/assets/javascripts/metrics.js +262 -0
  68. data/web/assets/stylesheets/application-dark.css +143 -0
  69. data/web/assets/stylesheets/application-rtl.css +242 -0
  70. data/web/assets/stylesheets/application.css +390 -145
  71. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  72. data/web/assets/stylesheets/bootstrap.css +4 -8
  73. data/web/locales/ar.yml +87 -0
  74. data/web/locales/de.yml +15 -3
  75. data/web/locales/el.yml +43 -19
  76. data/web/locales/en.yml +15 -1
  77. data/web/locales/es.yml +22 -5
  78. data/web/locales/fa.yml +80 -0
  79. data/web/locales/fr.yml +10 -3
  80. data/web/locales/he.yml +79 -0
  81. data/web/locales/ja.yml +12 -4
  82. data/web/locales/lt.yml +83 -0
  83. data/web/locales/pl.yml +4 -4
  84. data/web/locales/pt-br.yml +27 -9
  85. data/web/locales/ru.yml +4 -0
  86. data/web/locales/ur.yml +80 -0
  87. data/web/locales/vi.yml +83 -0
  88. data/web/views/_footer.erb +6 -3
  89. data/web/views/_job_info.erb +4 -3
  90. data/web/views/_nav.erb +5 -19
  91. data/web/views/_paging.erb +1 -1
  92. data/web/views/_poll_link.erb +2 -5
  93. data/web/views/_summary.erb +7 -7
  94. data/web/views/busy.erb +64 -26
  95. data/web/views/dashboard.erb +26 -17
  96. data/web/views/dead.erb +4 -4
  97. data/web/views/layout.erb +15 -5
  98. data/web/views/metrics.erb +69 -0
  99. data/web/views/metrics_for_job.erb +87 -0
  100. data/web/views/morgue.erb +21 -14
  101. data/web/views/queue.erb +28 -14
  102. data/web/views/queues.erb +15 -5
  103. data/web/views/retries.erb +24 -15
  104. data/web/views/retry.erb +5 -5
  105. data/web/views/scheduled.erb +9 -6
  106. data/web/views/scheduled_job_info.erb +1 -1
  107. metadata +73 -256
  108. data/.github/contributing.md +0 -32
  109. data/.github/issue_template.md +0 -4
  110. data/.gitignore +0 -12
  111. data/.travis.yml +0 -18
  112. data/3.0-Upgrade.md +0 -70
  113. data/4.0-Upgrade.md +0 -53
  114. data/COMM-LICENSE +0 -95
  115. data/Ent-Changes.md +0 -123
  116. data/Gemfile +0 -29
  117. data/Pro-2.0-Upgrade.md +0 -138
  118. data/Pro-3.0-Upgrade.md +0 -44
  119. data/Pro-Changes.md +0 -559
  120. data/Rakefile +0 -9
  121. data/bin/sidekiqctl +0 -99
  122. data/code_of_conduct.md +0 -50
  123. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +0 -6
  124. data/lib/generators/sidekiq/templates/worker_test.rb.erb +0 -8
  125. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  126. data/lib/sidekiq/core_ext.rb +0 -106
  127. data/lib/sidekiq/exception_handler.rb +0 -31
  128. data/lib/sidekiq/logging.rb +0 -106
  129. data/lib/sidekiq/middleware/server/active_record.rb +0 -13
  130. data/lib/sidekiq/middleware/server/logging.rb +0 -40
  131. data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
  132. data/lib/sidekiq/util.rb +0 -62
  133. data/lib/sidekiq/web_helpers.rb +0 -255
  134. data/test/config.yml +0 -9
  135. data/test/env_based_config.yml +0 -11
  136. data/test/fake_env.rb +0 -1
  137. data/test/fixtures/en.yml +0 -2
  138. data/test/helper.rb +0 -75
  139. data/test/test_actors.rb +0 -138
  140. data/test/test_api.rb +0 -528
  141. data/test/test_cli.rb +0 -406
  142. data/test/test_client.rb +0 -266
  143. data/test/test_exception_handler.rb +0 -56
  144. data/test/test_extensions.rb +0 -127
  145. data/test/test_fetch.rb +0 -50
  146. data/test/test_launcher.rb +0 -85
  147. data/test/test_logging.rb +0 -35
  148. data/test/test_manager.rb +0 -50
  149. data/test/test_middleware.rb +0 -158
  150. data/test/test_processor.rb +0 -201
  151. data/test/test_rails.rb +0 -22
  152. data/test/test_redis_connection.rb +0 -132
  153. data/test/test_retry.rb +0 -326
  154. data/test/test_retry_exhausted.rb +0 -149
  155. data/test/test_scheduled.rb +0 -115
  156. data/test/test_scheduling.rb +0 -50
  157. data/test/test_sidekiq.rb +0 -107
  158. data/test/test_testing.rb +0 -143
  159. data/test/test_testing_fake.rb +0 -357
  160. data/test/test_testing_inline.rb +0 -94
  161. data/test/test_util.rb +0 -13
  162. data/test/test_web.rb +0 -614
  163. data/test/test_web_helpers.rb +0 -54
  164. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  165. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  166. data/web/assets/images/status/active.png +0 -0
  167. data/web/assets/images/status/idle.png +0 -0
  168. data/web/assets/javascripts/locales/README.md +0 -27
  169. data/web/assets/javascripts/locales/jquery.timeago.ar.js +0 -96
  170. data/web/assets/javascripts/locales/jquery.timeago.bg.js +0 -18
  171. data/web/assets/javascripts/locales/jquery.timeago.bs.js +0 -49
  172. data/web/assets/javascripts/locales/jquery.timeago.ca.js +0 -18
  173. data/web/assets/javascripts/locales/jquery.timeago.cs.js +0 -18
  174. data/web/assets/javascripts/locales/jquery.timeago.cy.js +0 -20
  175. data/web/assets/javascripts/locales/jquery.timeago.da.js +0 -18
  176. data/web/assets/javascripts/locales/jquery.timeago.de.js +0 -18
  177. data/web/assets/javascripts/locales/jquery.timeago.el.js +0 -18
  178. data/web/assets/javascripts/locales/jquery.timeago.en-short.js +0 -20
  179. data/web/assets/javascripts/locales/jquery.timeago.en.js +0 -20
  180. data/web/assets/javascripts/locales/jquery.timeago.es.js +0 -18
  181. data/web/assets/javascripts/locales/jquery.timeago.et.js +0 -18
  182. data/web/assets/javascripts/locales/jquery.timeago.fa.js +0 -22
  183. data/web/assets/javascripts/locales/jquery.timeago.fi.js +0 -28
  184. data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +0 -16
  185. data/web/assets/javascripts/locales/jquery.timeago.fr.js +0 -17
  186. data/web/assets/javascripts/locales/jquery.timeago.he.js +0 -18
  187. data/web/assets/javascripts/locales/jquery.timeago.hr.js +0 -49
  188. data/web/assets/javascripts/locales/jquery.timeago.hu.js +0 -18
  189. data/web/assets/javascripts/locales/jquery.timeago.hy.js +0 -18
  190. data/web/assets/javascripts/locales/jquery.timeago.id.js +0 -18
  191. data/web/assets/javascripts/locales/jquery.timeago.it.js +0 -16
  192. data/web/assets/javascripts/locales/jquery.timeago.ja.js +0 -19
  193. data/web/assets/javascripts/locales/jquery.timeago.ko.js +0 -17
  194. data/web/assets/javascripts/locales/jquery.timeago.lt.js +0 -20
  195. data/web/assets/javascripts/locales/jquery.timeago.mk.js +0 -20
  196. data/web/assets/javascripts/locales/jquery.timeago.nb.js +0 -18
  197. data/web/assets/javascripts/locales/jquery.timeago.nl.js +0 -20
  198. data/web/assets/javascripts/locales/jquery.timeago.pl.js +0 -31
  199. data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +0 -16
  200. data/web/assets/javascripts/locales/jquery.timeago.pt.js +0 -16
  201. data/web/assets/javascripts/locales/jquery.timeago.ro.js +0 -18
  202. data/web/assets/javascripts/locales/jquery.timeago.rs.js +0 -49
  203. data/web/assets/javascripts/locales/jquery.timeago.ru.js +0 -34
  204. data/web/assets/javascripts/locales/jquery.timeago.sk.js +0 -18
  205. data/web/assets/javascripts/locales/jquery.timeago.sl.js +0 -44
  206. data/web/assets/javascripts/locales/jquery.timeago.sv.js +0 -18
  207. data/web/assets/javascripts/locales/jquery.timeago.th.js +0 -20
  208. data/web/assets/javascripts/locales/jquery.timeago.tr.js +0 -16
  209. data/web/assets/javascripts/locales/jquery.timeago.uk.js +0 -34
  210. data/web/assets/javascripts/locales/jquery.timeago.uz.js +0 -19
  211. data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +0 -20
  212. data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +0 -20
  213. data/web/views/_poll_js.erb +0 -5
@@ -0,0 +1,365 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "set"
5
+ require "yaml"
6
+ require "cgi"
7
+
8
+ module Sidekiq
9
+ # This is not a public API
10
+ module WebHelpers
11
+ def strings(lang)
12
+ @strings ||= {}
13
+
14
+ # Allow sidekiq-web extensions to add locale paths
15
+ # so extensions can be localized
16
+ @strings[lang] ||= settings.locales.each_with_object({}) do |path, global|
17
+ find_locale_files(lang).each do |file|
18
+ strs = YAML.safe_load(File.open(file))
19
+ global.merge!(strs[lang])
20
+ end
21
+ end
22
+ end
23
+
24
+ def singularize(str, count)
25
+ if count == 1 && str.respond_to?(:singularize) # rails
26
+ str.singularize
27
+ else
28
+ str
29
+ end
30
+ end
31
+
32
+ def clear_caches
33
+ @strings = nil
34
+ @locale_files = nil
35
+ @available_locales = nil
36
+ end
37
+
38
+ def locale_files
39
+ @locale_files ||= settings.locales.flat_map { |path|
40
+ Dir["#{path}/*.yml"]
41
+ }
42
+ end
43
+
44
+ def available_locales
45
+ @available_locales ||= locale_files.map { |path| File.basename(path, ".yml") }.uniq
46
+ end
47
+
48
+ def find_locale_files(lang)
49
+ locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
50
+ end
51
+
52
+ # This is a hook for a Sidekiq Pro feature. Please don't touch.
53
+ def filtering(*)
54
+ end
55
+
56
+ # This view helper provide ability display you html code in
57
+ # to head of page. Example:
58
+ #
59
+ # <% add_to_head do %>
60
+ # <link rel="stylesheet" .../>
61
+ # <meta .../>
62
+ # <% end %>
63
+ #
64
+ def add_to_head
65
+ @head_html ||= []
66
+ @head_html << yield.dup if block_given?
67
+ end
68
+
69
+ def display_custom_head
70
+ @head_html.join if defined?(@head_html)
71
+ end
72
+
73
+ def text_direction
74
+ get_locale["TextDirection"] || "ltr"
75
+ end
76
+
77
+ def rtl?
78
+ text_direction == "rtl"
79
+ end
80
+
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 { |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
+ }.sort { |(_, left), (_, right)|
90
+ right <=> left
91
+ }.map(&:first).compact
92
+ end
93
+
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
98
+ def locale
99
+ @locale ||= begin
100
+ matched_locale = user_preferred_languages.map { |preferred|
101
+ preferred_language = preferred.split("-", 2).first
102
+
103
+ lang_group = available_locales.select { |available|
104
+ preferred_language == available.split("-", 2).first
105
+ }
106
+
107
+ lang_group.find { |lang| lang == preferred } || lang_group.min_by(&:length)
108
+ }.compact.first
109
+
110
+ matched_locale || "en"
111
+ end
112
+ end
113
+
114
+ # within is used by Sidekiq Pro
115
+ def display_tags(job, within = nil)
116
+ job.tags.map { |tag|
117
+ "<span class='label label-info jobtag'>#{::Rack::Utils.escape_html(tag)}</span>"
118
+ }.join(" ")
119
+ end
120
+
121
+ # mperham/sidekiq#3243
122
+ def unfiltered?
123
+ yield unless env["PATH_INFO"].start_with?("/filter/")
124
+ end
125
+
126
+ def get_locale
127
+ strings(locale)
128
+ end
129
+
130
+ def t(msg, options = {})
131
+ string = get_locale[msg] || strings("en")[msg] || msg
132
+ if options.empty?
133
+ string
134
+ else
135
+ string % options
136
+ end
137
+ end
138
+
139
+ def sort_direction_label
140
+ params[:direction] == "asc" ? "&uarr;" : "&darr;"
141
+ end
142
+
143
+ def workset
144
+ @work ||= Sidekiq::WorkSet.new
145
+ end
146
+
147
+ def processes
148
+ @processes ||= Sidekiq::ProcessSet.new
149
+ end
150
+
151
+ # Sorts processes by hostname following the natural sort order so that
152
+ # 'worker.1' < 'worker.2' < 'worker.10' < 'worker.20'
153
+ # '2.1.1.1' < '192.168.0.2' < '192.168.0.10'
154
+ def sorted_processes
155
+ @sorted_processes ||= begin
156
+ return processes unless processes.all? { |p| p["hostname"] }
157
+
158
+ split_characters = /[._-]/
159
+
160
+ padding = processes.flat_map { |p| p["hostname"].split(split_characters) }.map(&:size).max
161
+
162
+ processes.to_a.sort_by do |process|
163
+ process["hostname"].split(split_characters).map do |substring|
164
+ # Left-pad the substring with '0' if it starts with a number or 'a'
165
+ # otherwise, so that '25' < 192' < 'a' ('025' < '192' < 'aaa')
166
+ padding_char = substring[0].match?(/\d/) ? "0" : "a"
167
+
168
+ substring.rjust(padding, padding_char)
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ def stats
175
+ @stats ||= Sidekiq::Stats.new
176
+ end
177
+
178
+ def redis_connection
179
+ Sidekiq.redis do |conn|
180
+ conn.connection[:id]
181
+ end
182
+ end
183
+
184
+ def namespace
185
+ @ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
186
+ end
187
+
188
+ def redis_info
189
+ Sidekiq.redis_info
190
+ end
191
+
192
+ def root_path
193
+ "#{env["SCRIPT_NAME"]}/"
194
+ end
195
+
196
+ def current_path
197
+ @current_path ||= request.path_info.gsub(/^\//, "")
198
+ end
199
+
200
+ def current_status
201
+ workset.size == 0 ? "idle" : "active"
202
+ end
203
+
204
+ def relative_time(time)
205
+ stamp = time.getutc.iso8601
206
+ %(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
207
+ end
208
+
209
+ def job_params(job, score)
210
+ "#{score}-#{job["jid"]}"
211
+ end
212
+
213
+ def parse_params(params)
214
+ score, jid = params.split("-", 2)
215
+ [score.to_f, jid]
216
+ end
217
+
218
+ SAFE_QPARAMS = %w[page direction]
219
+
220
+ # Merge options with current params, filter safe params, and stringify to query string
221
+ def qparams(options)
222
+ stringified_options = options.transform_keys(&:to_s)
223
+
224
+ to_query_string(params.merge(stringified_options))
225
+ end
226
+
227
+ def to_query_string(params)
228
+ params.map { |key, value|
229
+ SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next
230
+ }.compact.join("&")
231
+ end
232
+
233
+ def truncate(text, truncate_after_chars = 2000)
234
+ truncate_after_chars && text.size > truncate_after_chars ? "#{text[0..truncate_after_chars]}..." : text
235
+ end
236
+
237
+ def display_args(args, truncate_after_chars = 2000)
238
+ return "Invalid job payload, args is nil" if args.nil?
239
+ return "Invalid job payload, args must be an Array, not #{args.class.name}" unless args.is_a?(Array)
240
+
241
+ begin
242
+ args.map { |arg|
243
+ h(truncate(to_display(arg), truncate_after_chars))
244
+ }.join(", ")
245
+ rescue
246
+ "Illegal job arguments: #{h args.inspect}"
247
+ end
248
+ end
249
+
250
+ def csrf_tag
251
+ "<input type='hidden' name='authenticity_token' value='#{env[:csrf_token]}'/>"
252
+ end
253
+
254
+ def to_display(arg)
255
+ arg.inspect
256
+ rescue
257
+ begin
258
+ arg.to_s
259
+ rescue => ex
260
+ "Cannot display argument: [#{ex.class.name}] #{ex.message}"
261
+ end
262
+ end
263
+
264
+ RETRY_JOB_KEYS = Set.new(%w[
265
+ queue class args retry_count retried_at failed_at
266
+ jid error_message error_class backtrace
267
+ error_backtrace enqueued_at retry wrapped
268
+ created_at tags display_class
269
+ ])
270
+
271
+ def retry_extra_items(retry_job)
272
+ @retry_extra_items ||= {}.tap do |extra|
273
+ retry_job.item.each do |key, value|
274
+ extra[key] = value unless RETRY_JOB_KEYS.include?(key)
275
+ end
276
+ end
277
+ end
278
+
279
+ def format_memory(rss_kb)
280
+ return "0" if rss_kb.nil? || rss_kb == 0
281
+
282
+ if rss_kb < 100_000
283
+ "#{number_with_delimiter(rss_kb)} KB"
284
+ elsif rss_kb < 10_000_000
285
+ "#{number_with_delimiter((rss_kb / 1024.0).to_i)} MB"
286
+ else
287
+ "#{number_with_delimiter((rss_kb / (1024.0 * 1024.0)).round(1))} GB"
288
+ end
289
+ end
290
+
291
+ def number_with_delimiter(number)
292
+ return "" if number.nil?
293
+
294
+ begin
295
+ Float(number)
296
+ rescue ArgumentError, TypeError
297
+ return number
298
+ end
299
+
300
+ options = {delimiter: ",", separator: "."}
301
+ parts = number.to_s.to_str.split(".")
302
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
303
+ parts.join(options[:separator])
304
+ end
305
+
306
+ def h(text)
307
+ ::Rack::Utils.escape_html(text)
308
+ rescue ArgumentError => e
309
+ raise unless e.message.eql?("invalid byte sequence in UTF-8")
310
+ text.encode!("UTF-16", "UTF-8", invalid: :replace, replace: "").encode!("UTF-8", "UTF-16")
311
+ retry
312
+ end
313
+
314
+ # Any paginated list that performs an action needs to redirect
315
+ # back to the proper page after performing that action.
316
+ def redirect_with_query(url)
317
+ r = request.referer
318
+ if r && r =~ /\?/
319
+ ref = URI(r)
320
+ redirect("#{url}?#{ref.query}")
321
+ else
322
+ redirect url
323
+ end
324
+ end
325
+
326
+ def environment_title_prefix
327
+ environment = Sidekiq[:environment] || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
328
+
329
+ "[#{environment.upcase}] " unless environment == "production"
330
+ end
331
+
332
+ def product_version
333
+ "Sidekiq v#{Sidekiq::VERSION}"
334
+ end
335
+
336
+ def server_utc_time
337
+ Time.now.utc.strftime("%H:%M:%S UTC")
338
+ end
339
+
340
+ def redis_connection_and_namespace
341
+ @redis_connection_and_namespace ||= begin
342
+ namespace_suffix = namespace.nil? ? "" : "##{namespace}"
343
+ "#{redis_connection}#{namespace_suffix}"
344
+ end
345
+ end
346
+
347
+ def retry_or_delete_or_kill(job, params)
348
+ if params["retry"]
349
+ job.retry
350
+ elsif params["delete"]
351
+ job.delete
352
+ elsif params["kill"]
353
+ job.kill
354
+ end
355
+ end
356
+
357
+ def delete_or_add_queue(job, params)
358
+ if params["delete"]
359
+ job.delete
360
+ elsif params["add_to_queue"]
361
+ job.add_to_queue
362
+ end
363
+ end
364
+ end
365
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack"
4
+
5
+ module Sidekiq
6
+ module WebRouter
7
+ GET = "GET"
8
+ DELETE = "DELETE"
9
+ POST = "POST"
10
+ PUT = "PUT"
11
+ PATCH = "PATCH"
12
+ HEAD = "HEAD"
13
+
14
+ ROUTE_PARAMS = "rack.route_params"
15
+ REQUEST_METHOD = "REQUEST_METHOD"
16
+ PATH_INFO = "PATH_INFO"
17
+
18
+ def head(path, &block)
19
+ route(HEAD, path, &block)
20
+ end
21
+
22
+ def get(path, &block)
23
+ route(GET, path, &block)
24
+ end
25
+
26
+ def post(path, &block)
27
+ route(POST, path, &block)
28
+ end
29
+
30
+ def put(path, &block)
31
+ route(PUT, path, &block)
32
+ end
33
+
34
+ def patch(path, &block)
35
+ route(PATCH, path, &block)
36
+ end
37
+
38
+ def delete(path, &block)
39
+ route(DELETE, path, &block)
40
+ end
41
+
42
+ def route(method, path, &block)
43
+ @routes ||= {GET => [], POST => [], PUT => [], PATCH => [], DELETE => [], HEAD => []}
44
+
45
+ @routes[method] << WebRoute.new(method, path, block)
46
+ end
47
+
48
+ def match(env)
49
+ request_method = env[REQUEST_METHOD]
50
+ path_info = ::Rack::Utils.unescape env[PATH_INFO]
51
+
52
+ # There are servers which send an empty string when requesting the root.
53
+ # These servers should be ashamed of themselves.
54
+ path_info = "/" if path_info == ""
55
+
56
+ @routes[request_method].each do |route|
57
+ params = route.match(request_method, path_info)
58
+ if params
59
+ env[ROUTE_PARAMS] = params
60
+
61
+ return WebAction.new(env, route.block)
62
+ end
63
+ end
64
+
65
+ nil
66
+ end
67
+ end
68
+
69
+ class WebRoute
70
+ attr_accessor :request_method, :pattern, :block, :name
71
+
72
+ NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^.:$\/]+)/
73
+
74
+ def initialize(request_method, pattern, block)
75
+ @request_method = request_method
76
+ @pattern = pattern
77
+ @block = block
78
+ end
79
+
80
+ def matcher
81
+ @matcher ||= compile
82
+ end
83
+
84
+ def compile
85
+ if pattern.match?(NAMED_SEGMENTS_PATTERN)
86
+ p = pattern.gsub(NAMED_SEGMENTS_PATTERN, '/\1(?<\2>[^$/]+)')
87
+
88
+ Regexp.new("\\A#{p}\\Z")
89
+ else
90
+ pattern
91
+ end
92
+ end
93
+
94
+ def match(request_method, path)
95
+ case matcher
96
+ when String
97
+ {} if path == matcher
98
+ else
99
+ path_match = path.match(matcher)
100
+ path_match&.named_captures&.transform_keys(&:to_sym)
101
+ end
102
+ end
103
+ end
104
+ end