qless 0.9.3 → 0.10.0

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 (65) hide show
  1. data/Gemfile +9 -3
  2. data/README.md +70 -25
  3. data/Rakefile +125 -9
  4. data/exe/install_phantomjs +21 -0
  5. data/lib/qless.rb +115 -76
  6. data/lib/qless/config.rb +11 -9
  7. data/lib/qless/failure_formatter.rb +43 -0
  8. data/lib/qless/job.rb +201 -102
  9. data/lib/qless/job_reservers/ordered.rb +7 -1
  10. data/lib/qless/job_reservers/round_robin.rb +16 -6
  11. data/lib/qless/job_reservers/shuffled_round_robin.rb +9 -2
  12. data/lib/qless/lua/qless-lib.lua +2463 -0
  13. data/lib/qless/lua/qless.lua +2012 -0
  14. data/lib/qless/lua_script.rb +63 -12
  15. data/lib/qless/middleware/memory_usage_monitor.rb +62 -0
  16. data/lib/qless/middleware/metriks.rb +45 -0
  17. data/lib/qless/middleware/redis_reconnect.rb +6 -3
  18. data/lib/qless/middleware/requeue_exceptions.rb +94 -0
  19. data/lib/qless/middleware/retry_exceptions.rb +38 -9
  20. data/lib/qless/middleware/sentry.rb +3 -7
  21. data/lib/qless/middleware/timeout.rb +64 -0
  22. data/lib/qless/queue.rb +90 -55
  23. data/lib/qless/server.rb +177 -130
  24. data/lib/qless/server/views/_job.erb +33 -15
  25. data/lib/qless/server/views/completed.erb +11 -0
  26. data/lib/qless/server/views/layout.erb +70 -11
  27. data/lib/qless/server/views/overview.erb +93 -53
  28. data/lib/qless/server/views/queue.erb +9 -8
  29. data/lib/qless/server/views/queues.erb +18 -1
  30. data/lib/qless/subscriber.rb +37 -22
  31. data/lib/qless/tasks.rb +5 -10
  32. data/lib/qless/test_helpers/worker_helpers.rb +55 -0
  33. data/lib/qless/version.rb +3 -1
  34. data/lib/qless/worker.rb +4 -413
  35. data/lib/qless/worker/base.rb +247 -0
  36. data/lib/qless/worker/forking.rb +245 -0
  37. data/lib/qless/worker/serial.rb +41 -0
  38. metadata +135 -52
  39. data/lib/qless/qless-core/cancel.lua +0 -101
  40. data/lib/qless/qless-core/complete.lua +0 -233
  41. data/lib/qless/qless-core/config.lua +0 -56
  42. data/lib/qless/qless-core/depends.lua +0 -65
  43. data/lib/qless/qless-core/deregister_workers.lua +0 -12
  44. data/lib/qless/qless-core/fail.lua +0 -117
  45. data/lib/qless/qless-core/failed.lua +0 -83
  46. data/lib/qless/qless-core/get.lua +0 -37
  47. data/lib/qless/qless-core/heartbeat.lua +0 -51
  48. data/lib/qless/qless-core/jobs.lua +0 -41
  49. data/lib/qless/qless-core/pause.lua +0 -18
  50. data/lib/qless/qless-core/peek.lua +0 -165
  51. data/lib/qless/qless-core/pop.lua +0 -314
  52. data/lib/qless/qless-core/priority.lua +0 -32
  53. data/lib/qless/qless-core/put.lua +0 -169
  54. data/lib/qless/qless-core/qless-lib.lua +0 -2354
  55. data/lib/qless/qless-core/qless.lua +0 -1862
  56. data/lib/qless/qless-core/queues.lua +0 -58
  57. data/lib/qless/qless-core/recur.lua +0 -190
  58. data/lib/qless/qless-core/retry.lua +0 -73
  59. data/lib/qless/qless-core/stats.lua +0 -92
  60. data/lib/qless/qless-core/tag.lua +0 -100
  61. data/lib/qless/qless-core/track.lua +0 -79
  62. data/lib/qless/qless-core/unfail.lua +0 -54
  63. data/lib/qless/qless-core/unpause.lua +0 -12
  64. data/lib/qless/qless-core/workers.lua +0 -69
  65. data/lib/qless/wait_until.rb +0 -19
