resque-mongo 1.4.0 → 1.8.1

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 (44) hide show
  1. data/CONTRIBUTORS +24 -6
  2. data/HISTORY.md +65 -0
  3. data/README.markdown +34 -5
  4. data/Rakefile +1 -1
  5. data/bin/resque +2 -2
  6. data/bin/resque-web +6 -1
  7. data/deps.rip +2 -2
  8. data/docs/HOOKS.md +121 -0
  9. data/docs/PLUGINS.md +93 -0
  10. data/examples/demo/Rakefile +5 -0
  11. data/examples/monit/resque.monit +6 -0
  12. data/lib/resque.rb +94 -7
  13. data/lib/resque/errors.rb +3 -0
  14. data/lib/resque/failure.rb +3 -0
  15. data/lib/resque/failure/base.rb +3 -0
  16. data/lib/resque/failure/hoptoad.rb +29 -19
  17. data/lib/resque/failure/mongo.rb +10 -1
  18. data/lib/resque/helpers.rb +8 -2
  19. data/lib/resque/job.rb +107 -2
  20. data/lib/resque/plugin.rb +46 -0
  21. data/lib/resque/server.rb +30 -11
  22. data/lib/resque/server/public/ranger.js +50 -7
  23. data/lib/resque/server/public/style.css +8 -1
  24. data/lib/resque/server/test_helper.rb +19 -0
  25. data/lib/resque/server/views/failed.erb +17 -3
  26. data/lib/resque/server/views/key_sets.erb +20 -0
  27. data/lib/resque/server/views/{key.erb → key_string.erb} +2 -8
  28. data/lib/resque/server/views/queues.erb +5 -2
  29. data/lib/resque/server/views/stats.erb +2 -2
  30. data/lib/resque/server/views/workers.erb +1 -1
  31. data/lib/resque/server/views/working.erb +2 -0
  32. data/lib/resque/tasks.rb +1 -1
  33. data/lib/resque/version.rb +1 -1
  34. data/lib/resque/worker.rb +54 -15
  35. data/tasks/redis.rake +53 -29
  36. data/test/job_hooks_test.rb +302 -0
  37. data/test/job_plugins_test.rb +209 -0
  38. data/test/plugin_test.rb +116 -0
  39. data/test/resque-mongo_benchmark.rb +62 -0
  40. data/test/resque-web_test.rb +54 -0
  41. data/test/resque_test.rb +34 -0
  42. data/test/test_helper.rb +15 -0
  43. data/test/worker_test.rb +62 -2
  44. metadata +58 -23
@@ -0,0 +1,46 @@
1
+ module Resque
2
+ module Plugin
3
+ extend self
4
+
5
+ LintError = Class.new(RuntimeError)
6
+
7
+ # Ensure that your plugin conforms to good hook naming conventions.
8
+ #
9
+ # Resque::Plugin.lint(MyResquePlugin)
10
+ def lint(plugin)
11
+ hooks = before_hooks(plugin) + around_hooks(plugin) + after_hooks(plugin)
12
+
13
+ hooks.each do |hook|
14
+ if hook =~ /perform$/
15
+ raise LintError, "#{plugin}.#{hook} is not namespaced"
16
+ end
17
+ end
18
+
19
+ failure_hooks(plugin).each do |hook|
20
+ if hook =~ /failure$/
21
+ raise LintError, "#{plugin}.#{hook} is not namespaced"
22
+ end
23
+ end
24
+ end
25
+
26
+ # Given an object, returns a list `before_perform` hook names.
27
+ def before_hooks(job)
28
+ job.methods.grep(/^before_perform/).sort
29
+ end
30
+
31
+ # Given an object, returns a list `around_perform` hook names.
32
+ def around_hooks(job)
33
+ job.methods.grep(/^around_perform/).sort
34
+ end
35
+
36
+ # Given an object, returns a list `after_perform` hook names.
37
+ def after_hooks(job)
38
+ job.methods.grep(/^after_perform/).sort
39
+ end
40
+
41
+ # Given an object, returns a list `on_failure` hook names.
42
+ def failure_hooks(job)
43
+ job.methods.grep(/^on_failure/).sort
44
+ end
45
+ end
46
+ end
@@ -20,7 +20,7 @@ module Resque
20
20
  end
