sidekiq 7.3.7 → 8.0.0.beta1

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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +30 -0
  3. data/README.md +16 -13
  4. data/bin/sidekiqload +10 -10
  5. data/bin/webload +69 -0
  6. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +5 -18
  7. data/lib/sidekiq/api.rb +108 -36
  8. data/lib/sidekiq/capsule.rb +1 -1
  9. data/lib/sidekiq/cli.rb +15 -19
  10. data/lib/sidekiq/client.rb +13 -16
  11. data/lib/sidekiq/component.rb +30 -2
  12. data/lib/sidekiq/config.rb +18 -15
  13. data/lib/sidekiq/embedded.rb +1 -0
  14. data/lib/sidekiq/job/iterable.rb +3 -5
  15. data/lib/sidekiq/job_retry.rb +2 -2
  16. data/lib/sidekiq/job_util.rb +5 -1
  17. data/lib/sidekiq/launcher.rb +1 -1
  18. data/lib/sidekiq/logger.rb +6 -10
  19. data/lib/sidekiq/manager.rb +0 -1
  20. data/lib/sidekiq/metrics/query.rb +1 -3
  21. data/lib/sidekiq/middleware/current_attributes.rb +5 -17
  22. data/lib/sidekiq/paginator.rb +14 -1
  23. data/lib/sidekiq/processor.rb +21 -14
  24. data/lib/sidekiq/profiler.rb +59 -0
  25. data/lib/sidekiq/rails.rb +12 -2
  26. data/lib/sidekiq/redis_client_adapter.rb +0 -1
  27. data/lib/sidekiq/testing.rb +2 -2
  28. data/lib/sidekiq/version.rb +2 -2
  29. data/lib/sidekiq/web/action.rb +101 -85
  30. data/lib/sidekiq/web/application.rb +339 -333
  31. data/lib/sidekiq/web/config.rb +116 -0
  32. data/lib/sidekiq/web/helpers.rb +45 -20
  33. data/lib/sidekiq/web/router.rb +60 -76
  34. data/lib/sidekiq/web.rb +51 -156
  35. data/sidekiq.gemspec +6 -4
  36. data/web/assets/javascripts/application.js +6 -13
  37. data/web/assets/javascripts/base-charts.js +30 -18
  38. data/web/assets/javascripts/dashboard-charts.js +2 -0
  39. data/web/assets/javascripts/dashboard.js +6 -0
  40. data/web/assets/javascripts/metrics.js +1 -1
  41. data/web/assets/stylesheets/style.css +750 -0
  42. data/web/locales/ar.yml +1 -0
  43. data/web/locales/cs.yml +1 -0
  44. data/web/locales/da.yml +1 -0
  45. data/web/locales/de.yml +1 -0
  46. data/web/locales/el.yml +1 -0
  47. data/web/locales/en.yml +9 -0
  48. data/web/locales/es.yml +24 -2
  49. data/web/locales/fa.yml +1 -0
  50. data/web/locales/fr.yml +1 -0
  51. data/web/locales/gd.yml +1 -0
  52. data/web/locales/he.yml +1 -0
  53. data/web/locales/hi.yml +1 -0
  54. data/web/locales/it.yml +1 -0
  55. data/web/locales/ja.yml +1 -0
  56. data/web/locales/ko.yml +1 -0
  57. data/web/locales/lt.yml +1 -0
  58. data/web/locales/nb.yml +1 -0
  59. data/web/locales/nl.yml +1 -0
  60. data/web/locales/pl.yml +1 -0
  61. data/web/locales/{pt-br.yml → pt-BR.yml} +2 -1
  62. data/web/locales/pt.yml +1 -0
  63. data/web/locales/ru.yml +1 -0
  64. data/web/locales/sv.yml +1 -0
  65. data/web/locales/ta.yml +1 -0
  66. data/web/locales/tr.yml +1 -0
  67. data/web/locales/uk.yml +1 -0
  68. data/web/locales/ur.yml +1 -0
  69. data/web/locales/vi.yml +1 -0
  70. data/web/locales/{zh-cn.yml → zh-CN.yml} +85 -73
  71. data/web/locales/{zh-tw.yml → zh-TW.yml} +2 -1
  72. data/web/views/_footer.erb +31 -34
  73. data/web/views/_job_info.erb +91 -89
  74. data/web/views/_metrics_period_select.erb +1 -1
  75. data/web/views/_nav.erb +14 -21
  76. data/web/views/_paging.erb +23 -21
  77. data/web/views/_poll_link.erb +2 -2
  78. data/web/views/_summary.erb +16 -16
  79. data/web/views/busy.erb +124 -122
  80. data/web/views/dashboard.erb +62 -64
  81. data/web/views/dead.erb +31 -27
  82. data/web/views/filtering.erb +3 -3
  83. data/web/views/layout.erb +5 -21
  84. data/web/views/metrics.erb +83 -80
  85. data/web/views/metrics_for_job.erb +39 -42
  86. data/web/views/morgue.erb +61 -70
  87. data/web/views/profiles.erb +43 -0
  88. data/web/views/queue.erb +54 -52
  89. data/web/views/queues.erb +43 -41
  90. data/web/views/retries.erb +66 -75
  91. data/web/views/retry.erb +32 -27
  92. data/web/views/scheduled.erb +58 -54
  93. data/web/views/scheduled_job_info.erb +1 -1
  94. metadata +46 -22
  95. data/web/assets/stylesheets/application-dark.css +0 -147
  96. data/web/assets/stylesheets/application-rtl.css +0 -163
  97. data/web/assets/stylesheets/application.css +0 -759
  98. data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
  99. data/web/assets/stylesheets/bootstrap.css +0 -5
  100. data/web/views/_status.erb +0 -4