@@ -1,43 +1,52 @@
1
- require "qless/job"
2
- require "redis"
3
- require "json"
1
+ # Encoding: utf-8
2
+
3
+ require 'qless/job'
4
+ require 'redis'
5
+ require 'json'
4
6
 
5
7
  module Qless
8
+ # A class for interacting with jobs in different states in a queue. Not meant
9
+ # to be instantiated directly, it's accessed with Queue#jobs
6
10
  class QueueJobs
7
11
  def initialize(name, client)
8
12
  @name = name
9
13
  @client = client
10
14
  end
11
15
 
12
- def running(start=0, count=25)
13
- @client._jobs.call([], ['running', Time.now.to_f, @name, start, count])
16
+ def running(start = 0, count = 25)
17
+ @client.call('jobs', 'running', @name, start, count)
14
18
  end
15
19
 
16
- def stalled(start=0, count=25)
17
- @client._jobs.call([], ['stalled', Time.now.to_f, @name, start, count])
20
+ def stalled(start = 0, count = 25)
21
+ @client.call('jobs', 'stalled', @name, start, count)
18
22
  end
19
23
 
20
- def scheduled(start=0, count=25)
21
- @client._jobs.call([], ['scheduled', Time.now.to_f, @name, start, count])
24
+ def scheduled(start = 0, count = 25)
25
+ @client.call('jobs', 'scheduled', @name, start, count)
22
26
  end
23
27
 
24
- def depends(start=0, count=25)
25
- @client._jobs.call([], ['depends', Time.now.to_f, @name, start, count])
28
+ def depends(start = 0, count = 25)
29
+ @client.call('jobs', 'depends', @name, start, count)
26
30
  end
27
31
 
28
- def recurring(start=0, count=25)
29
- @client._jobs.call([], ['recurring', Time.now.to_f, @name, start, count])
32
+ def recurring(start = 0, count = 25)
33
+ @client.call('jobs', 'recurring', @name, start, count)
30
34
  end
31
35
  end
32
36
 
37
+ # A class for interacting with a specific queue. Not meant to be instantiated
38
+ # directly, it's accessed with Client#queues[...]
33
39
  class Queue
34
40
  attr_reader :name, :client
35
- attr_accessor :worker_name
36
41
 
37
42
  def initialize(name, client)
38
43
  @client = client
39
44
  @name = name
40
- self.worker_name = Qless.worker_name
45
+ end
46
+
47
+ # Our worker name is the same as our client's
48
+ def worker_name
49
+ @client.worker_name
41
50
  end
42
51
 
43
52
  def jobs
@@ -45,7 +54,7 @@ module Qless
45
54
  end
46
55
 
47
56
  def counts
48
- JSON.parse(@client._queues.call([], [Time.now.to_i, @name]))
57
+ JSON.parse(@client.call('queues', @name))
49
58
  end
50
59
 
51
60
  def heartbeat
@@ -57,20 +66,36 @@ module Qless
57
66
  end
58
67
 
59
68
  def max_concurrency
60
- value = get_config(:"max-concurrency")
69
+ value = get_config('max-concurrency')
61
70
  value && Integer(value)
62
71
  end
63
72
 
64
73
  def max_concurrency=(value)
65
- set_config :"max-concurrency", value
74
+ set_config 'max-concurrency', value
75
+ end
76
+
77
+ def paused?
78
+ counts['paused']
66
79
  end
67
80
 
68
- def pause
69
- @client._pause.call([], [name])
81
+ def pause(opts = {})
82
+ @client.call('pause', name)
83
+ @client.call('timeout', jobs.running(0, -1)) unless opts[:stopjobs].nil?
70
84
  end
71
85
 
72
86
  def unpause
73
- @client._unpause.call([], [name])
87
+ @client.call('unpause', name)
88
+ end
89
+
90
+ QueueNotEmptyError = Class.new(StandardError)
91
+
92
+ def forget
93
+ job_count = length
94
+ if job_count.zero?
95
+ @client.call('queue.forget', name)
96
+ else
97
+ raise QueueNotEmptyError, "The queue is not empty. It has #{job_count} jobs."
98
+ end
74
99
  end
75
100
 
76
101
  # Put the described job in this queue
@@ -78,20 +103,18 @@ module Qless
78
103
  # => priority (int)
79
104
  # => tags (array of strings)
