sidekiq 8.1.0 → 8.1.2

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.
@@ -0,0 +1,95 @@
1
+ require_relative "base_tab"
2
+
3
+ module Sidekiq
4
+ class TUI
5
+ module Tabs
6
+ class Queues < BaseTab
7
+ def features
8
+ %i[selectable]
9
+ end
10
+
11
+ def controls
12
+ @controls ||= super + [
13
+ {code: "D", modifiers: ["shift"], display: "D", description: "Delete",
14
+ action: ->(tui, tab) { tab.delete_queue! }, refresh: true},
15
+ {code: "p", description: "Pause/Unpause Queue",
16
+ action: ->(tui, tab) { tab.toggle_pause_queue! }}
17
+ ]
18
+ end
19
+
20
+ def delete_queue!
21
+ each_selection do |qname|
22
+ Sidekiq::Queue.new(qname).clear
23
+ end
24
+ end
25
+
26
+ def toggle_pause_queue!
27
+ return unless Sidekiq.pro?
28
+
29
+ each_selection do |qname|
30
+ queue = Sidekiq::Queue.new(qname)
31
+ if queue.paused?
32
+ queue.unpause!
33
+ else
34
+ queue.pause!
35
+ end
36
+ end
37
+ end
38
+
39
+ def refresh_data
40
+ refresh_data_for_stats
41
+
42
+ queue_summaries = Sidekiq::Stats.new.queue_summaries.sort_by(&:name)
43
+
44
+ selected = Array(@data[:selected])
45
+ queues = queue_summaries.map { |queue_summary|
46
+ row_cells = [
47
+ selected.index(queue_summary.name) ? "✅" : "",
48
+ queue_summary.name,
49
+ queue_summary.size.to_s,
50
+ number_with_delimiter(queue_summary.latency, {precision: 2})
51
+ ]
52
+ row_cells << (queue_summary.paused? ? "✅" : "") if Sidekiq.pro?
53
+ row_cells
54
+ }
55
+
56
+ table_row_ids = queue_summaries.map(&:name)
57
+
58
+ @data[:queues] = queues
59
+ @data[:table] = {row_ids: table_row_ids}
60
+ end
61
+
62
+ def render(tui, frame, area)
63
+ header = ["☑️", "Queue", "Size", "Latency"].map { |x| t(x) }
64
+ header << "Paused?" if Sidekiq.pro?
65
+
66
+ chunks = tui.layout_split(
67
+ area,
68
+ direction: :vertical,
69
+ constraints: [
70
+ tui.constraint_length(4), # Stats
71
+ tui.constraint_fill(1) # Table
72
+ ]
73
+ )
74
+
75
+ render_stats_section(tui, frame, chunks[0])
76
+ render_table(tui, frame, chunks[1]) do
77
+ {
78
+ title: t(name),
79
+ header:,
80
+ widths: header.map.with_index { |_, idx|
81
+ tui.constraint_length((idx == 1) ? 60 : 10)
82
+ },
83
+ rows: @data[:queues].map.with_index { |cells, idx|
84
+ tui.table_row(
85
+ cells:,
86
+ style: idx.even? ? nil : tui.style(bg: :dark_gray)
87
+ )
88
+ }
89
+ }
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "base_tab"
2
+ require_relative "set_tab"
3
+
4
+ module Sidekiq
5
+ class TUI
6
+ module Tabs
7
+ class Retries < BaseTab
8
+ include SetTab
9
+
10
+ def set_class = Sidekiq::RetrySet
11
+
12
+ def refresh_data
13
+ refresh_data_for_stats
14
+ refresh_data_for_set
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ require_relative "base_tab"
2
+ require_relative "set_tab"
3
+
4
+ module Sidekiq
5
+ class TUI
6
+ module Tabs
7
+ class Scheduled < BaseTab
8
+ include SetTab
9
+
10
+ def set_class = Sidekiq::ScheduledSet
11
+
12
+ def refresh_data
13
+ refresh_data_for_stats
14
+ refresh_data_for_set
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,96 @@
1
+ module Sidekiq
2
+ class TUI
3
+ module Tabs
4
+ module SetTab
5
+ include Sidekiq::Paginator
6
+ include Filtering
7
+
8
+ def features
9
+ %i[selectable pageable filterable]
10
+ end
11
+
12
+ def controls
13
+ @controls ||= super + [{code: "D", modifiers: ["shift"], display: "D", description: "Delete",
14
+ action: ->(tui, tab) { tab.alter_rows!(:delete) }, refresh: true},
15
+ {code: "R", modifiers: ["shift"], display: "R", description: "Retry",
16
+ action: ->(tui, tab) { tab.alter_rows!(:retry) }, refresh: true},
17
+ {code: "E", modifiers: ["shift"], display: "E", description: "Enqueue",
18
+ action: ->(tui, tab) { tab.alter_rows!(:add_to_queue) }, refresh: true},
19
+ {code: "K", modifiers: ["shift"], display: "K", description: "Kill",
20
+ action: ->(tui, tab) { tab.alter_rows!(:kill) }, refresh: true}]
21
+ end
22
+
23
+ def alter_rows!(action)
24
+ # log(to_s, @data[:selected])
25
+ set = set_class.new
26
+ each_selection do |id|
27
+ score, jid = id.split("|")
28
+ item = set.fetch(score, jid)&.first
29
+ item&.send(action)
30
+ end
31
+ end
32
+
33
+ def refresh_data_for_set
34
+ set = set_class.new
35
+ f = current_filter
36
+ pager, rows, current, total = if f && f.size > 2
37
+ rows = set.scan(f).to_a
38
+ sz = rows.size
39
+ [Sidekiq::TUI::PageOptions.new(1, sz), rows, 1, sz]
40
+ else
41
+ pager = @data.dig(:table, :pager) || Sidekiq::TUI::PageOptions.new(1, 25)
42
+ current, total, items = page(set.name, pager.page, pager.size)
43
+ rows = items.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) }
44
+ [pager, rows, current, total]
45
+ end
46
+
47
+ @data.merge!(
48
+ table: {pager:, rows:, current_page: current, total:,
49
+ next_page: (current * pager.size < total) ? pager.page + 1 : nil,
50
+ row_ids: rows.map { |job| [job.score, job["jid"]].join("|") }}
51
+ )
52
+ end
53
+
54
+ def render(tui, frame, area)
55
+ chunks = tui.layout_split(
56
+ area,
57
+ direction: :vertical,
58
+ constraints: [
59
+ tui.constraint_length(4), # Stats
60
+ tui.constraint_fill(1) # Table
61
+ ]
62
+ )
63
+
64
+ render_stats_section(tui, frame, chunks[0])
65
+ render_table(tui, frame, chunks[1]) do
66
+ {
67
+ title: t(name),
68
+ header: ["☑️", "When", "Queue", "Job", "Arguments"].map { |x| t(x) },
69
+ widths: [
70
+ tui.constraint_length(5),
71
+ tui.constraint_length(24),
72
+ tui.constraint_length(20),
73
+ tui.constraint_length(30),
74
+ tui.constraint_fill(1)
75
+ ]
76
+ }.tap do |h|
77
+ rows = @data[:table][:rows].map.with_index { |entry, idx|
78
+ tui.table_row(
79
+ cells: [
80
+ selected?(entry) ? "✅" : "",
81
+ entry.at,
82
+ entry.queue,
83
+ entry.display_class,
84
+ entry.display_args
85
+ ],
86
+ style: idx.even? ? nil : tui.style(bg: :dark_gray)
87
+ )
88
+ }
89
+ h[:rows] = rows
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,15 @@
1
+ require_relative "tabs/home"
2
+ require_relative "tabs/busy"
3
+ require_relative "tabs/queues"
4
+ require_relative "tabs/scheduled"
5
+ require_relative "tabs/retries"
6
+ require_relative "tabs/dead"
7
+ require_relative "tabs/metrics"
8
+
9
+ module Sidekiq
10
+ class TUI
11
+ module Tabs
12
+ All = Set.new([Home, Busy, Queues, Scheduled, Retries, Dead, Metrics])
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,380 @@
1
+ # https://sr.ht/~kerrick/ratatui_ruby/
2
+ # https://git.sr.ht/~kerrick/ratatui_ruby/tree/stable/item/examples/
3
+ gem "ratatui_ruby", ">=1.4.0"
4
+ require "ratatui_ruby"
5
+
6
+ RatatuiRuby.debug_mode! if !!ENV["DEBUG"]
7
+
8
+ require "sidekiq/api"
9
+ require "sidekiq/paginator"
10
+
11
+ require_relative "tui/filtering"
12
+ require_relative "tui/controls"
13
+ require_relative "tui/tabs"
14
+
15
+ module Sidekiq
16
+ class TUI
17
+ include Sidekiq::Component
18
+
19
+ PageOptions = Data.define(:page, :size)
20
+
21
+ REFRESH_INTERVAL_SECONDS = 2
22
+ LOCALE_DIRECTORIES = [File.expand_path("#{File.dirname(__FILE__)}/../../web/locales")]
23
+
24
+ # language is meant to be a locale code, e.g.
25
+ # LANG=en_US.utf-8
26
+ def initialize(cfg, language: ENV["LANG"] || "en")
27
+ @lang = language
28
+ @config = cfg
29
+ @base_style = nil
30
+ @last_refresh = Time.at(0)
31
+ @fps = Array.new(2) { 0 }
32
+ @previous_fps = 0
33
+ @showing = :main
34
+ end
35
+
36
+ def prepare(tui)
37
+ load_locale
38
+
39
+ @tui = tui
40
+ @highlight_style = @tui.style(fg: :light_red, modifiers: [:underlined])
41
+ @hotkey_style = @tui.style(modifiers: [:bold, :underlined])
42
+ # eager load tabs
43
+ all
44
+ end
45
+
46
+ def run_loop
47
+ # Must log to a file, terminal is now controlled by Ratatui
48
+ config.logger = Logger.new("tui.log")
49
+
50
+ loop do
51
+ refresh_data if should_refresh?
52
+ render
53
+ break if handle_input == :quit
54
+ end
55
+ end
56
+
57
+ def render
58
+ track_fps do
59
+ if @showing == :main
60
+ @tui.draw do |frame|
61
+ main_area, controls_area = @tui.layout_split(
62
+ frame.area,
63
+ direction: :vertical,
64
+ constraints: [
65
+ @tui.constraint_fill(1),
66
+ @tui.constraint_length(5)
67
+ ]
68
+ )
69
+
70
+ # Split main area into tabs and content
71
+ tabs_area, content_area = @tui.layout_split(
72
+ main_area,
73
+ direction: :vertical,
74
+ constraints: [
75
+ @tui.constraint_length(3),
76
+ @tui.constraint_fill(1)
77
+ ]
78
+ )
79
+
80
+ all_tabs = all
81
+ tabs = @tui.tabs(
82
+ titles: all_tabs.map { |tab| t(tab.name) },
83
+ selected_index: all_tabs.index(current_tab),
84
+ block: @tui.block(title: " #{Sidekiq::NAME}", borders: [:all], title_style: @tui.style(fg: :light_red, modifiers: [:bold])),
85
+ divider: " | ",
86
+ highlight_style: @highlight_style,
87
+ style: @base_style
88
+ )
89
+ frame.render_widget(tabs, tabs_area)
90
+
91
+ render_content_area(frame, content_area)
92
+ render_controls(frame, controls_area)
93
+ end
94
+ end
95
+
96
+ if @showing == :help
97
+ @tui.draw do |frame|
98
+ main_area, controls_area = @tui.layout_split(
99
+ frame.area,
100
+ direction: :vertical,
101
+ constraints: [
102
+ @tui.constraint_fill(1),
103
+ @tui.constraint_length(4)
104
+ ]
105
+ )
106
+ content = @tui.block(
107
+ title: " #{Sidekiq::NAME} ",
108
+ borders: [:all],
109
+ title_style: @tui.style(fg: :light_red, modifiers: [:bold]),
110
+ children: [
111
+ # TODO convert to table
112
+ @tui.paragraph(
113
+ text: [
114
+ @tui.text_line(spans: ["Welcome to the Sidekiq Terminal UI"], alignment: :center),
115
+ @tui.text_line(spans: [
116
+ @tui.text_span(content: "Global hotkeys")
117
+ ]),
118
+ @tui.text_line(spans: []),
119
+ @tui.text_line(spans: [
120
+ @tui.text_span(content: "Esc", style: @hotkey_style),
121
+ @tui.text_span(content: ": Close this window")
122
+ ]),
123
+ @tui.text_line(spans: [
124
+ @tui.text_span(content: "←/→", style: @hotkey_style),
125
+ @tui.text_span(content: ": Move between tabs")
126
+ ]),
127
+ @tui.text_line(spans: [
128
+ @tui.text_span(content: "h/l", style: @hotkey_style),
129
+ @tui.text_span(content: ": Move to prev/next page of data")
130
+ ]),
131
+ @tui.text_line(spans: [
132
+ @tui.text_span(content: "j/k", style: @hotkey_style),
133
+ @tui.text_span(content: ": Move to prev/next row in current page")
134
+ ]),
135
+ @tui.text_line(spans: [
136
+ @tui.text_span(content: "x", style: @hotkey_style),
137
+ @tui.text_span(content: ": Select/deselect current row")
138
+ ]),
139
+ @tui.text_line(spans: [
140
+ @tui.text_span(content: "A", style: @hotkey_style),
141
+ @tui.text_span(content: ": Select/deselect All rows in current page")
142
+ ]),
143
+ @tui.text_line(spans: [
144
+ @tui.text_span(content: "q", style: @hotkey_style),
145
+ @tui.text_span(content: ": Quit")
146
+ ])
147
+ ]
148
+ )
149
+ ]
150
+ )
151
+ frame.render_widget(content, main_area)
152
+ controls = @tui.block(
153
+ title: t("Controls"),
154
+ borders: [:all],
155
+ children: [
156
+ @tui.paragraph(
157
+ text: [
158
+ @tui.text_line(spans: [
159
+ @tui.text_span(content: "Esc", style: @hotkey_style),
160
+ @tui.text_span(content: ": Close ")
161
+ ])
162
+ ]
163
+ )
164
+ ]
165
+ )
166
+ frame.render_widget(controls, controls_area)
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ def render_content_area(frame, content_area)
173
+ return render_error(frame, content_area, current_tab.error) if current_tab.error
174
+
175
+ current_tab.render(@tui, frame, content_area)
176
+ end
177
+
178
+ def render_controls(frame, area)
179
+ active_keys = current_tab.controls.filter { |hash| hash[:description] }
180
+
181
+ # Split controls into two lines, 8 is arbitrary
182
+ # TODO Dynamically split based on term width?
183
+ first = active_keys[...8]
184
+ lines = []
185
+ lines << @tui.text_line(spans: first.map { |hash|
186
+ [
187
+ @tui.text_span(content: hash[:display] || hash[:code], style: @hotkey_style),
188
+ @tui.text_span(content: ": #{t(hash[:description])} ")
189
+ ]
190
+ }.flatten)
191
+
192
+ last = active_keys[8...]
193
+ lines << if last && last.size > 0
194
+ @tui.text_line(spans: last.map { |hash|
195
+ [
196
+ @tui.text_span(content: hash[:display] || hash[:code], style: @hotkey_style),
197
+ @tui.text_span(content: ": #{t(hash[:description])} ")
198
+ ]
199
+ }.flatten)
200
+ else
201
+ @tui.text_line(spans: [])
202
+ end
203
+
204
+ footer = [
205
+ @tui.text_span(content: "Redis: #{redis_url} "),
206
+ @tui.text_span(content: "#{t("Now")}: #{Time.now.utc} "),
207
+ @tui.text_span(content: "#{t("Locale")}: #{@lang}")
208
+ ]
209
+
210
+ if current_tab.data[:filter]
211
+ @filter_style = @tui.style(fg: :white, bg: :dark_gray)
212
+ footer += [
213
+ @tui.text_span(content: " #{t("Filter")}: ", style: @filter_style),
214
+ @tui.text_span(content: current_tab.data[:filter], style: @filter_style),
215
+ @tui.text_span(content: "_", style: @tui.style(fg: :white, bg: :dark_gray, modifiers: [:slow_blink]))
216
+ ]
217
+ end
218
+ footer << @tui.text_span(content: " FPS: #{previous_fps}") if debugging?
219
+ lines << @tui.text_line(spans: footer)
220
+
221
+ controls = @tui.block(title: t("Controls"), borders: [:all],
222
+ children: [@tui.paragraph(text: lines)])
223
+ frame.render_widget(controls, area)
224
+ end
225
+
226
+ def handle_input
227
+ # We shouldn't need more than 10 FPS for a data-oriented app.
228
+ # This throttles down our CPU usage. Default is 60 FPS.
229
+ case @tui.poll_event(timeout: 0.1)
230
+ in {type: :key, code: "esc"} if @showing == :help
231
+ @showing = :main
232
+ in {type: :key, code: code} if current_tab.filtering? && code.length == 1
233
+ current_tab.append_to_filter(code)
234
+ current_tab.refresh_data
235
+ in {type: :key, code:, modifiers:}
236
+ control = current_tab.controls.find { |ctrl|
237
+ ctrl[:code] == code &&
238
+ (ctrl[:modifiers] || []) == (modifiers || [])
239
+ }
240
+ return unless control
241
+ control[:action].call(self, current_tab).tap {
242
+ refresh_data if control[:refresh]
243
+ }
244
+ else
245
+ # Ignore other events
246
+ end
247
+ rescue => ex
248
+ logger.error { [ex.message, ex.backtrace] }
249
+ end
250
+
251
+ def redis_url
252
+ Sidekiq.redis do |conn|
253
+ conn.config.server_url
254
+ end
255
+ rescue
256
+ "N/A"
257
+ end
258
+
259
+ def should_refresh?
260
+ Time.now - @last_refresh >= REFRESH_INTERVAL_SECONDS
261
+ end
262
+
263
+ def refresh_data
264
+ # logger.info GC.stat
265
+ current_tab.refresh_data
266
+ @last_refresh = Time.now
267
+ rescue => e
268
+ handle_exception(e)
269
+ current_tab.error = e
270
+ end
271
+
272
+ def render_error(frame, area, err)
273
+ header = [@tui.text_line(
274
+ spans: [@tui.text_span(content: err.message, style: @tui.style(modifiers: [:bold]))],
275
+ alignment: :center
276
+ )]
277
+ lines = Array(err.backtrace).map { |line| @tui.text_line(spans: [@tui.text_span(content: line)]) }
278
+
279
+ frame.render_widget(
280
+ @tui.paragraph(
281
+ text: header + lines,
282
+ alignment: :left,
283
+ block: @tui.block(title: t("Error"), borders: [:all], border_style: @tui.style(fg: :light_red))
284
+ ),
285
+ area
286
+ )
287
+ end
288
+
289
+ def show_help
290
+ @showing = :help
291
+ end
292
+
293
+ def all
294
+ @all ||= Tabs::All.map { |kls| kls.new(self) }
295
+ end
296
+
297
+ def current_tab
298
+ @current ||= @all.first
299
+ end
300
+
301
+ # Navigate tabs to the left or right.
302
+ # @param direction [Symbol] :left or :right
303
+ def navigate(direction)
304
+ index_change = (direction == :right) ? 1 : -1
305
+ @current = @all[(@all.index(current_tab) + index_change) % @all.size]
306
+ @current.reset_data
307
+ end
308
+
309
+ public def t(msg, options = nil)
310
+ string = @strings[msg] || msg
311
+ if options.nil?
312
+ string
313
+ else
314
+ string % options
315
+ end
316
+ end
317
+
318
+ def load_strings(lang)
319
+ {}.tap do |all|
320
+ find_locale_files(lang).each do |file|
321
+ strs = YAML.safe_load_file(file)
322
+ all.merge! strs[lang]
323
+ end
324
+ end
325
+ end
326
+
327
+ def locale_files
328
+ @@locale_files ||= LOCALE_DIRECTORIES.flat_map { |path|
329
+ Dir["#{path}/*.yml"]
330
+ }
331
+ end
332
+
333
+ def available_locales
334
+ @@available_locales ||= Set.new(locale_files.map { |path| File.basename(path, ".yml") })
335
+ end
336
+
337
+ def find_locale_files(lang)
338
+ locale_files.select { |file| file =~ /\/#{lang}\.yml$/ }
339
+ end
340
+
341
+ def load_locale
342
+ require "yaml"
343
+ lang = @lang.split(".").first # "en_US"
344
+ while lang.size > 0
345
+ hash = load_strings(lang)
346
+ if hash.size > 0
347
+ # found a working language dataset
348
+ @lang = lang
349
+ @strings = hash
350
+ Sidekiq.logger.debug { "using the #{lang} locale" }
351
+ break
352
+ end
353
+ # Try "en_US", "en_U", "en_", "en"
354
+ # It's ugly and bruteforce but it works
355
+ lang = lang[..-2]
356
+ end
357
+ end
358
+
359
+ def track_fps
360
+ # We hold two fps buckets: one for current second, one for previous second
361
+ idx = Time.now.to_i % 2
362
+ @fps[idx] += 1
363
+ yield
364
+ end
365
+
366
+ def previous_fps
367
+ curidx = Time.now.to_i % 2
368
+ prev = (curidx == 1) ? 0 : 1
369
+ if (val = @fps[prev]) != 0
370
+ @previous_fps = val
371
+ @fps[prev] = 0
372
+ end
373
+ @previous_fps
374
+ end
375
+
376
+ def debugging?
377
+ !!ENV["DEBUG"]
378
+ end
379
+ end
380
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "8.1.0"
4
+ VERSION = "8.1.2"
5
5
  MAJOR = 8
6
6
 
7
7
  def self.gem_version
@@ -99,8 +99,8 @@ module Sidekiq
99
99
  if url_params("identity")
100
100
  pro = Sidekiq::ProcessSet[url_params("identity")]
101
101
 
102
- pro.quiet! if url_params("quiet")
103
- pro.stop! if url_params("stop")
102
+ pro&.quiet! if url_params("quiet")
103
+ pro&.stop! if url_params("stop")
104
104
  else
105
105
  processes.each do |pro|
106
106
  next if pro.embedded?
@@ -287,6 +287,17 @@ module Sidekiq
287
287
  %(<time class="ltr" dir="ltr" title="#{stamp}" datetime="#{stamp}">#{time}</time>)
288
288
  end
289
289
 
290
+ def queue_names_by_capsule(pro)
291
+ cap = pro.capsules
292
+ if cap
293
+ cap.map { |k, v| v["weights"].keys.join(", ") }.join("; ")
294
+ else
295
+ # DEPRECATED Backwards compatibility with older processes.
296
+ # 'capsules' element added in v8.0.9
297
+ pro.queues.join(", ")
298
+ end
299
+ end
300
+
290
301
  def job_params(job, score)
291
302
  "#{score}-#{job["jid"]}"
292
303
  end
data/lib/sidekiq.rb CHANGED
@@ -47,6 +47,13 @@ module Sidekiq
47
47
  puts "Take a deep breath and count to ten..."
48
48
  end
49
49
 
50
+ def self.testing!(mode = :fake, &block)
51
+ raise "Unknown testing mode: #{mode}" unless %i[fake disable inline].include?(mode)
52
+
53
+ require "sidekiq/test_api"
54
+ Sidekiq::Testing.__set_test_mode(mode, &block)
55
+ end
56
+
50
57
  def self.server?
51
58
  defined?(Sidekiq::CLI)
52
59
  end
data/sidekiq.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |gem|
8
8
  gem.homepage = "https://sidekiq.org"
9
9
  gem.license = "LGPL-3.0"
10
10
 
11
- gem.executables = ["sidekiq", "sidekiqmon"]
11
+ gem.executables = ["sidekiq", "sidekiqmon", "kiq"]
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
@@ -154,7 +154,7 @@ function livePollCallback() {
154
154
 
155
155
  function checkResponse(resp) {
156
156
  if (!resp.ok) {
157
- throw response.error();
157
+ throw resp.error();
158
158
  }
159
159
  return resp
160
160
  }