data/lib/sidekiq/web.rb CHANGED
@@ -2,20 +2,11 @@
2
2
 
3
3
  require "erb"
4
4
  require "securerandom"
5
-
6
- require "sidekiq"
7
- require "sidekiq/api"
8
- require "sidekiq/paginator"
9
- require "sidekiq/web/helpers"
10
-
11
- require "sidekiq/web/router"
12
- require "sidekiq/web/action"
13
- require "sidekiq/web/application"
14
- require "sidekiq/web/csrf_protection"
15
-
16
- require "rack/content_length"
17
5
  require "rack/builder"
18
6
  require "rack/static"
7
+ require "sidekiq"
8
+ require "sidekiq/api"
9
+ require "sidekiq/web/config"
19
10
 
20
11
  module Sidekiq
21
12
  class Web
@@ -32,176 +23,86 @@ module Sidekiq
32
23
  "Retries" => "retries",
33
24
  "Scheduled" => "scheduled",
34
25
  "Dead" => "morgue",
35
- "Metrics" => "metrics"
26
+ "Metrics" => "metrics",
27
+ "Profiles" => "profiles"
36
28
  }
37
29
 
38
- if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
39
- CONTENT_LANGUAGE = "Content-Language"
40
- CONTENT_SECURITY_POLICY = "Content-Security-Policy"
41
- LOCATION = "Location"
42
- X_CASCADE = "X-Cascade"
43
- X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options"
44
- else
45
- CONTENT_LANGUAGE = "content-language"
46
- CONTENT_SECURITY_POLICY = "content-security-policy"
47
- LOCATION = "location"
48
- X_CASCADE = "x-cascade"
49
- X_CONTENT_TYPE_OPTIONS = "x-content-type-options"
50
- end
30
+ @@config = Sidekiq::Web::Config.new
51
31
 
52
32
  class << self
53
- # Forward compatibility with 8.0
54
33
  def configure
55
- yield self
56
- end
57
-
58
- def settings
59
- self
34
+ if block_given?
35
+ yield @@config
36
+ else
37
+ @@config
38
+ end
60
39
  end
61
40
 