80
105
  # => delay (int)
81
- def put(klass, data, opts={})
106
+ def put(klass, data, opts = {})
82
107
  opts = job_options(klass, data, opts)
83
-
84
- @client._put.call([@name], [
85
- (opts[:jid] or Qless.generate_jid),
86
- klass.name,
87
- JSON.generate(data),
88
- Time.now.to_f,
89
- opts.fetch(:delay, 0),
90
- 'priority', opts.fetch(:priority, 0),
91
- 'tags', JSON.generate(opts.fetch(:tags, [])),
92
- 'retries', opts.fetch(:retries, 5),
93
- 'depends', JSON.generate(opts.fetch(:depends, []))
94
- ])
108
+ @client.call('put', worker_name, @name,
109
+ (opts[:jid] || Qless.generate_jid),
110
+ klass.is_a?(String) ? klass : klass.name,
111
+ JSON.generate(data),
112
+ opts.fetch(:delay, 0),
113
+ 'priority', opts.fetch(:priority, 0),
114
+ 'tags', JSON.generate(opts.fetch(:tags, [])),
115
+ 'retries', opts.fetch(:retries, 5),
116
+ 'depends', JSON.generate(opts.fetch(:depends, []))
117
+ )
95
118
  end
96
119
 
97
120
  # Make a recurring job in this queue
@@ -100,52 +123,64 @@ module Qless
100
123
  # => tags (array of strings)
101
124
  # => retries (int)
102
125
  # => offset (int)
103
- def recur(klass, data, interval, opts={})
126
+ def recur(klass, data, interval, opts = {})
104
127
  opts = job_options(klass, data, opts)
105
-
106
- @client._recur.call([], [
107
- 'on',
128
+ @client.call(
129
+ 'recur',
108
130
  @name,
109
- (opts[:jid] or Qless.generate_jid),
110
- klass.to_s,
131
+ (opts[:jid] || Qless.generate_jid),
132
+ klass.is_a?(String) ? klass : klass.name,
111
133
  JSON.generate(data),
112
- Time.now.to_f,
113
134
  'interval', interval, opts.fetch(:offset, 0),
114
135
  'priority', opts.fetch(:priority, 0),
115
136
  'tags', JSON.generate(opts.fetch(:tags, [])),
116
- 'retries', opts.fetch(:retries, 5)
117
- ])
137
+ 'retries', opts.fetch(:retries, 5),
138
+ 'backlog', opts.fetch(:backlog, 0)
139
+ )
118
140
  end
119
141
 
120
142
  # Pop a work item off the queue
121
- def pop(count=nil)
122
- results = @client._pop.call([@name], [worker_name, (count || 1), Time.now.to_f]).map { |j| Job.new(@client, JSON.parse(j)) }
123
- count.nil? ? results[0] : results
143
+ def pop(count = nil)
144
+ jids = JSON.parse(@client.call('pop', @name, worker_name, (count || 1)))
145
+ jobs = jids.map { |j| Job.new(@client, j) }
146
+ count.nil? ? jobs[0] : jobs
124
147
  end
125
148
 
126
149
  # Peek at a work item
127
- def peek(count=nil)
128
- results = @client._peek.call([@name], [(count || 1), Time.now.to_f]).map { |j| Job.new(@client, JSON.parse(j)) }
129
- count.nil? ? results[0] : results
150
+ def peek(count = nil)
151
+ jids = JSON.parse(@client.call('peek', @name, (count || 1)))
152
+ jobs = jids.map { |j| Job.new(@client, j) }
153
+ count.nil? ? jobs[0] : jobs
130
154
  end
131
155
 
132
- def stats(date=nil)
133
- JSON.parse(@client._stats.call([], [@name, (date || Time.now.to_f)]))
156
+ def stats(date = nil)
157
+ JSON.parse(@client.call('stats', @name, (date || Time.now.to_f)))
134
158
  end
135
159
 
136
160
  # How many items in the queue?
137
161
  def length
138
162
  (@client.redis.multi do
139
- @client.redis.zcard("ql:q:#{@name}-locks")
140
- @client.redis.zcard("ql:q:#{@name}-work")
141
- @client.redis.zcard("ql:q:#{@name}-scheduled")
163
+ %w[ locks work scheduled depends ].each do |suffix|
164
+ @client.redis.zcard("ql:q:#{@name}-#{suffix}")
165
+ end
142
166
  end).inject(0, :+)