21
21
 
22
22
  def current_page
23
- url request.path_info.sub('/','').downcase
23
+ url request.path_info.sub('/','')
24
24
  end
25
25
 
26
26
  def url(*path_parts)
@@ -32,13 +32,14 @@ module Resque
32
32
  request.env['SCRIPT_NAME']
33
33
  end
34
34
 
35
- def class_if_current(page = '')
36
- 'class="current"' if current_page.include? page.to_s
35
+ def class_if_current(path = '')
36
+ 'class="current"' if current_page[0, path.size] == path
37
37
  end
38
38
 
39
39
  def tab(name)
40
40
  dname = name.to_s.downcase
41
- "<li #{class_if_current(dname)}><a href='#{url dname}'>#{name}</a></li>"
41
+ path = url(dname)
42
+ "<li #{class_if_current(path)}><a href='#{path}'>#{name}</a></li>"
42
43
  end
43
44
 
44
45
  def tabs
@@ -55,19 +56,23 @@ module Resque
55
56
  Resque.redis.scard(key)
56
57
  when 'string'
57
58
  Resque.redis.get(key).length
59
+ when 'zset'
60
+ Resque.redis.zcard(key)
58
61
  end
59
62
  end
60
63
 
61
- def redis_get_value_as_array(key)
64
+ def redis_get_value_as_array(key, start=0)
62
65
  case Resque.redis.type(key)
63
66
  when 'none'
64
67
  []
65
68
  when 'list'
66
- Resque.redis.lrange(key, 0, 20)
69
+ Resque.redis.lrange(key, start, start + 20)
67
70
  when 'set'
68
- Resque.redis.smembers(key)
71
+ Resque.redis.smembers(key)[start..(start + 20)]
69
72
  when 'string'
70
73
  [Resque.redis.get(key)]
74
+ when 'zset'
75
+ Resque.redis.zrange(key, start, start + 20)
71
76
  end
72
77
  end
73
78
 
@@ -85,7 +90,7 @@ module Resque
85
90
  ensure
86
91
  @partial = false
87
92
  end
88
-
93
+
89
94
  def poll
90
95
  if @polling
91
96
  text = "Last Updated: #{Time.now.strftime("%H:%M:%S")}"
@@ -94,14 +99,14 @@ module Resque
94
99
  end
95
100
  "<p class='poll'>#{text}</p>"
96
101
  end
97
-
102
+
98
103
  end
99
104
 
100
105
  def show(page, layout = true)
101
106
  begin
102
107
  erb page.to_sym, {:layout => layout}, :resque => Resque
103
108
  rescue Errno::ECONNREFUSED
104
- erb :error, {:layout => false}, :error => "Can't connect to Redis! (#{Resque.redis.server})"
109
+ erb :error, {:layout => false}, :error => "Can't connect to Mongo! (#{Resque.mongo.server})"
105
110
  end
106
111
  end
107
112
 
@@ -120,6 +125,11 @@ module Resque
120
125
  end
121
126
  end
122
127
 
128
+ post "/queues/:id/remove" do
129
+ Resque.remove_queue(params[:id])
130
+ redirect u('queues')
131
+ end
132
+
123
133
  %w( overview workers ).each do |page|
124
134
  get "/#{page}.poll" do
125
135
  content_type "text/plain"
@@ -135,11 +145,20 @@ module Resque
135
145
  show :failed
136
146
  end
137
147
  end
138
-
148
+
139
149
  post "/failed/clear" do
140
150
  Resque::Failure.clear
141
151
  redirect u('failed')
142
152
  end
153
+
154
+ get "/failed/requeue/:index" do
155
+ Resque::Failure.requeue(params[:index])
156
+ if request.xhr?
157
+ return Resque::Failure.all(params[:index])['retried_at']
158
+ else
159
+ redirect u('failed')
160
+ end
161
+ end
143
162
 
144
163
  get "/stats" do
145
164
  redirect url("/stats/resque")