62
- def default_tabs
63
- DEFAULT_TABS
41
+ def app_url=(url)
42
+ @@config.app_url = url
64
43
  end
65
44
 
66
- def custom_tabs
67
- @custom_tabs ||= {}
68
- end
69
- alias_method :tabs, :custom_tabs
45
+ def tabs = @@config.tabs
70
46
 
71
- def custom_job_info_rows
72
- @custom_job_info_rows ||= []
73
- end
47
+ def locales = @@config.locales
74
48
 
75
- def locales
76
- @locales ||= LOCALES
77
- end
49
+ def views = @@config.views
78
50
 
79
- def views
80
- @views ||= VIEWS
81
- end
51
+ def custom_job_info_rows = @@config.custom_job_info_rows
82
52
 
83
- def enable(*opts)
84
- opts.each { |key| set(key, true) }
53
+ def redis_pool
54
+ @pool || Sidekiq.default_configuration.redis_pool
85
55
  end
86
56
 
87
- def disable(*opts)
88
- opts.each { |key| set(key, false) }
57
+ def redis_pool=(pool)
58
+ @pool = pool
89
59
  end
90
60
 
91
- def middlewares
92
- @middlewares ||= []
93
- end
61
+ def middlewares = @@config.middlewares
94
62
 
95
- def use(*args, &block)
96
- middlewares << [args, block]
97
- end
63
+ def use(*args, &block) = @@config.middlewares << [args, block]
98
64
 
99
- def set(attribute, value)
100
- send(:"#{attribute}=", value)
65
+ def register(*args, **kw, &block)
66
+ # TODO
67
+ puts "`Sidekiq::Web.register` is deprecated, use `Sidekiq::Web.configure {|cfg| cfg.register(...) }`"
68
+ @@config.register(*args, **kw, &block)
101
69
  end
102
-
103
- attr_accessor :app_url, :redis_pool
104
- attr_writer :locales, :views
105
- end
106
-
107
- def self.inherited(child)
108
- child.app_url = app_url
109
- child.redis_pool = redis_pool
110
- end
111
-
112
- def settings
113
- self.class.settings
114
70
  end
115
71
 
116
- def middlewares
117
- @middlewares ||= self.class.middlewares
72
+ # Allow user to say
73
+ # run Sidekiq::Web
74
+ # rather than:
75
+ # run Sidekiq::Web.new
76
+ def self.call(env)
77
+ @inst ||= new
78
+ @inst.call(env)
118
79
  end
119
80
 
120
- def use(*args, &block)
121
- middlewares << [args, block]
81
+ # testing, internal use only
82
+ def self.reset!
83
+ @@config.reset!
84
+ @inst = nil
122
85
  end
123
86
 
124
87
  def call(env)
125
- env[:csp_nonce] = SecureRandom.base64(16)
88
+ env[:web_config] = Sidekiq::Web.configure
89
+ env[:csp_nonce] = SecureRandom.hex(8)
90
+ env[:redis_pool] = self.class.redis_pool
126
91
  app.call(env)
127
92
  end
128
93
 
129
- def self.call(env)
130
- @app ||= new
131
- @app.call(env)
132
- end
133
-
134
94
  def app