143
167
  end
144
168
 
145
169
  def to_s
146
170
  "#<Qless::Queue #{@name}>"
147
171
  end
148
- alias inspect to_s
172
+ alias_method :inspect, :to_s
173
+
174
+ def ==(other)
175
+ self.class == other.class &&
176
+ client == other.client &&
177
+ name.to_s == other.name.to_s
178
+ end
179
+ alias eql? ==
180
+
181
+ def hash
182
+ self.class.hash ^ client.hash ^ name.to_s.hash
183
+ end
149
184
 
150
185
  private
151
186
 
@@ -1,9 +1,10 @@
1
+ # Encoding: utf-8
2
+
1
3
  require 'sinatra/base'
2
4
  require 'qless'
3
5
 
4
- # Much of this is shamelessly poached from the resque web client
5
-
6
6
  module Qless
7
+ # The Qless web interface
7
8
  class Server < Sinatra::Base
8
9
  # Path-y-ness
9
10
  dir = File.dirname(File.expand_path(__FILE__))
@@ -27,7 +28,7 @@ module Qless
27
28
  include Rack::Utils
28
29
 
29
30
  def url_path(*path_parts)
30
- [ path_prefix, path_parts ].join("/").squeeze('/')
31
+ [path_prefix, path_parts].join('/').squeeze('/')
31
32
  end
32
33
  alias_method :u, :url_path
33
34
 
@@ -49,11 +50,11 @@ module Qless
49
50
  end
50
51
 
51
52
  def next_page_url
52
- page_url 1
53
+ page_url(1)
53
54
  end
54
55
 
55
56
  def prev_page_url
56
- page_url -1
57
+ page_url(-1)
57
58
  end
58
59
 
59
60
  def current_page
@@ -75,34 +76,35 @@ module Qless
75
76
  end
76
77
 
77
78
  def tabs