@@ -1,24 +1,67 @@
1
- var poll_interval = 2;
2
-
3
1
  $(function() {
2
+ var poll_interval = 2
3
+
4
+ var relatizer = function(){
5
+ var dt = $(this).text(), relatized = $.relatizeDate(this)
6
+ if ($(this).parents("a").length > 0 || $(this).is("a")) {
7
+ $(this).relatizeDate()
8
+ if (!$(this).attr('title')) {
9
+ $(this).attr('title', dt)
10
+ }
11
+ } else {
12
+ $(this)
13
+ .text('')
14
+ .append( $('<a href="#" class="toggle_format" title="' + dt + '" />')
15
+ .append('<span class="date_time">' + dt +
16
+ '</span><span class="relatized_time">' +
17
+ relatized + '</span>') )
18
+ }
19
+ };
20
+
21
+ $('.time').each(relatizer);
22
+
23
+ $('.time a.toggle_format .date_time').hide()
24
+
25
+ var format_toggler = function(){
26
+ $('.time a.toggle_format span').toggle()
27
+ $(this).attr('title', $('span:hidden',this).text())
28
+ return false
29
+ };
30
+
31
+ $('.time a.toggle_format').click(format_toggler);
4
32
 
5
- $('.time').relatizeDate()
6
33
  $('.backtrace').click(function() {
7
34
  $(this).next().toggle()
8
35
  return false
9
36
  })
10
-
37
+
11
38
  $('a[rel=poll]').click(function() {
12
39
  var href = $(this).attr('href')
13
40
  $(this).parent().text('Starting...')
14
41
  $("#main").addClass('polling')
42
+
15
43
  setInterval(function() {
16
- $.ajax({dataType:'text', type:'get', url:href, success:function(data) {
17
- $('#main').html(data)
44
+ $.ajax({dataType: 'text', type: 'get', url: href, success: function(data) {
45
+ $('#main').html(data)
18
46
  $('#main .time').relatizeDate()
19
47
  }})
20
48
  }, poll_interval * 1000)
49
+
21
50
  return false
22
51
  })
23
-
52
+
53
+ $('ul.failed a[rel=retry]').click(function() {
54
+ var href = $(this).attr('href');
55
+ $(this).text('Retrying...');
56
+ var parent = $(this).parent();
57
+ $.ajax({dataType: 'text', type: 'get', url: href, success: function(data) {
58
+ parent.html('Retried <b><span class="time">' + data + '</span></b>');
59
+ relatizer.apply($('.time', parent));
60
+ $('.date_time', parent).hide();
61
+ $('a.toggle_format span', parent).click(format_toggler);
62
+ }});
63
+ return false;
64
+ })
65
+
66
+
24
67
  })
@@ -8,6 +8,8 @@ body { padding:0; margin:0; }
8
8
  .header ul li a:hover { background:#333;}
9
9
  .header ul li.current a { background:#429234; font-weight:bold; color:#fff;}
10
10
 
11
+ .header .namespace { position: absolute; right: 75px; top: 10px; color: #7A7A7A; }
12
+
11
13
  .subnav { padding:2px 5% 7px 5%; background:#429234; font-size:90%;}
12
14
  .subnav li { display:inline;}
13
15
  .subnav li a { color:#fff; text-decoration:none; margin-right:10px; display:inline-block; background:#55ad46; padding:5px; -webkit-border-radius:3px; -moz-border-radius:3px;}
@@ -31,6 +33,8 @@ body { padding:0; margin:0; }
31
33
 
32
34
  #main table.queues { width:40%;}
33
35
  #main table.queues td.queue { font-weight:bold; width:50%;}
36
+ #main table.queues tr.failed td { border-top:2px solid; font-size:90%; }
37
+
34
38
  #main table.queues tr.failed td { background:#ebffed; border-top:2px solid #6fd380; font-size:90%; color:#6fd380;}
35
39
  #main table.queues tr.failed td a{ color:#6fd380;}
36
40
 
@@ -64,6 +68,7 @@ body { padding:0; margin:0; }
64
68
  #main ul.failed li {background:-webkit-gradient(linear, left top, left bottom, from(#efefef), to(#fff)) #efefef; margin-top:10px; padding:10px; overflow:hidden; -webkit-border-radius:5px; border:1px solid #ccc; }
65
69
  #main ul.failed li dl dt {font-size:80%; color:#999; width:60px; float:left; padding-top:1px; text-align:right;}
66
70
  #main ul.failed li dl dd {margin-bottom:10px; margin-left:70px;}
71
+ #main ul.failed li dl dd .retry { float: right; }
67
72
  #main ul.failed li dl dd code, #main ul.failed li dl dd pre { font-family:Monaco, "Courier New", monospace; font-size:90%;}
68
73
  #main ul.failed li dl dd.error a {font-family:Monaco, "Courier New", monospace; font-size:90%; }
69
74
  #main ul.failed li dl dd.error pre { margin-top:3px; line-height:1.3;}
@@ -72,4 +77,6 @@ body { padding:0; margin:0; }
72
77
  #main p.pagination a.less { float:left;}
73
78
  #main p.pagination a.more { float:right;}
74
79
 
75
- #main form.clear-failed {float:right; margin-top:-10px;}
80
+ #main form {float:right; margin-top:-10px;}
81
+
82
+ #main .time a.toggle_format {text-decoration:none;}
@@ -0,0 +1,19 @@
1
+ require 'rack/test'
2
+ require 'resque/server'
3
+
4
+ module Resque
5
+ module TestHelper
6
+ class Test::Unit::TestCase
7
+ include Rack::Test::Methods
8
+ def app
9
+ Resque::Server.new
10
+ end
11
+
12
+ def self.should_respond_with_success
13
+ test "should respond with success" do
14
+ assert last_response.ok?
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,6 @@
1
1
  <%start = params[:start].to_i %>
2
- <%failed = Resque::Failure.all(start, 20)%>
2
+ <%failed = [Resque::Failure.all(start, 20)].flatten %>
3
+ <% index = 0 %>
3
4
 
4
5
  <h1>Failed Jobs</h1>
5
6
  <%unless failed.empty?%>
@@ -12,18 +13,30 @@
12
13
 
13
14
  <ul class='failed'>
14
15
  <%for job in failed%>
16
+ <% index += 1 %>
15
17
  <li>
16
18
  <dl>
17
19
  <dt>Worker</dt>
18
- <dd><a href="<%= url(:workers, job['worker']) %>"><%= job['worker'].split(':')[0...2].join(':') %></a> on <b class='queue-tag'><%= job['queue'] %></b > at <b><span class="time"><%= job['failed_at'] %></span></b></dd>
20
+ <dd>
21
+ <a href="<%= url(:workers, job['worker']) %>"><%= job['worker'].split(':')[0...2].join(':') %></a> on <b class='queue-tag'><%= job['queue'] %></b > at <b><span class="time"><%= job['failed_at'] %></span></b>
22
+ <div class='retry'>
23
+ <% if job['retried_at'] %>
24
+ Retried <b><span class="time"><%= job['retried_at'] %></span></b>
25
+ <% else %>
26
+ <a href="<%= u "failed/requeue/#{start + index - 1}" %>" rel="retry">Retry</a>
27
+ <% end %>
28
+ </div>
29
+ </dd>
19
30
  <dt>Class</dt>
20
31
  <dd><code><%= job['payload']['class'] %></code></dd>
21
32
  <dt>Arguments</dt>
22
33
  <dd><pre><%=h show_args(job['payload']['args']) %></pre></dd>
34
+ <dt>Exception</td>
35
+ <dd><code><%= job['exception'] %></code></dd>
23
36
  <dt>Error</dt>
24
37
  <dd class='error'>
25
38
  <a href="#" class="backtrace"><%= h(job['error']) %></a>
26
- <pre style='display:none'><%=h job['backtrace'].join("\n") %></pre>
39
+ <pre style='display:none'><%=h job['backtrace'].join("\n") %></pre>
27
40
  </dd>
28
41
  </dl>
29
42
  <div class='r'>
@@ -33,3 +46,4 @@
33
46
  </ul>
34
47
 
35
48
  <%= partial :next_more, :start => start, :size => size %>
49
+
@@ -0,0 +1,20 @@
1
+ <% if key = params[:key] %>
2
+
3
+ <p class='sub'>
4
+ Showing <%= start = params[:start].to_i %> to <%= start + 20 %> of <b><%=size = redis_get_size(key) %></b>
5
+ </p>
6
+
7
+
8
+ <h1>Key "<%= key %>" is a <%= resque.redis.type key %></h1>
9
+ <table>
10
+ <% for row in redis_get_value_as_array(key, start) %>
11
+ <tr>
12
+ <td>
13
+ <%= row %>
14
+ </td>
15
+ </tr>
16
+ <% end %>
17
+ </table>
18
+
19
+ <%= partial :next_more, :start => start, :size => size %>
20
+ <% end %>
@@ -1,17 +1,11 @@
1
1
  <% if key = params[:key] %>
2
-
3
2
  <h1>Key "<%= key %>" is a <%= resque.redis.type key %></h1>
4
3
  <h2>size: <%= redis_get_size(key) %></h2>
5
4
  <table>
6
- <% for row in redis_get_value_as_array(key) %>
7
5
  <tr>
8
6
  <td>
9
- <%= row %>
7
+ <%= redis_get_value_as_array(key) %>
10
8
  </td>
11
9
  </tr>
12
- <% end %>
13
10
  </table>
14
-
15
- <% else %>
16
-
17
- <% end %>
11
+ <% end %>
@@ -3,6 +3,9 @@
3
3
  <% if queue = params[:id] %>
4
4
 
5
5
  <h1>Pending jobs on <span class='hl'><%= queue %></span></h1>
6
+ <form method="POST" action="<%=u "/queues/#{queue}/remove" %>" class='remove-queue'>
7
+ <input type='submit' name='' value='Remove Queue' />
8
+ </form>
6
9
  <p class='sub'>Showing <%= start = params[:start].to_i %> to <%= start + 20 %> of <b><%=size = resque.size(queue)%></b> jobs</p>
7
10
  <table class='jobs'>
8
11
  <tr>
@@ -37,10 +40,10 @@
37
40
  <td class='size'><%= resque.size queue %></td>
38
41
  </tr>
39
42
  <% end %>
40
- <tr class='failed'>
43
+ <tr class="<%= Resque::Failure.count.zero? ? "failed" : "failure" %>">
41
44
  <td class='queue failed'><a class="queue" href="<%= url :failed %>">failed</a></td>
42
45
  <td class='size'><%= Resque::Failure.count %></td>
43
46
  </tr>
44
47
  </table>
45
48
 
46
- <% end %>
49
+ <% end %>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <% if params[:key] %>
4
4
 
5
- <%= partial :key %>
5
+ <%= partial resque.redis.type(params[:key]).eql?("string") ? :key_string : :key_sets %>
6
6
 
7
7
  <% elsif params[:id] == "resque" %>
8
8
 
@@ -59,4 +59,4 @@
59
59
 
60
60
  <% else %>
61
61
 
62
- <% end %>
62
+ <% end %>
@@ -18,7 +18,7 @@
18
18
  <% host, pid, queues = worker.to_s.split(':') %>
19
19
  <td><%= host %></td>
20
20
  <td><%= pid %></td>
21
- <td><span class="time"><%= worker.started %></a></td>
21
+ <td><span class="time"><%= worker.started %></span></td>
22
22
  <td class='queues'><%= queues.split(',').map { |q| '<a class="queue-tag" href="' + u("/queues/#{q}") + '">' + q + '</a>'}.join('') %></td>
23
23
  <td><%= worker.processed %></td>
24
24
  <td><%= worker.failed %></td>
@@ -45,6 +45,8 @@
45
45
 
46
46
  <% for worker in workers.sort_by { |w| w.job['run_at'] ? w.job['run_at'] : '' } %>
47
47
  <% job = worker.job %>
48
+ <% next if worker.idle? %>
49
+
48
50
  <tr>
49
51
  <td class='icon'><img src="<%=u state = worker.state %>.png" alt="<%= state %>" title="<%= state %>"></td>
50
52
  <% host, pid, queues = worker.to_s.split(':') %>
@@ -24,7 +24,7 @@ namespace :resque do
24
24
  worker.work(ENV['INTERVAL'] || 5) # interval, will block
25
25
  end
26
26
 
27
- desc "Start multiple Resque workers"
27
+ desc "Start multiple Resque workers. Should only be used in dev mode."
28
28
  task :workers do
29
29
  threads = []
30
30
 
@@ -1,3 +1,3 @@
1
1
  module Resque
2
- Version = '1.4.0'
2
+ Version = '1.8.1'
3
3
  end