135
- @app ||= build
136
- end
137
-
138
- def enable(*opts)
139
- opts.each { |key| set(key, true) }
140
- end
141
-
142
- def disable(*opts)
143
- opts.each { |key| set(key, false) }
144
- end
145
-
146
- def set(attribute, value)
147
- send(:"#{attribute}=", value)
148
- end
149
-
150
- # Register a class as a Sidekiq Web UI extension. The class should
151
- # provide one or more tabs which map to an index route. Options:
152
- #
153
- # @param extension [Class] Class which contains the HTTP actions, required
154
- # @param name [String] the name of the extension, used to namespace assets
155
- # @param tab [String | Array] labels(s) of the UI tabs
156
- # @param index [String | Array] index route(s) for each tab
157
- # @param root_dir [String] directory location to find assets, locales and views, typically `web/` within the gemfile
158
- # @param asset_paths [Array] one or more directories under {root}/assets/{name} to be publicly served, e.g. ["js", "css", "img"]
159
- # @param cache_for [Integer] amount of time to cache assets, default one day
160
- #
161
- # TODO name, tab and index will be mandatory in 8.0
162
- #
163
- # Web extensions will have a root `web/` directory with `locales/`, `assets/`
164
- # and `views/` subdirectories.
165
- def self.register(extension, name: nil, tab: nil, index: nil, root_dir: nil, cache_for: 86400, asset_paths: nil)
166
- tab = Array(tab)
167
- index = Array(index)
168
- tab.zip(index).each do |tab, index|
169
- tabs[tab] = index
170
- end
171
- if root_dir
172
- locdir = File.join(root_dir, "locales")
173
- locales << locdir if File.directory?(locdir)
174
-
175
- if asset_paths && name
176
- # if you have {root}/assets/{name}/js/scripts.js
177
- # and {root}/assets/{name}/css/styles.css
178
- # you would pass in:
179
- # asset_paths: ["js", "css"]
180
- # See script_tag and style_tag in web/helpers.rb
181
- assdir = File.join(root_dir, "assets")
182
- assurls = Array(asset_paths).map { |x| "/#{name}/#{x}" }
183
- assetprops = {
184
- urls: assurls,
185
- root: assdir,
186
- cascade: true
187
- }
188
- assetprops[:header_rules] = [[:all, {Rack::CACHE_CONTROL => "private, max-age=#{cache_for.to_i}"}]] if cache_for
189
- middlewares << [[Rack::Static, assetprops], nil]
190
- end
191
- end
192
-
193
- yield self if block_given?
194
- extension.registered(WebApplication)
95
+ @app ||= build(@@config)
195
96
  end
196
97
 
197
98
  private
198
99
 
199
- def build
200
- klass = self.class
201
- m = middlewares
100
+ def build(cfg)
101
+ cfg.freeze
102
+ m = cfg.middlewares
202
103
 
203
104
  rules = []