78
- return [
79
- {:name => 'Queues' , :path => '/queues' },
80
- {:name => 'Workers' , :path => '/workers' },
81
- {:name => 'Track' , :path => '/track' },
82
- {:name => 'Failed' , :path => '/failed' },
83
- {:name => 'Config' , :path => '/config' },
84
- {:name => 'About' , :path => '/about' }
79
+ [
80
+ { name: 'Queues' , path: '/queues' },
81
+ { name: 'Workers' , path: '/workers' },
82
+ { name: 'Track' , path: '/track' },
83
+ { name: 'Failed' , path: '/failed' },
84
+ { name: 'Completed', path: '/completed'},
85
+ { name: 'Config' , path: '/config' },
86
+ { name: 'About' , path: '/about' }
85
87
  ]
86
88
  end
87
89
 
88
90
  def application_name
89
- return client.config['application']
91
+ client.config['application']
90
92
  end
91
93
 
92
94
  def queues
93
- return client.queues.counts
95
+ client.queues.counts
94
96
  end
95
97
 
96
98
  def tracked
97
- return client.jobs.tracked
99
+ client.jobs.tracked
98
100
  end
99
101
 
100
102
  def workers
101
- return client.workers.counts
103
+ client.workers.counts
102
104
  end
103
105
 
104
106
  def failed
105
- return client.jobs.failed
107
+ client.jobs.failed
106
108
  end
107
109
 
108
110
  # Return the supplied object back as JSON
@@ -113,45 +115,46 @@ module Qless
113
115
 
114
116
  # Make the id acceptable as an id / att in HTML
115
117
  def sanitize_attr(attr)
116
- return attr.gsub(/[^a-zA-Z\:\_]/, '-')
118
+ attr.gsub(/[^a-zA-Z\:\_]/, '-')
117
119
  end
118
120
 
119
121
  # What are the top tags? Since it might go on, say, every
120
122
  # page, then we should probably be caching it
121
123
  def top_tags
122
124
  @top_tags ||= {
123
- :top => client.tags,
124
- :fetched => Time.now
125
+ top: client.tags,
126
+ fetched: Time.now
125
127
  }
126
- if (Time.now - @top_tags[:fetched]) > 60 then
128
+ if (Time.now - @top_tags[:fetched]) > 60
127
129
  @top_tags = {
128
- :top => client.tags,
129
- :fetched => Time.now
130
+ top: client.tags,
131
+ fetched: Time.now
130
132
  }
131
133
  end
132
134
  @top_tags[:top]
133
135
  end
134
136
 
135
137
  def strftime(t)
136
- # From http://stackoverflow.com/questions/195740/how-do-you-do-relative-time-in-rails
138
+ # From http://stackoverflow.com/questions/195740
137
139
  diff_seconds = Time.now - t
140
+ formatted = t.strftime('%b %e, %Y %H:%M:%S')
138
141
  case diff_seconds
139
- when 0 .. 59
140
- "#{diff_seconds.to_i} seconds ago"
141
- when 60 ... 3600
142
- "#{(diff_seconds/60).to_i} minutes ago"
143
- when 3600 ... 3600*24
144
- "#{(diff_seconds/3600).to_i} hours ago"
145
- when (3600*24) ... (3600*24*30)
146
- "#{(diff_seconds/(3600*24)).to_i} days ago"
147
- else
148
- t.strftime('%b %e, %Y %H:%M:%S %Z (%z)')
142
+ when 0 .. 59
143
+ "#{formatted} (#{diff_seconds.to_i} seconds ago)"
144
+ when 60 ... 3600
145
+ "#{formatted} (#{(diff_seconds / 60).to_i} minutes ago)"
146
+ when 3600 ... 3600 * 24
147
+ "#{formatted} (#{(diff_seconds / 3600).to_i} hours ago)"
148
+ when (3600 * 24) ... (3600 * 24 * 30)
149
+ "#{formatted} (#{(diff_seconds / (3600 * 24)).to_i} days ago)"
150
+ else
151
+ formatted
149
152
  end
150
153
  end
151
154
  end
152
155
 
153
156
  get '/?' do
154
- erb :overview, :layout => true, :locals => { :title => "Overview" }
157
+ erb :overview, layout: true, locals: { title: 'Overview' }
155
158
  end
156
159
 
157
160
  # Returns a JSON blob with the job counts for various queues
@@ -160,8 +163,8 @@ module Qless
160
163
  end
161
164
 
162
165
  get '/queues/?' do
163
- erb :queues, :layout => true, :locals => {
164
- :title => 'Queues'
166
+ erb :queues, layout: true, locals: {
167
+ title: 'Queues'
165
168
  }
166
169
  end
167
170
 
@@ -175,20 +178,19 @@ module Qless
175
178
  queue = client.queues[params[:name]]
176
179
  tab = params.fetch('tab', 'stats')
177
180
 
178
- jobs = if tab == 'waiting'
179
- queue.peek(20)
181
+ jobs = []
182
+ if tab == 'waiting'
183
+ jobs = queue.peek(20)
180
184
  elsif filtered_tabs.include?(tab)
181
- paginated(queue.jobs, tab).map { |jid| client.jobs[jid] }
182
- else
183
- []
185
+ jobs = paginated(queue.jobs, tab).map { |jid| client.jobs[jid] }
184
186
  end
185
187
 
186
- erb :queue, :layout => true, :locals => {
187
- :title => "Queue #{params[:name]}",
188
- :tab => tab,
189
- :jobs => jobs,
190
- :queue => client.queues[params[:name]].counts,
191
- :stats => queue.stats
188
+ erb :queue, layout: true, locals: {
189
+ title: "Queue #{params[:name]}",
190
+ tab: tab,
191
+ jobs: jobs,
192
+ queue: client.queues[params[:name]].counts,
193
+ stats: queue.stats
192
194
  }
193
195
  end
194
196
 
@@ -200,106 +202,117 @@ module Qless
200
202
  # qless-core doesn't provide functionality this way, so we'll
201
203
  # do it ourselves. I'm not sure if this is how the core library
202
204
  # should behave or not.
203
- erb :failed, :layout => true, :locals => {
204
- :title => 'Failed',
205
- :failed => client.jobs.failed.keys.map { |t| client.jobs.failed(t).tap { |f| f['type'] = t } }
205
+ erb :failed, layout: true, locals: {
206
+ title: 'Failed',
207
+ failed: client.jobs.failed.keys.map do |t|
208
+ client.jobs.failed(t).tap { |f| f['type'] = t }
209
+ end
206
210
  }
207
211
  end
208
212
 
209
213
  get '/failed/:type/?' do
210
- erb :failed_type, :layout => true, :locals => {
211
- :title => 'Failed | ' + params[:type],
212
- :type => params[:type],
213
- :failed => paginated(client.jobs, :failed, params[:type])
214
+ erb :failed_type, layout: true, locals: {
215
+ title: 'Failed | ' + params[:type],
216
+ type: params[:type],
217
+ failed: paginated(client.jobs, :failed, params[:type])
218
+ }
219
+ end
220
+
221
+ get '/completed/?' do
222
+ completed = paginated(client.jobs, :complete)
223
+ erb :completed, layout: true, locals: {
224
+ title: 'Completed',
225
+ jobs: completed.map { |jid| client.jobs[jid] }
214
226
  }
215
227
  end
216
228
 
217
229
  get '/track/?' do
218
- erb :track, :layout => true, :locals => {
219
- :title => 'Track'
230
+ erb :track, layout: true, locals: {
231
+ title: 'Track'
220
232
  }
221
233
  end
222
234
 
223
235
  get '/jobs/:jid' do
224
- erb :job, :layout => true, :locals => {
225
- :title => "Job | #{params[:jid]}",
226
- :jid => params[:jid],
227
- :job => client.jobs[params[:jid]]
236
+ erb :job, layout: true, locals: {
237
+ title: "Job | #{params[:jid]}",
238
+ jid: params[:jid],
239
+ job: client.jobs[params[:jid]]
228
240
  }
229
241
  end
230
242
 
231
243
  get '/workers/?' do
232
- erb :workers, :layout => true, :locals => {
233
- :title => 'Workers'
244
+ erb :workers, layout: true, locals: {
245
+ title: 'Workers'
234
246
  }
235
247
  end
236
248
 
237
249
  get '/workers/:worker' do
238
- erb :worker, :layout => true, :locals => {
239
- :title => 'Worker | ' + params[:worker],
240
- :worker => client.workers[params[:worker]].tap { |w|
250
+ erb :worker, layout: true, locals: {
251
+ title: 'Worker | ' + params[:worker],
252
+ worker: client.workers[params[:worker]].tap do |w|
241
253
  w['jobs'] = w['jobs'].map { |j| client.jobs[j] }
242
254
  w['stalled'] = w['stalled'].map { |j| client.jobs[j] }
243
255
  w['name'] = params[:worker]
244
- }
256
+ end
245
257
  }
246
258
  end
247
259
 
248
260
  get '/tag/?' do
249
261
  jobs = paginated(client.jobs, :tagged, params[:tag])
250
- erb :tag, :layout => true, :locals => {
251
- :title => "Tag | #{params[:tag]}",
252
- :tag => params[:tag],
253
- :jobs => jobs['jobs'].map { |jid| client.jobs[jid] },
254
- :total => jobs['total']
262
+ erb :tag, layout: true, locals: {
263
+ title: "Tag | #{params[:tag]}",
264
+ tag: params[:tag],
265
+ jobs: jobs['jobs'].map { |jid| client.jobs[jid] },
266
+ total: jobs['total']
255
267
  }
256
268
  end
257
269
 
258
270
  get '/config/?' do
259
- erb :config, :layout => true, :locals => {
260
- :title => 'Config',
261
- :options => client.config.all
271
+ erb :config, layout: true, locals: {
272
+ title: 'Config',
273
+ options: client.config.all
262
274
  }
263
275
  end
264
276
 
265
277
  get '/about/?' do
266
- erb :about, :layout => true, :locals => {
267
- :title => 'About'
278
+ erb :about, layout: true, locals: {
279
+ title: 'About'
268
280
  }
269
281
  end
270
282
 
271
283
  # These are the bits where we accept AJAX requests
272
- post "/track/?" do
284
+ post '/track/?' do
273
285
  # Expects a JSON-encoded hash with a job id, and optionally some tags
274
286
  data = JSON.parse(request.body.read)
275
- job = client.jobs[data["id"]]
276
- if not job.nil?
277
- data.fetch("tags", false) ? job.track(*data["tags"]) : job.track()
287
+ job = client.jobs[data['id']]
288
+ if !job.nil?
289
+ data.fetch('tags', false) ? job.track(*data['tags']) : job.track
278
290
  if request.xhr?
279
- json({ :tracked => [job.jid] })
291
+ json({ tracked: [job.jid] })
280
292
  else
281
293
  redirect to('/track')
282
294
  end
283
295
  else
284
296
  if request.xhr?
285
- json({ :tracked => [] })
297
+ json({ tracked: [] })
286
298
  else
287
299
  redirect to(request.referrer)
288
300
  end
289
301
  end
290
302
  end
291
303
 
292
- post "/untrack/?" do
304
+ post '/untrack/?' do
293
305
  # Expects a JSON-encoded array of job ids to stop tracking
294
- jobs = JSON.parse(request.body.read).map { |jid| client.jobs[jid] }.select { |j| not j.nil? }
306
+ jobs = JSON.parse(request.body.read).map { |jid| client.jobs[jid] }
307
+ jobs.compact!
295
308
  # Go ahead and cancel all the jobs!
296
309
  jobs.each do |job|
297
- job.untrack()
310
+ job.untrack
298
311
  end
299
- return json({ :untracked => jobs.map { |job| job.jid } })
312
+ return json({ untracked: jobs.map { |job| job.jid } })
300
313
  end
301
314
 
302
- post "/priority/?" do
315
+ post '/priority/?' do
303
316
  # Expects a JSON-encoded dictionary of jid => priority
304
317
  response = Hash.new
305
318
  r = JSON.parse(request.body.read)
@@ -314,7 +327,40 @@ module Qless
314
327
  return json(response)
315
328
  end
316
329
 
317
- post "/tag/?" do
330
+ post '/pause/?' do
331
+ # Expects JSON blob: {'queue': <queue>}
332
+ r = JSON.parse(request.body.read)
333
+ if r['queue']
334
+ @client.queues[r['queue']].pause
335
+ return json({ queue: 'paused' })
336
+ else
337
+ raise 'No queue provided'
338
+ end
339
+ end
340
+
341
+ post '/unpause/?' do
342
+ # Expects JSON blob: {'queue': <queue>}
343
+ r = JSON.parse(request.body.read)
344
+ if r['queue']
345
+ @client.queues[r['queue']].unpause
346
+ return json({ queue: 'unpaused' })
347
+ else
348
+ raise 'No queue provided'
349
+ end
350
+ end
351
+
352
+ post '/timeout/?' do
353
+ # Expects JSON blob: {'jid': <jid>}
354
+ r = JSON.parse(request.body.read)
355
+ if r['jid']
356
+ @client.jobs[r['jid']].timeout
357
+ return json({ jid: r['jid'] })
358
+ else
359
+ raise 'No jid provided'
360
+ end
361
+ end
362
+
363
+ post '/tag/?' do
318
364
  # Expects a JSON-encoded dictionary of jid => [tag, tag, tag]
319
365
  response = Hash.new
320
366
  JSON.parse(request.body.read).each_pair do |jid, tags|
@@ -328,7 +374,7 @@ module Qless
328
374
  return json(response)
329
375
  end
330
376
 
331
- post "/untag/?" do
377
+ post '/untag/?' do
332
378
  # Expects a JSON-encoded dictionary of jid => [tag, tag, tag]
333
379
  response = Hash.new
334
380
  JSON.parse(request.body.read).each_pair do |jid, tags|
@@ -342,99 +388,100 @@ module Qless
342
388
  return json(response)
343
389
  end
344
390
 
345
- post "/move/?" do
391
+ post '/move/?' do
346
392
  # Expects a JSON-encoded hash of id: jid, and queue: queue_name
347
393
  data = JSON.parse(request.body.read)
348
- if data["id"].nil? or data["queue"].nil?
349
- halt 400, "Need id and queue arguments"
394
+ if data['id'].nil? || data['queue'].nil?
395
+ halt 400, 'Need id and queue arguments'
350
396
  else
351
- job = client.jobs[data["id"]]
397
+ job = client.jobs[data['id']]
352
398
  if job.nil?
353
- halt 404, "Could not find job"
399
+ halt 404, 'Could not find job'
354
400
  else
355
- job.move(data["queue"])
356
- return json({ :id => data["id"], :queue => data["queue"]})
401
+ job.requeue(data['queue'])
402
+ return json({ id: data['id'], queue: data['queue'] })
357
403
  end
358
404
  end
359
405
  end
360
406
 
361
- post "/undepend/?" do
407
+ post '/undepend/?' do
362
408
  # Expects a JSON-encoded hash of id: jid, and queue: queue_name
363
409
  data = JSON.parse(request.body.read)
364
- if data["id"].nil?
365
- halt 400, "Need id"
410
+ if data['id'].nil?
411
+ halt 400, 'Need id'
366
412
  else
367
- job = client.jobs[data["id"]]
413
+ job = client.jobs[data['id']]
368
414
  if job.nil?
369
- halt 404, "Could not find job"
415
+ halt 404, 'Could not find job'
370
416
  else
371
417
  job.undepend(data['dependency'])
372
- return json({:id => data["id"]})
418
+ return json({ id: data['id'] })
373
419
  end
374
420
  end
375
421
  end
376
422
 
377
- post "/retry/?" do
423
+ post '/retry/?' do
378
424
  # Expects a JSON-encoded hash of id: jid, and queue: queue_name
379
425
  data = JSON.parse(request.body.read)
380
- if data["id"].nil?
381
- halt 400, "Need id"
426
+ if data['id'].nil?
427
+ halt 400, 'Need id'
382
428
  else
383
- job = client.jobs[data["id"]]
429
+ job = client.jobs[data['id']]
384
430
  if job.nil?
385
- halt 404, "Could not find job"
431
+ halt 404, 'Could not find job'
386
432
  else
387
- queue = job.raw_queue_history[-1]["q"]
388
- job.move(queue)
389
- return json({ :id => data["id"], :queue => queue})
433
+ job.requeue(job.queue_name)
434
+ return json({ id: data['id'], queue: job.queue_name })
390
435
  end
391
436
  end
392
437
  end
393
438
 
394
439
  # Retry all the failures of a particular type
395
- post "/retryall/?" do
440
+ post '/retryall/?' do
396
441
  # Expects a JSON-encoded hash of type: failure-type
397
442
  data = JSON.parse(request.body.read)
398
- if data["type"].nil?
399
- halt 400, "Neet type"
443
+ if data['type'].nil?
444
+ halt 400, 'Neet type'
400
445
  else
401
- return json(client.jobs.failed(data["type"], 0, 500)['jobs'].map do |job|
402
- queue = job.raw_queue_history[-1]["q"]
403
- job.move(queue)
404
- { :id => job.jid, :queue => queue}
405
- end)
446
+ jobs = client.jobs.failed(data['type'], 0, 500)['jobs']
447
+ results = jobs.map do |job|
448
+ job.requeue(job.queue_name)
449
+ { id: job.jid, queue: job.queue_name }
450
+ end
451
+ return json(results)
406
452
  end
407
453
  end
408
454
 
409
- post "/cancel/?" do
455
+ post '/cancel/?' do
410
456
  # Expects a JSON-encoded array of job ids to cancel
411
- jobs = JSON.parse(request.body.read).map { |jid| client.jobs[jid] }.select { |j| not j.nil? }
457
+ jobs = JSON.parse(request.body.read).map { |jid| client.jobs[jid] }
458
+ jobs.compact!
412
459
  # Go ahead and cancel all the jobs!
413
460
  jobs.each do |job|
414
- job.cancel()
461
+ job.cancel
415
462
  end
416
463
 
417
464
  if request.xhr?
418
- return json({ :canceled => jobs.map { |job| job.jid } })
465
+ return json({ canceled: jobs.map { |job| job.jid } })
419
466
  else
420
467
  redirect to(request.referrer)
421
468
  end
422
469
  end
423
470
 
424
- post "/cancelall/?" do
471
+ post '/cancelall/?' do
425
472
  # Expects a JSON-encoded hash of type: failure-type
426
473
  data = JSON.parse(request.body.read)
427
- if data["type"].nil?
428
- halt 400, "Neet type"
474
+ if data['type'].nil?
475
+ halt 400, 'Neet type'
429
476
  else
430
- return json(client.jobs.failed(data["type"])['jobs'].map do |job|
431
- job.cancel()
432
- { :id => job.jid }
477
+ return json(client.jobs.failed(data['type'])['jobs'].map do |job|
478
+ job.cancel
479
+ { id: job.jid }
433
480
  end)
434
481
  end
435
482
  end
436
483
 
437
484
  # start the server if ruby file executed directly
438
- run! if app_file == $0
485
+ run! if app_file == $PROGRAM_NAME
439
486
  end
440
487
  end