sidekiq 6.5.9 → 7.0.0.beta1
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.
- checksums.yaml +4 -4
- data/Changes.md +5 -13
- data/README.md +13 -12
- data/bin/sidekiq +3 -8
- data/bin/sidekiqload +14 -23
- data/lib/sidekiq/api.rb +51 -127
- data/lib/sidekiq/capsule.rb +110 -0
- data/lib/sidekiq/cli.rb +44 -59
- data/lib/sidekiq/client.rb +30 -17
- data/lib/sidekiq/component.rb +1 -0
- data/lib/sidekiq/config.rb +270 -0
- data/lib/sidekiq/deploy.rb +62 -0
- data/lib/sidekiq/embedded.rb +61 -0
- data/lib/sidekiq/fetch.rb +10 -11
- data/lib/sidekiq/job.rb +375 -10
- data/lib/sidekiq/job_logger.rb +1 -1
- data/lib/sidekiq/job_retry.rb +8 -8
- data/lib/sidekiq/job_util.rb +4 -4
- data/lib/sidekiq/launcher.rb +36 -46
- data/lib/sidekiq/logger.rb +1 -26
- data/lib/sidekiq/manager.rb +9 -11
- data/lib/sidekiq/metrics/query.rb +3 -3
- data/lib/sidekiq/metrics/shared.rb +4 -3
- data/lib/sidekiq/metrics/tracking.rb +18 -18
- data/lib/sidekiq/middleware/chain.rb +7 -9
- data/lib/sidekiq/middleware/current_attributes.rb +3 -8
- data/lib/sidekiq/monitor.rb +1 -1
- data/lib/sidekiq/paginator.rb +1 -9
- data/lib/sidekiq/pool.rb +7 -0
- data/lib/sidekiq/processor.rb +17 -26
- data/lib/sidekiq/redis_client_adapter.rb +9 -45
- data/lib/sidekiq/redis_connection.rb +11 -111
- data/lib/sidekiq/scheduled.rb +19 -20
- data/lib/sidekiq/testing.rb +4 -32
- data/lib/sidekiq/transaction_aware_client.rb +4 -5
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/application.rb +1 -4
- data/lib/sidekiq/web/csrf_protection.rb +1 -1
- data/lib/sidekiq/web/helpers.rb +21 -22
- data/lib/sidekiq/web.rb +2 -17
- data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
- data/lib/sidekiq.rb +52 -274
- data/sidekiq.gemspec +29 -5
- data/web/assets/javascripts/base-charts.js +106 -0
- data/web/assets/javascripts/dashboard-charts.js +166 -0
- data/web/assets/javascripts/dashboard.js +3 -223
- data/web/assets/javascripts/metrics.js +90 -116
- data/web/assets/stylesheets/application-rtl.css +2 -91
- data/web/assets/stylesheets/application.css +21 -296
- data/web/locales/ar.yml +70 -70
- data/web/locales/cs.yml +62 -62
- data/web/locales/da.yml +52 -52
- data/web/locales/de.yml +65 -65
- data/web/locales/el.yml +2 -7
- data/web/locales/en.yml +76 -70
- data/web/locales/es.yml +68 -68
- data/web/locales/fa.yml +65 -65
- data/web/locales/fr.yml +67 -67
- data/web/locales/he.yml +65 -64
- data/web/locales/hi.yml +59 -59
- data/web/locales/it.yml +53 -53
- data/web/locales/ja.yml +64 -68
- data/web/locales/ko.yml +52 -52
- data/web/locales/lt.yml +66 -66
- data/web/locales/nb.yml +61 -61
- data/web/locales/nl.yml +52 -52
- data/web/locales/pl.yml +45 -45
- data/web/locales/pt-br.yml +59 -69
- data/web/locales/pt.yml +51 -51
- data/web/locales/ru.yml +67 -66
- data/web/locales/sv.yml +53 -53
- data/web/locales/ta.yml +60 -60
- data/web/locales/uk.yml +62 -61
- data/web/locales/ur.yml +64 -64
- data/web/locales/vi.yml +67 -67
- data/web/locales/zh-cn.yml +1 -0
- data/web/locales/zh-tw.yml +10 -1
- data/web/views/_footer.erb +5 -2
- data/web/views/busy.erb +1 -6
- data/web/views/dashboard.erb +36 -5
- data/web/views/metrics.erb +30 -19
- data/web/views/metrics_for_job.erb +16 -34
- metadata +60 -37
- data/lib/sidekiq/delay.rb +0 -43
- data/lib/sidekiq/extensions/action_mailer.rb +0 -48
- data/lib/sidekiq/extensions/active_record.rb +0 -43
- data/lib/sidekiq/extensions/class_methods.rb +0 -43
- data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
- data/lib/sidekiq/metrics/deploy.rb +0 -47
- data/lib/sidekiq/worker.rb +0 -370
- data/web/assets/javascripts/graph.js +0 -16
- /data/{LICENSE → LICENSE.txt} +0 -0
data/lib/sidekiq.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "sidekiq/version"
|
4
|
-
fail "Sidekiq #{Sidekiq::VERSION} does not support Ruby versions below 2.
|
4
|
+
fail "Sidekiq #{Sidekiq::VERSION} does not support Ruby versions below 2.7.0." if RUBY_PLATFORM != "java" && Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.7.0")
|
5
5
|
|
6
|
+
require "sidekiq/config"
|
6
7
|
require "sidekiq/logger"
|
7
8
|
require "sidekiq/client"
|
8
9
|
require "sidekiq/transaction_aware_client"
|
9
|
-
require "sidekiq/worker"
|
10
10
|
require "sidekiq/job"
|
11
|
-
require "sidekiq/
|
12
|
-
require "sidekiq/
|
11
|
+
require "sidekiq/worker_compatibility_alias"
|
12
|
+
require "sidekiq/redis_client_adapter"
|
13
13
|
|
14
14
|
require "json"
|
15
15
|
|
@@ -17,320 +17,98 @@ module Sidekiq
|
|
17
17
|
NAME = "Sidekiq"
|
18
18
|
LICENSE = "See LICENSE and the LGPL-3.0 for licensing details."
|
19
19
|
|
20
|
-
DEFAULTS = {
|
21
|
-
queues: [],
|
22
|
-
labels: [],
|
23
|
-
concurrency: 10,
|
24
|
-
require: ".",
|
25
|
-
strict: true,
|
26
|
-
environment: nil,
|
27
|
-
timeout: 25,
|
28
|
-
poll_interval_average: nil,
|
29
|
-
average_scheduled_poll_interval: 5,
|
30
|
-
on_complex_arguments: :warn,
|
31
|
-
error_handlers: [],
|
32
|
-
death_handlers: [],
|
33
|
-
lifecycle_events: {
|
34
|
-
startup: [],
|
35
|
-
quiet: [],
|
36
|
-
shutdown: [],
|
37
|
-
# triggers when we fire the first heartbeat on startup OR repairing a network partition
|
38
|
-
heartbeat: [],
|
39
|
-
# triggers on EVERY heartbeat call, every 10 seconds
|
40
|
-
beat: []
|
41
|
-
},
|
42
|
-
dead_max_jobs: 10_000,
|
43
|
-
dead_timeout_in_seconds: 180 * 24 * 60 * 60, # 6 months
|
44
|
-
reloader: proc { |&block| block.call }
|
45
|
-
}
|
46
|
-
|
47
|
-
FAKE_INFO = {
|
48
|
-
"redis_version" => "9.9.9",
|
49
|
-
"uptime_in_days" => "9999",
|
50
|
-
"connected_clients" => "9999",
|
51
|
-
"used_memory_human" => "9P",
|
52
|
-
"used_memory_peak_human" => "9P"
|
53
|
-
}
|
54
|
-
|
55
20
|
def self.❨╯°□°❩╯︵┻━┻
|
56
|
-
puts "
|
57
|
-
end
|
58
|
-
|
59
|
-
# config.concurrency = 5
|
60
|
-
def self.concurrency=(val)
|
61
|
-
self[:concurrency] = Integer(val)
|
62
|
-
end
|
63
|
-
|
64
|
-
# config.queues = %w( high default low ) # strict
|
65
|
-
# config.queues = %w( high,3 default,2 low,1 ) # weighted
|
66
|
-
# config.queues = %w( feature1,1 feature2,1 feature3,1 ) # random
|
67
|
-
#
|
68
|
-
# With weighted priority, queue will be checked first (weight / total) of the time.
|
69
|
-
# high will be checked first (3/6) or 50% of the time.
|
70
|
-
# I'd recommend setting weights between 1-10. Weights in the hundreds or thousands
|
71
|
-
# are ridiculous and unnecessarily expensive. You can get random queue ordering
|
72
|
-
# by explicitly setting all weights to 1.
|
73
|
-
def self.queues=(val)
|
74
|
-
self[:queues] = Array(val).each_with_object([]) do |qstr, memo|
|
75
|
-
name, weight = qstr.split(",")
|
76
|
-
self[:strict] = false if weight.to_i > 0
|
77
|
-
[weight.to_i, 1].max.times do
|
78
|
-
memo << name
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
### Private APIs
|
84
|
-
def self.default_error_handler(ex, ctx)
|
85
|
-
logger.warn(dump_json(ctx)) unless ctx.empty?
|
86
|
-
logger.warn("#{ex.class.name}: #{ex.message}")
|
87
|
-
logger.warn(ex.backtrace.join("\n")) unless ex.backtrace.nil?
|
88
|
-
end
|
89
|
-
|
90
|
-
# DEFAULT_ERROR_HANDLER is a constant that allows the default error handler to
|
91
|
-
# be referenced. It must be defined here, after the default_error_handler
|
92
|
-
# method is defined.
|
93
|
-
DEFAULT_ERROR_HANDLER = method(:default_error_handler)
|
94
|
-
|
95
|
-
@config = DEFAULTS.dup
|
96
|
-
def self.options
|
97
|
-
logger.warn "`config.options[:key] = value` is deprecated, use `config[:key] = value`: #{caller(1..2)}"
|
98
|
-
@config
|
99
|
-
end
|
100
|
-
|
101
|
-
def self.options=(opts)
|
102
|
-
logger.warn "config.options = hash` is deprecated, use `config.merge!(hash)`: #{caller(1..2)}"
|
103
|
-
@config = opts
|
104
|
-
end
|
105
|
-
|
106
|
-
def self.[](key)
|
107
|
-
@config[key]
|
108
|
-
end
|
109
|
-
|
110
|
-
def self.[]=(key, val)
|
111
|
-
@config[key] = val
|
112
|
-
end
|
113
|
-
|
114
|
-
def self.merge!(hash)
|
115
|
-
@config.merge!(hash)
|
116
|
-
end
|
117
|
-
|
118
|
-
def self.fetch(*args, &block)
|
119
|
-
@config.fetch(*args, &block)
|
120
|
-
end
|
121
|
-
|
122
|
-
def self.handle_exception(ex, ctx = {})
|
123
|
-
self[:error_handlers].each do |handler|
|
124
|
-
handler.call(ex, ctx)
|
125
|
-
rescue => ex
|
126
|
-
logger.error "!!! ERROR HANDLER THREW AN ERROR !!!"
|
127
|
-
logger.error ex
|
128
|
-
logger.error ex.backtrace.join("\n") unless ex.backtrace.nil?
|
129
|
-
end
|
130
|
-
end
|
131
|
-
###
|
132
|
-
|
133
|
-
##
|
134
|
-
# Configuration for Sidekiq server, use like:
|
135
|
-
#
|
136
|
-
# Sidekiq.configure_server do |config|
|
137
|
-
# config.server_middleware do |chain|
|
138
|
-
# chain.add MyServerHook
|
139
|
-
# end
|
140
|
-
# end
|
141
|
-
def self.configure_server
|
142
|
-
yield self if server?
|
143
|
-
end
|
144
|
-
|
145
|
-
##
|
146
|
-
# Configuration for Sidekiq client, use like:
|
147
|
-
#
|
148
|
-
# Sidekiq.configure_client do |config|
|
149
|
-
# config.redis = { size: 1, url: 'redis://myhost:8877/0' }
|
150
|
-
# end
|
151
|
-
def self.configure_client
|
152
|
-
yield self unless server?
|
21
|
+
puts "Take a deep breath and count to ten..."
|
153
22
|
end
|
154
23
|
|
155
24
|
def self.server?
|
156
25
|
defined?(Sidekiq::CLI)
|
157
26
|
end
|
158
27
|
|
159
|
-
def self.
|
160
|
-
|
161
|
-
redis_pool.with do |conn|
|
162
|
-
retryable = true
|
163
|
-
begin
|
164
|
-
yield conn
|
165
|
-
rescue RedisConnection.adapter::BaseError => ex
|
166
|
-
# 2550 Failover can cause the server to become a replica, need
|
167
|
-
# to disconnect and reopen the socket to get back to the primary.
|
168
|
-
# 4495 Use the same logic if we have a "Not enough replicas" error from the primary
|
169
|
-
# 4985 Use the same logic when a blocking command is force-unblocked
|
170
|
-
# The same retry logic is also used in client.rb
|
171
|
-
if retryable && ex.message =~ /READONLY|NOREPLICAS|UNBLOCKED/
|
172
|
-
conn.disconnect!
|
173
|
-
retryable = false
|
174
|
-
retry
|
175
|
-
end
|
176
|
-
raise
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
def self.redis_info
|
182
|
-
redis do |conn|
|
183
|
-
# admin commands can't go through redis-namespace starting
|
184
|
-
# in redis-namespace 2.0
|
185
|
-
if conn.respond_to?(:namespace)
|
186
|
-
conn.redis.info
|
187
|
-
else
|
188
|
-
conn.info
|
189
|
-
end
|
190
|
-
rescue RedisConnection.adapter::CommandError => ex
|
191
|
-
# 2850 return fake version when INFO command has (probably) been renamed
|
192
|
-
raise unless /unknown command/.match?(ex.message)
|
193
|
-
FAKE_INFO
|
194
|
-
end
|
28
|
+
def self.load_json(string)
|
29
|
+
JSON.parse(string)
|
195
30
|
end
|
196
31
|
|
197
|
-
def self.
|
198
|
-
|
32
|
+
def self.dump_json(object)
|
33
|
+
JSON.generate(object)
|
199
34
|
end
|
200
35
|
|
201
|
-
def self.
|
202
|
-
|
203
|
-
hash
|
204
|
-
else
|
205
|
-
RedisConnection.create(hash)
|
206
|
-
end
|
36
|
+
def self.pro?
|
37
|
+
defined?(Sidekiq::Pro)
|
207
38
|
end
|
208
39
|
|
209
|
-
def self.
|
210
|
-
|
211
|
-
yield @client_chain if block_given?
|
212
|
-
@client_chain
|
40
|
+
def self.ent?
|
41
|
+
defined?(Sidekiq::Enterprise)
|
213
42
|
end
|
214
43
|
|
215
|
-
def self.
|
216
|
-
|
217
|
-
yield @server_chain if block_given?
|
218
|
-
@server_chain
|
44
|
+
def self.redis_pool
|
45
|
+
(Thread.current[:sidekiq_capsule] || default_configuration).redis_pool
|
219
46
|
end
|
220
47
|
|
221
|
-
def self.
|
222
|
-
|
48
|
+
def self.redis(&block)
|
49
|
+
(Thread.current[:sidekiq_capsule] || default_configuration).redis(&block)
|
223
50
|
end
|
224
51
|
|
225
|
-
def self.
|
226
|
-
|
52
|
+
def self.strict_args!(mode = :raise)
|
53
|
+
Sidekiq::Config::DEFAULTS[:on_complex_arguments] = mode
|
227
54
|
end
|
228
55
|
|
229
56
|
def self.default_job_options=(hash)
|
230
57
|
@default_job_options = default_job_options.merge(hash.transform_keys(&:to_s))
|
231
58
|
end
|
232
59
|
|
233
|
-
def self.default_worker_options # deprecated
|
234
|
-
@default_job_options ||= {"retry" => true, "queue" => "default"}
|
235
|
-
end
|
236
|
-
|
237
60
|
def self.default_job_options
|
238
61
|
@default_job_options ||= {"retry" => true, "queue" => "default"}
|
239
62
|
end
|
240
63
|
|
241
|
-
|
242
|
-
|
243
|
-
# the job dies. It's the notification to your application
|
244
|
-
# that this job will not succeed without manual intervention.
|
245
|
-
#
|
246
|
-
# Sidekiq.configure_server do |config|
|
247
|
-
# config.death_handlers << ->(job, ex) do
|
248
|
-
# end
|
249
|
-
# end
|
250
|
-
def self.death_handlers
|
251
|
-
self[:death_handlers]
|
252
|
-
end
|
253
|
-
|
254
|
-
def self.load_json(string)
|
255
|
-
JSON.parse(string)
|
256
|
-
end
|
257
|
-
|
258
|
-
def self.dump_json(object)
|
259
|
-
JSON.generate(object)
|
260
|
-
end
|
261
|
-
|
262
|
-
def self.log_formatter
|
263
|
-
@log_formatter ||= if ENV["DYNO"]
|
264
|
-
Sidekiq::Logger::Formatters::WithoutTimestamp.new
|
265
|
-
else
|
266
|
-
Sidekiq::Logger::Formatters::Pretty.new
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
def self.log_formatter=(log_formatter)
|
271
|
-
@log_formatter = log_formatter
|
272
|
-
logger.formatter = log_formatter
|
64
|
+
def self.default_configuration
|
65
|
+
@config ||= Sidekiq::Config.new
|
273
66
|
end
|
274
67
|
|
275
68
|
def self.logger
|
276
|
-
|
277
|
-
end
|
278
|
-
|
279
|
-
def self.logger=(logger)
|
280
|
-
if logger.nil?
|
281
|
-
self.logger.level = Logger::FATAL
|
282
|
-
return self.logger
|
283
|
-
end
|
284
|
-
|
285
|
-
logger.extend(Sidekiq::LoggingUtils)
|
286
|
-
|
287
|
-
@logger = logger
|
288
|
-
end
|
289
|
-
|
290
|
-
def self.pro?
|
291
|
-
defined?(Sidekiq::Pro)
|
69
|
+
default_configuration.logger
|
292
70
|
end
|
293
71
|
|
294
|
-
def self.
|
295
|
-
|
72
|
+
def self.configure_server(&block)
|
73
|
+
(@config_blocks ||= []) << block
|
74
|
+
yield default_configuration if server?
|
296
75
|
end
|
297
76
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
#
|
302
|
-
# See sidekiq/scheduled.rb for an in-depth explanation of this value
|
303
|
-
def self.average_scheduled_poll_interval=(interval)
|
304
|
-
self[:average_scheduled_poll_interval] = interval
|
77
|
+
def self.freeze!
|
78
|
+
@frozen = true
|
79
|
+
@config_blocks = nil
|
305
80
|
end
|
306
81
|
|
307
|
-
#
|
82
|
+
# Creates a Sidekiq::Config instance that is more tuned for embedding
|
83
|
+
# within an arbitrary Ruby process. Noteably it reduces concurrency by
|
84
|
+
# default so there is less contention for CPU time with other threads.
|
308
85
|
#
|
309
|
-
# Sidekiq.
|
310
|
-
# config.
|
86
|
+
# inst = Sidekiq.configure_embed do |config|
|
87
|
+
# config.queues = %w[critical default low]
|
311
88
|
# end
|
89
|
+
# inst.run
|
90
|
+
# sleep 10
|
91
|
+
# inst.terminate
|
312
92
|
#
|
313
|
-
#
|
314
|
-
|
315
|
-
self[:error_handlers]
|
316
|
-
end
|
317
|
-
|
318
|
-
# Register a block to run at a point in the Sidekiq lifecycle.
|
319
|
-
# :startup, :quiet or :shutdown are valid events.
|
93
|
+
# NB: it is really easy to overload a Ruby process with threads due to the GIL.
|
94
|
+
# I do not recommend setting concurrency higher than 2-3.
|
320
95
|
#
|
321
|
-
#
|
322
|
-
#
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
96
|
+
# NB: Sidekiq only supports one instance in memory. You will get undefined behavior
|
97
|
+
# if you try to embed Sidekiq twice in the same process.
|
98
|
+
def self.configure_embed(&block)
|
99
|
+
raise "Sidekiq global configuration is frozen, you must create all embedded instances BEFORE calling `run`" if @frozen
|
100
|
+
|
101
|
+
require "sidekiq/embedded"
|
102
|
+
cfg = default_configuration
|
103
|
+
cfg.concurrency = 2
|
104
|
+
@config_blocks&.each { |block| block.call(cfg) }
|
105
|
+
yield cfg
|
106
|
+
|
107
|
+
Sidekiq::Embedded.new(cfg)
|
330
108
|
end
|
331
109
|
|
332
|
-
def self.
|
333
|
-
|
110
|
+
def self.configure_client
|
111
|
+
yield default_configuration unless server?
|
334
112
|
end
|
335
113
|
|
336
114
|
# We are shutting down Sidekiq but what about threads that
|
data/sidekiq.gemspec
CHANGED
@@ -9,10 +9,10 @@ Gem::Specification.new do |gem|
|
|
9
9
|
gem.license = "LGPL-3.0"
|
10
10
|
|
11
11
|
gem.executables = ["sidekiq", "sidekiqmon"]
|
12
|
-
gem.files = [
|
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.
|
15
|
+
gem.required_ruby_version = ">= 2.7.0"
|
16
16
|
|
17
17
|
gem.metadata = {
|
18
18
|
"homepage_uri" => "https://sidekiq.org",
|
@@ -22,7 +22,31 @@ Gem::Specification.new do |gem|
|
|
22
22
|
"source_code_uri" => "https://github.com/mperham/sidekiq"
|
23
23
|
}
|
24
24
|
|
25
|
-
gem.add_dependency "redis",
|
26
|
-
gem.add_dependency "connection_pool",
|
27
|
-
gem.add_dependency "rack", "
|
25
|
+
gem.add_dependency "redis-client", ">= 0.9.0"
|
26
|
+
gem.add_dependency "connection_pool", ">= 2.3.0"
|
27
|
+
gem.add_dependency "rack", ">= 2.2.4"
|
28
|
+
gem.add_dependency "concurrent-ruby", "< 2"
|
29
|
+
gem.post_install_message = <<~EOM
|
30
|
+
|
31
|
+
####################################################
|
32
|
+
|
33
|
+
|
34
|
+
█████████ █████ ██████████ ██████████ █████ ████ █████ ██████ ██████████ █████
|
35
|
+
███░░░░░███░░███ ░░███░░░░███ ░░███░░░░░█░░███ ███░ ░░███ ███░░░░███ ░███░░░░███ ███░░░███
|
36
|
+
░███ ░░░ ░███ ░███ ░░███ ░███ █ ░ ░███ ███ ░███ ███ ░░███ ░░░ ███ ███ ░░███
|
37
|
+
░░█████████ ░███ ░███ ░███ ░██████ ░███████ ░███ ░███ ░███ ███ ░███ ░███
|
38
|
+
░░░░░░░░███ ░███ ░███ ░███ ░███░░█ ░███░░███ ░███ ░███ ██░███ ███ ░███ ░███
|
39
|
+
███ ░███ ░███ ░███ ███ ░███ ░ █ ░███ ░░███ ░███ ░░███ ░░████ ███ ░░███ ███
|
40
|
+
░░█████████ █████ ██████████ ██████████ █████ ░░████ █████ ░░░██████░██ ███ ██ ░░░█████░
|
41
|
+
░░░░░░░░░ ░░░░░ ░░░░░░░░░░ ░░░░░░░░░░ ░░░░░ ░░░░ ░░░░░ ░░░░░░ ░░ ░░░ ░░ ░░░░░░
|
42
|
+
|
43
|
+
|
44
|
+
WARNING: This is a beta release, expect breakage!
|
45
|
+
|
46
|
+
1. Use `gem 'sidekiq', '<7'` in your Gemfile if you don't want to be a beta tester.
|
47
|
+
2. Read the release notes at https://github.com/mperham/sidekiq/blob/7-0/docs/7.0-Upgrade.md
|
48
|
+
3. Search for open/closed issues at https://github.com/mperham/sidekiq/issues/
|
49
|
+
|
50
|
+
####################################################
|
51
|
+
EOM
|
28
52
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
2
|
+
Chart.defaults.borderColor = "#333";
|
3
|
+
Chart.defaults.color = "#aaa";
|
4
|
+
}
|
5
|
+
|
6
|
+
class Colors {
|
7
|
+
constructor() {
|
8
|
+
this.assignments = {};
|
9
|
+
this.success = "#006f68";
|
10
|
+
this.failure = "#af0014";
|
11
|
+
this.fallback = "#999";
|
12
|
+
this.primary = "#537bc4";
|
13
|
+
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",
|
25
|
+
];
|
26
|
+
}
|
27
|
+
|
28
|
+
checkOut(assignee) {
|
29
|
+
const color =
|
30
|
+
this.assignments[assignee] || this.available.shift() || this.fallback;
|
31
|
+
this.assignments[assignee] = color;
|
32
|
+
return color;
|
33
|
+
}
|
34
|
+
|
35
|
+
checkIn(assignee) {
|
36
|
+
const color = this.assignments[assignee];
|
37
|
+
delete this.assignments[assignee];
|
38
|
+
|
39
|
+
if (color && color != this.fallback) {
|
40
|
+
this.available.unshift(color);
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
class BaseChart {
|
46
|
+
constructor(el, options) {
|
47
|
+
this.el = el;
|
48
|
+
this.options = options;
|
49
|
+
this.colors = new Colors();
|
50
|
+
}
|
51
|
+
|
52
|
+
init() {
|
53
|
+
this.chart = new Chart(this.el, {
|
54
|
+
type: this.options.chartType,
|
55
|
+
data: { labels: this.options.labels, datasets: this.datasets },
|
56
|
+
options: this.chartOptions,
|
57
|
+
});
|
58
|
+
}
|
59
|
+
|
60
|
+
update() {
|
61
|
+
this.chart.options = this.chartOptions;
|
62
|
+
this.chart.update();
|
63
|
+
}
|
64
|
+
|
65
|
+
get chartOptions() {
|
66
|
+
let chartOptions = {
|
67
|
+
interaction: {
|
68
|
+
mode: "nearest",
|
69
|
+
axis: "x",
|
70
|
+
intersect: false,
|
71
|
+
},
|
72
|
+
scales: {
|
73
|
+
x: {
|
74
|
+
ticks: {
|
75
|
+
autoSkipPadding: 10,
|
76
|
+
},
|
77
|
+
},
|
78
|
+
},
|
79
|
+
plugins: {
|
80
|
+
legend: {
|
81
|
+
display: false,
|
82
|
+
},
|
83
|
+
annotation: {
|
84
|
+
annotations: {},
|
85
|
+
},
|
86
|
+
tooltip: {
|
87
|
+
animation: false,
|
88
|
+
},
|
89
|
+
},
|
90
|
+
};
|
91
|
+
|
92
|
+
if (this.options.marks) {
|
93
|
+
this.options.marks.forEach(([bucket, label], i) => {
|
94
|
+
chartOptions.plugins.annotation.annotations[`deploy-${i}`] = {
|
95
|
+
type: "line",
|
96
|
+
xMin: bucket,
|
97
|
+
xMax: bucket,
|
98
|
+
borderColor: "rgba(220, 38, 38, 0.4)",
|
99
|
+
borderWidth: 2,
|
100
|
+
};
|
101
|
+
});
|
102
|
+
}
|
103
|
+
|
104
|
+
return chartOptions;
|
105
|
+
}
|
106
|
+
}
|