204
- rules = [[:all, {Rack::CACHE_CONTROL => "private, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
105
+ rules = [[:all, {"cache-control" => "private, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
205
106
 
206
107
  ::Rack::Builder.new do
207
108
  use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"],
@@ -209,18 +110,12 @@ module Sidekiq
209
110
  cascade: true,
210
111
  header_rules: rules
211
112
  m.each { |middleware, block| use(*middleware, &block) }
212
- use Sidekiq::Web::CsrfProtection unless $TESTING
213
- run WebApplication.new(klass)
113
+ run Sidekiq::Web::Application.new(self.class)
214
114
  end
215
115
  end
216
116
  end
217
-
218
- Sidekiq::WebApplication.helpers WebHelpers
219
- Sidekiq::WebApplication.helpers Sidekiq::Paginator
220
-
221
- Sidekiq::WebAction.class_eval <<-RUBY, Web::LAYOUT, -1 # standard:disable Style/EvalWithLocation
222
- def _render
223
- #{ERB.new(File.read(Web::LAYOUT)).src}
224
- end
225
- RUBY
226
117
  end
118
+
119
+ require "sidekiq/web/router"
120
+ require "sidekiq/web/action"
121
+ require "sidekiq/web/application"
data/sidekiq.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |gem|
12
12
  gem.files = %w[sidekiq.gemspec README.md Changes.md LICENSE.txt] + `git ls-files | grep -E '^(bin|lib|web)'`.split("\n")
13
13
  gem.name = "sidekiq"
14
14
  gem.version = Sidekiq::VERSION
15
- gem.required_ruby_version = ">= 2.7.0"
15
+ gem.required_ruby_version = ">= 3.2.0"
16
16
 
17
17
  gem.metadata = {
18
18
  "homepage_uri" => "https://sidekiq.org",
@@ -23,8 +23,10 @@ Gem::Specification.new do |gem|
23
23
  "rubygems_mfa_required" => "true"
24
24
  }
25
25
 
26
- gem.add_dependency "redis-client", ">= 0.22.2"
27
- gem.add_dependency "connection_pool", ">= 2.3.0"
28
- gem.add_dependency "rack", ">= 2.2.4"
26
+ gem.add_dependency "redis-client", ">= 0.23.2"
27
+ gem.add_dependency "connection_pool", ">= 2.5.0"
28
+ gem.add_dependency "rack", ">= 3.1.0"
29
+ gem.add_dependency "json", ">= 2.9.0"
29
30
  gem.add_dependency "logger"
31
+ gem.add_dependency "base64"
30
32
  end
@@ -31,7 +31,7 @@ function addListeners() {
31
31
  node.addEventListener("click", addDataToggleListeners)
32
32
  })
33
33
 
34
- addShiftClickListeners()
34
+ addShiftClickListeners();
35
35
  updateFuzzyTimes();
36
36
  updateNumbers();
37
37
  updateProgressBars();
@@ -69,11 +69,7 @@ function addDataToggleListeners(event) {
69
69
  var source = event.target || event.srcElement;
70
70
  var targName = source.getAttribute("data-toggle");
71
71
  var full = document.getElementById(targName);
72
- if (full.style.display == "block") {
73
- full.style.display = 'none';
74
- } else {
75
- full.style.display = 'block';
76
- }
72
+ full.classList.toggle("is-open");
77
73
  }
78
74
 
79
75
  function addShiftClickListeners() {
@@ -130,11 +126,11 @@ function setLivePollFromUrl() {
130
126
 
131
127
  function updateLivePollButton() {
132
128
  if (localStorage.sidekiqLivePoll == "enabled") {
133
- document.querySelectorAll('.live-poll-stop').forEach(box => { box.style.display = "inline-block" })
134
- document.querySelectorAll('.live-poll-start').forEach(box => { box.style.display = "none" })
129
+ document.querySelectorAll('.live-poll-stop').forEach(box => { box.classList.add("active") })
130
+ document.querySelectorAll('.live-poll-start').forEach(box => { box.classList.remove("active") })
135
131
  } else {
136
- document.querySelectorAll('.live-poll-start').forEach(box => { box.style.display = "inline-block" })
137
- document.querySelectorAll('.live-poll-stop').forEach(box => { box.style.display = "none" })
132
+ document.querySelectorAll('.live-poll-start').forEach(box => { box.classList.add("active") })
133
+ document.querySelectorAll('.live-poll-stop').forEach(box => { box.classList.remove("active") })
138
134
  }
139
135
  }
140
136
 
@@ -169,9 +165,6 @@ function replacePage(text) {
169
165
  var page = doc.querySelector('#page')
170
166
  document.querySelector("#page").replaceWith(page)
171
167
 
172
- var header_status = doc.querySelector('.status')
173
- document.querySelector('.status').replaceWith(header_status)
174
-
175
168
  addListeners();
176
169
  }
177
170
 
@@ -1,27 +1,33 @@
1
1
  if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
2
- Chart.defaults.borderColor = "#333";
3
- Chart.defaults.color = "#aaa";
2
+ Chart.defaults.borderColor = "oklch(22% 0.01 256)";
3
+ Chart.defaults.color = "oklch(65% 0.01 256)";
4
4
  }
5
5
 
6
6
  class Colors {
7
7
  constructor() {
8
8
  this.assignments = {};
9
- this.success = "#006f68";
10
- this.failure = "#af0014";
11
- this.fallback = "#999";
12
- this.primary = "#537bc4";
9
+ if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
10
+ this.light = "65%";
11
+ this.chroma = "0.15";
12
+ } else {
13
+ this.light = "48%";
14
+ this.chroma = "0.2";
15
+ }
16
+ this.success = "oklch(" + this.light + " " + this.chroma + " 179)";
17
+ this.failure = "oklch(" + this.light + " " + this.chroma + " 29)";
18
+ this.fallback = "oklch(" + this.light + " 0.02 269)";
19
+ this.primary = "oklch(" + this.light + " " + this.chroma + " 269)";
13
20
  this.available = [
14
- // Colors taken from https://www.chartjs.org/docs/latest/samples/utils.html
15
- "#537bc4",
16
- "#4dc9f6",
17
- "#f67019",
18
- "#f53794",
19
- "#acc236",
20
- "#166a8f",
21
- "#00a950",
22
- "#58595b",
23
- "#8549ba",
24
- "#991b1b",
21
+ "oklch(" + this.light + " " + this.chroma + " 256)",
22
+ "oklch(" + this.light + " " + this.chroma + " 196)",
23
+ "oklch(" + this.light + " " + this.chroma + " 46)",
24
+ "oklch(" + this.light + " " + this.chroma + " 316)",
25
+ "oklch(" + this.light + " " + this.chroma + " 106)",
26
+ "oklch(" + this.light + " " + this.chroma + " 226)",
27
+ "oklch(" + this.light + " " + this.chroma + " 136)",
28
+ "oklch(" + this.light + " 0.02 269)",
29
+ "oklch(" + this.light + " " + this.chroma + " 286)",
30
+ "oklch(" + this.light + " " + this.chroma + " 16)",
25
31
  ];
26
32
  }
27
33
 
@@ -90,12 +96,18 @@ class BaseChart {
90
96
  };
91
97
 
92
98
  if (this.options.marks) {
99
+ if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
100
+ this.Borderlight = "30%";
101
+ } else {
102
+ this.Borderlight = "65%";
103
+ }
104
+
93
105
  this.options.marks.forEach(([bucket, label], i) => {
94
106
  chartOptions.plugins.annotation.annotations[`deploy-${i}`] = {
95
107
  type: "line",
96
108
  xMin: bucket,
97
109
  xMax: bucket,
98
- borderColor: "rgba(220, 38, 38, 0.4)",
110
+ borderColor: "oklch(" + this.Borderlight + " 0.01 256)",
99
111
  borderWidth: 2,
100
112
  };
101
113
  });
@@ -83,6 +83,8 @@ class RealtimeChart extends DashboardChart {
83
83
  this.chart.data.datasets[1].data.push(failed);
84
84
  this.chart.update();
85
85
 
86
+ updateScreenReaderDashboardValues(processed, failed);
87
+
86
88
  updateStatsSummary(this.stats.sidekiq);
87
89
  updateRedisStats(this.stats.redis);
88
90
  updateFooterUTCTime(this.stats.server_utc_time);
@@ -36,6 +36,12 @@ var ready = (callback) => {
36
36
  else document.addEventListener("DOMContentLoaded", callback);
37
37
  }
38
38
 
39
+ var updateScreenReaderDashboardValues = function(processed, failed) {
40
+ let lastDashboardUpdateSpan = document.getElementById("sr-last-dashboard-update");
41
+ var updateText = document.getElementById("sr-last-dashboard-update-template").innerText;
42
+ lastDashboardUpdateSpan.innerText = updateText.replace("PROCESSED_COUNT", processed).replace("FAILED_COUNT", failed);
43
+ }
44
+
39
45
  ready(() => {
40
46
  var sldr = document.getElementById('sldr');
41
47
  if (typeof localStorage.sidekiqTimeInterval !== 'undefined') {
@@ -39,7 +39,7 @@ class JobMetricsOverviewChart extends BaseChart {
39
39
  updateSwatch(kls, checked) {
40
40
  const el = this.swatches[kls];
41
41
  el.checked = checked;
42
- el.style.color = this.colors.assignments[kls] || "";
42
+ el.style.accentColor = this.colors.assignments[kls] || "";
43
43
  }
44
44
 
45
45
  toggleKls(kls, visible) {