sidekiq 6.4.0 → 7.1.2
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 +232 -12
- data/README.md +44 -31
- data/bin/sidekiq +4 -9
- data/bin/sidekiqload +207 -117
- data/bin/sidekiqmon +4 -1
- data/lib/sidekiq/api.rb +329 -188
- data/lib/sidekiq/capsule.rb +127 -0
- data/lib/sidekiq/cli.rb +85 -81
- data/lib/sidekiq/client.rb +98 -58
- data/lib/sidekiq/component.rb +68 -0
- data/lib/sidekiq/config.rb +278 -0
- data/lib/sidekiq/deploy.rb +62 -0
- data/lib/sidekiq/embedded.rb +61 -0
- data/lib/sidekiq/fetch.rb +23 -24
- data/lib/sidekiq/job.rb +371 -10
- data/lib/sidekiq/job_logger.rb +16 -28
- data/lib/sidekiq/job_retry.rb +80 -56
- data/lib/sidekiq/job_util.rb +60 -20
- data/lib/sidekiq/launcher.rb +103 -95
- data/lib/sidekiq/logger.rb +9 -44
- data/lib/sidekiq/manager.rb +33 -32
- data/lib/sidekiq/metrics/query.rb +153 -0
- data/lib/sidekiq/metrics/shared.rb +95 -0
- data/lib/sidekiq/metrics/tracking.rb +136 -0
- data/lib/sidekiq/middleware/chain.rb +96 -51
- data/lib/sidekiq/middleware/current_attributes.rb +58 -20
- data/lib/sidekiq/middleware/i18n.rb +6 -4
- data/lib/sidekiq/middleware/modules.rb +21 -0
- data/lib/sidekiq/monitor.rb +17 -4
- data/lib/sidekiq/paginator.rb +17 -9
- data/lib/sidekiq/processor.rb +60 -60
- data/lib/sidekiq/rails.rb +22 -10
- data/lib/sidekiq/redis_client_adapter.rb +96 -0
- data/lib/sidekiq/redis_connection.rb +13 -82
- data/lib/sidekiq/ring_buffer.rb +29 -0
- data/lib/sidekiq/scheduled.rb +66 -38
- data/lib/sidekiq/testing/inline.rb +4 -4
- data/lib/sidekiq/testing.rb +41 -68
- data/lib/sidekiq/transaction_aware_client.rb +44 -0
- data/lib/sidekiq/version.rb +2 -1
- data/lib/sidekiq/web/action.rb +3 -3
- data/lib/sidekiq/web/application.rb +40 -9
- data/lib/sidekiq/web/csrf_protection.rb +3 -3
- data/lib/sidekiq/web/helpers.rb +35 -21
- data/lib/sidekiq/web.rb +10 -17
- data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
- data/lib/sidekiq.rb +84 -206
- data/sidekiq.gemspec +12 -10
- data/web/assets/javascripts/application.js +76 -26
- data/web/assets/javascripts/base-charts.js +106 -0
- data/web/assets/javascripts/chart.min.js +13 -0
- data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
- data/web/assets/javascripts/dashboard-charts.js +166 -0
- data/web/assets/javascripts/dashboard.js +3 -240
- data/web/assets/javascripts/metrics.js +264 -0
- data/web/assets/stylesheets/application-dark.css +4 -0
- data/web/assets/stylesheets/application-rtl.css +2 -91
- data/web/assets/stylesheets/application.css +66 -297
- data/web/locales/ar.yml +70 -70
- data/web/locales/cs.yml +62 -62
- data/web/locales/da.yml +60 -53
- data/web/locales/de.yml +65 -65
- data/web/locales/el.yml +43 -24
- data/web/locales/en.yml +82 -69
- data/web/locales/es.yml +68 -68
- data/web/locales/fa.yml +65 -65
- data/web/locales/fr.yml +81 -67
- data/web/locales/gd.yml +99 -0
- 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 +73 -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 +63 -55
- 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 +43 -16
- data/web/locales/zh-tw.yml +42 -8
- data/web/views/_footer.erb +5 -2
- data/web/views/_job_info.erb +18 -2
- data/web/views/_metrics_period_select.erb +12 -0
- data/web/views/_nav.erb +1 -1
- data/web/views/_paging.erb +2 -0
- data/web/views/_poll_link.erb +1 -1
- data/web/views/_summary.erb +1 -1
- data/web/views/busy.erb +44 -28
- data/web/views/dashboard.erb +36 -4
- data/web/views/metrics.erb +82 -0
- data/web/views/metrics_for_job.erb +68 -0
- data/web/views/morgue.erb +5 -9
- data/web/views/queue.erb +15 -15
- data/web/views/queues.erb +3 -1
- data/web/views/retries.erb +5 -9
- data/web/views/scheduled.erb +12 -13
- metadata +56 -27
- data/lib/sidekiq/delay.rb +0 -43
- data/lib/sidekiq/exception_handler.rb +0 -27
- 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/util.rb +0 -108
- data/lib/sidekiq/worker.rb +0 -364
- /data/{LICENSE → LICENSE.txt} +0 -0
data/lib/sidekiq.rb
CHANGED
@@ -1,14 +1,39 @@
|
|
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
|
+
begin
|
7
|
+
require "sidekiq-ent/version"
|
8
|
+
fail <<~EOM if Gem::Version.new(Sidekiq::Enterprise::VERSION).segments[0] != Sidekiq::MAJOR
|
9
|
+
|
10
|
+
Sidekiq Enterprise #{Sidekiq::Enterprise::VERSION} does not work with Sidekiq #{Sidekiq::VERSION}.
|
11
|
+
Starting with Sidekiq 7, major versions are synchronized so Sidekiq Enterprise 7 works with Sidekiq 7.
|
12
|
+
Use `bundle up sidekiq-ent` to upgrade.
|
13
|
+
|
14
|
+
EOM
|
15
|
+
rescue LoadError
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
require "sidekiq/pro/version"
|
20
|
+
fail <<~EOM if Gem::Version.new(Sidekiq::Pro::VERSION).segments[0] != Sidekiq::MAJOR
|
21
|
+
|
22
|
+
Sidekiq Pro #{Sidekiq::Pro::VERSION} does not work with Sidekiq #{Sidekiq::VERSION}.
|
23
|
+
Starting with Sidekiq 7, major versions are synchronized so Sidekiq Pro 7 works with Sidekiq 7.
|
24
|
+
Use `bundle up sidekiq-pro` to upgrade.
|
25
|
+
|
26
|
+
EOM
|
27
|
+
rescue LoadError
|
28
|
+
end
|
29
|
+
|
30
|
+
require "sidekiq/config"
|
6
31
|
require "sidekiq/logger"
|
7
32
|
require "sidekiq/client"
|
8
|
-
require "sidekiq/
|
33
|
+
require "sidekiq/transaction_aware_client"
|
9
34
|
require "sidekiq/job"
|
10
|
-
require "sidekiq/
|
11
|
-
require "sidekiq/
|
35
|
+
require "sidekiq/worker_compatibility_alias"
|
36
|
+
require "sidekiq/redis_client_adapter"
|
12
37
|
|
13
38
|
require "json"
|
14
39
|
|
@@ -16,253 +41,106 @@ module Sidekiq
|
|
16
41
|
NAME = "Sidekiq"
|
17
42
|
LICENSE = "See LICENSE and the LGPL-3.0 for licensing details."
|
18
43
|
|
19
|
-
DEFAULTS = {
|
20
|
-
queues: [],
|
21
|
-
labels: [],
|
22
|
-
concurrency: 10,
|
23
|
-
require: ".",
|
24
|
-
strict: true,
|
25
|
-
environment: nil,
|
26
|
-
timeout: 25,
|
27
|
-
poll_interval_average: nil,
|
28
|
-
average_scheduled_poll_interval: 5,
|
29
|
-
on_complex_arguments: :warn,
|
30
|
-
error_handlers: [],
|
31
|
-
death_handlers: [],
|
32
|
-
lifecycle_events: {
|
33
|
-
startup: [],
|
34
|
-
quiet: [],
|
35
|
-
shutdown: [],
|
36
|
-
heartbeat: []
|
37
|
-
},
|
38
|
-
dead_max_jobs: 10_000,
|
39
|
-
dead_timeout_in_seconds: 180 * 24 * 60 * 60, # 6 months
|
40
|
-
reloader: proc { |&block| block.call }
|
41
|
-
}
|
42
|
-
|
43
|
-
DEFAULT_WORKER_OPTIONS = {
|
44
|
-
"retry" => true,
|
45
|
-
"queue" => "default"
|
46
|
-
}
|
47
|
-
|
48
|
-
FAKE_INFO = {
|
49
|
-
"redis_version" => "9.9.9",
|
50
|
-
"uptime_in_days" => "9999",
|
51
|
-
"connected_clients" => "9999",
|
52
|
-
"used_memory_human" => "9P",
|
53
|
-
"used_memory_peak_human" => "9P"
|
54
|
-
}
|
55
|
-
|
56
44
|
def self.❨╯°□°❩╯︵┻━┻
|
57
|
-
puts "
|
58
|
-
end
|
59
|
-
|
60
|
-
def self.options
|
61
|
-
@options ||= DEFAULTS.dup
|
62
|
-
end
|
63
|
-
|
64
|
-
def self.options=(opts)
|
65
|
-
@options = opts
|
66
|
-
end
|
67
|
-
|
68
|
-
##
|
69
|
-
# Configuration for Sidekiq server, use like:
|
70
|
-
#
|
71
|
-
# Sidekiq.configure_server do |config|
|
72
|
-
# config.redis = { :namespace => 'myapp', :size => 25, :url => 'redis://myhost:8877/0' }
|
73
|
-
# config.server_middleware do |chain|
|
74
|
-
# chain.add MyServerHook
|
75
|
-
# end
|
76
|
-
# end
|
77
|
-
def self.configure_server
|
78
|
-
yield self if server?
|
79
|
-
end
|
80
|
-
|
81
|
-
##
|
82
|
-
# Configuration for Sidekiq client, use like:
|
83
|
-
#
|
84
|
-
# Sidekiq.configure_client do |config|
|
85
|
-
# config.redis = { :namespace => 'myapp', :size => 1, :url => 'redis://myhost:8877/0' }
|
86
|
-
# end
|
87
|
-
def self.configure_client
|
88
|
-
yield self unless server?
|
45
|
+
puts "Take a deep breath and count to ten..."
|
89
46
|
end
|
90
47
|
|
91
48
|
def self.server?
|
92
49
|
defined?(Sidekiq::CLI)
|
93
50
|
end
|
94
51
|
|
95
|
-
def self.
|
96
|
-
|
97
|
-
redis_pool.with do |conn|
|
98
|
-
retryable = true
|
99
|
-
begin
|
100
|
-
yield conn
|
101
|
-
rescue Redis::BaseError => ex
|
102
|
-
# 2550 Failover can cause the server to become a replica, need
|
103
|
-
# to disconnect and reopen the socket to get back to the primary.
|
104
|
-
# 4495 Use the same logic if we have a "Not enough replicas" error from the primary
|
105
|
-
# 4985 Use the same logic when a blocking command is force-unblocked
|
106
|
-
if retryable && ex.message =~ /READONLY|NOREPLICAS|UNBLOCKED/
|
107
|
-
conn.disconnect!
|
108
|
-
retryable = false
|
109
|
-
retry
|
110
|
-
end
|
111
|
-
raise
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def self.redis_info
|
117
|
-
redis do |conn|
|
118
|
-
# admin commands can't go through redis-namespace starting
|
119
|
-
# in redis-namespace 2.0
|
120
|
-
if conn.respond_to?(:namespace)
|
121
|
-
conn.redis.info
|
122
|
-
else
|
123
|
-
conn.info
|
124
|
-
end
|
125
|
-
rescue Redis::CommandError => ex
|
126
|
-
# 2850 return fake version when INFO command has (probably) been renamed
|
127
|
-
raise unless /unknown command/.match?(ex.message)
|
128
|
-
FAKE_INFO
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
def self.redis_pool
|
133
|
-
@redis ||= Sidekiq::RedisConnection.create
|
134
|
-
end
|
135
|
-
|
136
|
-
def self.redis=(hash)
|
137
|
-
@redis = if hash.is_a?(ConnectionPool)
|
138
|
-
hash
|
139
|
-
else
|
140
|
-
Sidekiq::RedisConnection.create(hash)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
def self.client_middleware
|
145
|
-
@client_chain ||= Middleware::Chain.new
|
146
|
-
yield @client_chain if block_given?
|
147
|
-
@client_chain
|
52
|
+
def self.load_json(string)
|
53
|
+
JSON.parse(string)
|
148
54
|
end
|
149
55
|
|
150
|
-
def self.
|
151
|
-
|
152
|
-
yield @server_chain if block_given?
|
153
|
-
@server_chain
|
56
|
+
def self.dump_json(object)
|
57
|
+
JSON.generate(object)
|
154
58
|
end
|
155
59
|
|
156
|
-
def self.
|
157
|
-
|
60
|
+
def self.pro?
|
61
|
+
defined?(Sidekiq::Pro)
|
158
62
|
end
|
159
63
|
|
160
|
-
def self.
|
161
|
-
|
162
|
-
@default_worker_options = default_worker_options.merge(hash.transform_keys(&:to_s))
|
64
|
+
def self.ent?
|
65
|
+
defined?(Sidekiq::Enterprise)
|
163
66
|
end
|
164
67
|
|
165
|
-
def self.
|
166
|
-
|
68
|
+
def self.redis_pool
|
69
|
+
(Thread.current[:sidekiq_capsule] || default_configuration).redis_pool
|
167
70
|
end
|
168
71
|
|
169
|
-
|
170
|
-
|
171
|
-
# the job dies. It's the notification to your application
|
172
|
-
# that this job will not succeed without manual intervention.
|
173
|
-
#
|
174
|
-
# Sidekiq.configure_server do |config|
|
175
|
-
# config.death_handlers << ->(job, ex) do
|
176
|
-
# end
|
177
|
-
# end
|
178
|
-
def self.death_handlers
|
179
|
-
options[:death_handlers]
|
72
|
+
def self.redis(&block)
|
73
|
+
(Thread.current[:sidekiq_capsule] || default_configuration).redis(&block)
|
180
74
|
end
|
181
75
|
|
182
|
-
def self.
|
183
|
-
|
76
|
+
def self.strict_args!(mode = :raise)
|
77
|
+
Sidekiq::Config::DEFAULTS[:on_complex_arguments] = mode
|
184
78
|
end
|
185
79
|
|
186
|
-
def self.
|
187
|
-
|
80
|
+
def self.default_job_options=(hash)
|
81
|
+
@default_job_options = default_job_options.merge(hash.transform_keys(&:to_s))
|
188
82
|
end
|
189
83
|
|
190
|
-
def self.
|
191
|
-
@
|
192
|
-
Sidekiq::Logger::Formatters::WithoutTimestamp.new
|
193
|
-
else
|
194
|
-
Sidekiq::Logger::Formatters::Pretty.new
|
195
|
-
end
|
84
|
+
def self.default_job_options
|
85
|
+
@default_job_options ||= {"retry" => true, "queue" => "default"}
|
196
86
|
end
|
197
87
|
|
198
|
-
def self.
|
199
|
-
@
|
200
|
-
logger.formatter = log_formatter
|
88
|
+
def self.default_configuration
|
89
|
+
@config ||= Sidekiq::Config.new
|
201
90
|
end
|
202
91
|
|
203
92
|
def self.logger
|
204
|
-
|
205
|
-
end
|
206
|
-
|
207
|
-
def self.logger=(logger)
|
208
|
-
if logger.nil?
|
209
|
-
self.logger.level = Logger::FATAL
|
210
|
-
return self.logger
|
211
|
-
end
|
212
|
-
|
213
|
-
logger.extend(Sidekiq::LoggingUtils)
|
214
|
-
|
215
|
-
@logger = logger
|
93
|
+
default_configuration.logger
|
216
94
|
end
|
217
95
|
|
218
|
-
def self.
|
219
|
-
|
96
|
+
def self.configure_server(&block)
|
97
|
+
(@config_blocks ||= []) << block
|
98
|
+
yield default_configuration if server?
|
220
99
|
end
|
221
100
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
#
|
226
|
-
# See sidekiq/scheduled.rb for an in-depth explanation of this value
|
227
|
-
def self.average_scheduled_poll_interval=(interval)
|
228
|
-
options[:average_scheduled_poll_interval] = interval
|
101
|
+
def self.freeze!
|
102
|
+
@frozen = true
|
103
|
+
@config_blocks = nil
|
229
104
|
end
|
230
105
|
|
231
|
-
#
|
106
|
+
# Creates a Sidekiq::Config instance that is more tuned for embedding
|
107
|
+
# within an arbitrary Ruby process. Notably it reduces concurrency by
|
108
|
+
# default so there is less contention for CPU time with other threads.
|
232
109
|
#
|
233
|
-
# Sidekiq.
|
234
|
-
# config.
|
110
|
+
# inst = Sidekiq.configure_embed do |config|
|
111
|
+
# config.queues = %w[critical default low]
|
235
112
|
# end
|
113
|
+
# inst.run
|
114
|
+
# sleep 10
|
115
|
+
# inst.terminate
|
236
116
|
#
|
237
|
-
#
|
238
|
-
|
239
|
-
options[:error_handlers]
|
240
|
-
end
|
241
|
-
|
242
|
-
# Register a block to run at a point in the Sidekiq lifecycle.
|
243
|
-
# :startup, :quiet or :shutdown are valid events.
|
117
|
+
# NB: it is really easy to overload a Ruby process with threads due to the GIL.
|
118
|
+
# I do not recommend setting concurrency higher than 2-3.
|
244
119
|
#
|
245
|
-
#
|
246
|
-
#
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
120
|
+
# NB: Sidekiq only supports one instance in memory. You will get undefined behavior
|
121
|
+
# if you try to embed Sidekiq twice in the same process.
|
122
|
+
def self.configure_embed(&block)
|
123
|
+
raise "Sidekiq global configuration is frozen, you must create all embedded instances BEFORE calling `run`" if @frozen
|
124
|
+
|
125
|
+
require "sidekiq/embedded"
|
126
|
+
cfg = default_configuration
|
127
|
+
cfg.concurrency = 2
|
128
|
+
@config_blocks&.each { |block| block.call(cfg) }
|
129
|
+
yield cfg
|
130
|
+
|
131
|
+
Sidekiq::Embedded.new(cfg)
|
254
132
|
end
|
255
133
|
|
256
|
-
def self.
|
257
|
-
|
134
|
+
def self.configure_client
|
135
|
+
yield default_configuration unless server?
|
258
136
|
end
|
259
137
|
|
260
|
-
# We are shutting down Sidekiq but what about
|
138
|
+
# We are shutting down Sidekiq but what about threads that
|
261
139
|
# are working on some long job? This error is
|
262
|
-
# raised in
|
140
|
+
# raised in jobs that have not finished within the hard
|
263
141
|
# timeout limit. This is needed to rollback db transactions,
|
264
142
|
# otherwise Ruby's Thread#kill will commit. See #377.
|
265
|
-
# DO NOT RESCUE THIS ERROR IN YOUR
|
143
|
+
# DO NOT RESCUE THIS ERROR IN YOUR JOBS
|
266
144
|
class Shutdown < Interrupt; end
|
267
145
|
end
|
268
146
|
|
data/sidekiq.gemspec
CHANGED
@@ -2,27 +2,29 @@ require_relative "lib/sidekiq/version"
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |gem|
|
4
4
|
gem.authors = ["Mike Perham"]
|
5
|
-
gem.email = ["
|
5
|
+
gem.email = ["info@contribsys.com"]
|
6
6
|
gem.summary = "Simple, efficient background processing for Ruby"
|
7
7
|
gem.description = "Simple, efficient background processing for Ruby."
|
8
8
|
gem.homepage = "https://sidekiq.org"
|
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",
|
19
|
-
"bug_tracker_uri" => "https://github.com/
|
20
|
-
"documentation_uri" => "https://github.com/
|
21
|
-
"changelog_uri" => "https://github.com/
|
22
|
-
"source_code_uri" => "https://github.com/
|
19
|
+
"bug_tracker_uri" => "https://github.com/sidekiq/sidekiq/issues",
|
20
|
+
"documentation_uri" => "https://github.com/sidekiq/sidekiq/wiki",
|
21
|
+
"changelog_uri" => "https://github.com/sidekiq/sidekiq/blob/main/Changes.md",
|
22
|
+
"source_code_uri" => "https://github.com/sidekiq/sidekiq",
|
23
|
+
"rubygems_mfa_required" => "true"
|
23
24
|
}
|
24
25
|
|
25
|
-
gem.add_dependency "redis", ">=
|
26
|
-
gem.add_dependency "connection_pool", ">= 2.
|
27
|
-
gem.add_dependency "rack", "
|
26
|
+
gem.add_dependency "redis-client", ">= 0.14.0"
|
27
|
+
gem.add_dependency "connection_pool", ">= 2.3.0"
|
28
|
+
gem.add_dependency "rack", ">= 2.2.4"
|
29
|
+
gem.add_dependency "concurrent-ruby", "< 2"
|
28
30
|
end
|
@@ -9,7 +9,9 @@ var ready = (callback) => {
|
|
9
9
|
else document.addEventListener("DOMContentLoaded", callback);
|
10
10
|
}
|
11
11
|
|
12
|
-
ready(
|
12
|
+
ready(addListeners)
|
13
|
+
|
14
|
+
function addListeners() {
|
13
15
|
document.querySelectorAll(".check_all").forEach(node => {
|
14
16
|
node.addEventListener("click", event => {
|
15
17
|
node.closest('table').querySelectorAll('input[type=checkbox]').forEach(inp => { inp.checked = !!node.checked; });
|
@@ -26,42 +28,66 @@ ready(() => {
|
|
26
28
|
})
|
27
29
|
|
28
30
|
document.querySelectorAll("[data-toggle]").forEach(node => {
|
29
|
-
node.addEventListener("click",
|
30
|
-
var targName = node.getAttribute("data-toggle");
|
31
|
-
var full = document.getElementById(targName + "_full");
|
32
|
-
if (full.style.display == "block") {
|
33
|
-
full.style.display = 'none';
|
34
|
-
} else {
|
35
|
-
full.style.display = 'block';
|
36
|
-
}
|
37
|
-
})
|
31
|
+
node.addEventListener("click", addDataToggleListeners)
|
38
32
|
})
|
39
33
|
|
34
|
+
addShiftClickListeners()
|
40
35
|
updateFuzzyTimes();
|
36
|
+
setLivePollFromUrl();
|
41
37
|
|
42
38
|
var buttons = document.querySelectorAll(".live-poll");
|
43
39
|
if (buttons.length > 0) {
|
44
40
|
buttons.forEach(node => {
|
45
|
-
node.addEventListener("click",
|
46
|
-
if (localStorage.sidekiqLivePoll == "enabled") {
|
47
|
-
localStorage.sidekiqLivePoll = "disabled";
|
48
|
-
clearTimeout(livePollTimer);
|
49
|
-
livePollTimer = null;
|
50
|
-
} else {
|
51
|
-
localStorage.sidekiqLivePoll = "enabled";
|
52
|
-
livePollCallback();
|
53
|
-
}
|
54
|
-
|
55
|
-
updateLivePollButton();
|
56
|
-
})
|
41
|
+
node.addEventListener("click", addPollingListeners)
|
57
42
|
});
|
58
43
|
|
59
44
|
updateLivePollButton();
|
60
|
-
if (localStorage.sidekiqLivePoll == "enabled") {
|
45
|
+
if (localStorage.sidekiqLivePoll == "enabled" && !livePollTimer) {
|
61
46
|
scheduleLivePoll();
|
62
47
|
}
|
63
48
|
}
|
64
|
-
}
|
49
|
+
}
|
50
|
+
|
51
|
+
function addPollingListeners(_event) {
|
52
|
+
if (localStorage.sidekiqLivePoll == "enabled") {
|
53
|
+
localStorage.sidekiqLivePoll = "disabled";
|
54
|
+
clearTimeout(livePollTimer);
|
55
|
+
livePollTimer = null;
|
56
|
+
} else {
|
57
|
+
localStorage.sidekiqLivePoll = "enabled";
|
58
|
+
livePollCallback();
|
59
|
+
}
|
60
|
+
|
61
|
+
updateLivePollButton();
|
62
|
+
}
|
63
|
+
|
64
|
+
function addDataToggleListeners(event) {
|
65
|
+
var source = event.target || event.srcElement;
|
66
|
+
var targName = source.getAttribute("data-toggle");
|
67
|
+
var full = document.getElementById(targName);
|
68
|
+
if (full.style.display == "block") {
|
69
|
+
full.style.display = 'none';
|
70
|
+
} else {
|
71
|
+
full.style.display = 'block';
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
function addShiftClickListeners() {
|
76
|
+
let checkboxes = Array.from(document.querySelectorAll(".shift_clickable"));
|
77
|
+
let lastChecked = null;
|
78
|
+
checkboxes.forEach(checkbox => {
|
79
|
+
checkbox.addEventListener("click", (e) => {
|
80
|
+
if (e.shiftKey && lastChecked) {
|
81
|
+
let myIndex = checkboxes.indexOf(checkbox);
|
82
|
+
let lastIndex = checkboxes.indexOf(lastChecked);
|
83
|
+
let [min, max] = [myIndex, lastIndex].sort();
|
84
|
+
let newState = checkbox.checked;
|
85
|
+
checkboxes.slice(min, max).forEach(c => c.checked = newState);
|
86
|
+
}
|
87
|
+
lastChecked = checkbox;
|
88
|
+
});
|
89
|
+
});
|
90
|
+
}
|
65
91
|
|
66
92
|
function updateFuzzyTimes() {
|
67
93
|
var locale = document.body.getAttribute("data-locale");
|
@@ -76,6 +102,14 @@ function updateFuzzyTimes() {
|
|
76
102
|
t.cancel();
|
77
103
|
}
|
78
104
|
|
105
|
+
function setLivePollFromUrl() {
|
106
|
+
var url_params = new URL(window.location.href).searchParams
|
107
|
+
|
108
|
+
if (url_params.get("poll") == "true") {
|
109
|
+
localStorage.sidekiqLivePoll = "enabled";
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
79
113
|
function updateLivePollButton() {
|
80
114
|
if (localStorage.sidekiqLivePoll == "enabled") {
|
81
115
|
document.querySelectorAll('.live-poll-stop').forEach(box => { box.style.display = "inline-block" })
|
@@ -89,7 +123,19 @@ function updateLivePollButton() {
|
|
89
123
|
function livePollCallback() {
|
90
124
|
clearTimeout(livePollTimer);
|
91
125
|
|
92
|
-
fetch(window.location.href)
|
126
|
+
fetch(window.location.href)
|
127
|
+
.then(checkResponse)
|
128
|
+
.then(resp => resp.text())
|
129
|
+
.then(replacePage)
|
130
|
+
.catch(showError)
|
131
|
+
.finally(scheduleLivePoll)
|
132
|
+
}
|
133
|
+
|
134
|
+
function checkResponse(resp) {
|
135
|
+
if (!resp.ok) {
|
136
|
+
throw response.error();
|
137
|
+
}
|
138
|
+
return resp
|
93
139
|
}
|
94
140
|
|
95
141
|
function scheduleLivePoll() {
|
@@ -107,5 +153,9 @@ function replacePage(text) {
|
|
107
153
|
var header_status = doc.querySelector('.status')
|
108
154
|
document.querySelector('.status').replaceWith(header_status)
|
109
155
|
|
110
|
-
|
156
|
+
addListeners();
|
157
|
+
}
|
158
|
+
|
159
|
+
function showError(error) {
|
160
|
+
console.error(error)
|
111
161
|
}
|
@@ -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
|
+
}
|