resque-state 1.0.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.
- checksums.yaml +7 -0
- data/.travis.yml +4 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +102 -0
- data/LICENSE +21 -0
- data/README.rdoc +208 -0
- data/Rakefile +48 -0
- data/examples/sleep_job.rb +36 -0
- data/init.rb +1 -0
- data/lib/resque/job_with_state.rb +5 -0
- data/lib/resque/plugins/state/hash.rb +326 -0
- data/lib/resque/plugins/state.rb +289 -0
- data/lib/resque/server/views/state.erb +91 -0
- data/lib/resque/server/views/state_styles.erb +104 -0
- data/lib/resque/server/views/statuses.erb +79 -0
- data/lib/resque/state.rb +8 -0
- data/lib/resque/state_server.rb +85 -0
- data/lib/resque-state.rb +1 -0
- data/resque-state.gemspec +65 -0
- data/test/test_helper.rb +100 -0
- data/test/test_resque_plugins_state.rb +426 -0
- data/test/test_resque_plugins_state_hash.rb +255 -0
- metadata +99 -0
@@ -0,0 +1,104 @@
|
|
1
|
+
<style type="text/css" media="screen">
|
2
|
+
th.progress {
|
3
|
+
width: 100px;
|
4
|
+
}
|
5
|
+
td.progress {
|
6
|
+
position: relative;
|
7
|
+
width: 100px;
|
8
|
+
display:block;
|
9
|
+
}
|
10
|
+
td.status {
|
11
|
+
font-weight: bold;
|
12
|
+
}
|
13
|
+
td.progress .progress-bar {
|
14
|
+
position: absolute;
|
15
|
+
top: 0px;
|
16
|
+
left: 0px;
|
17
|
+
background: #999;
|
18
|
+
display:block;
|
19
|
+
height: 100%;
|
20
|
+
z-index: 0;
|
21
|
+
opacity: 0.5;
|
22
|
+
-moz-opacity: 0.5;
|
23
|
+
-webkit-opacity: 0.5;
|
24
|
+
}
|
25
|
+
td.progress .progress-pct {
|
26
|
+
z-index: 10;
|
27
|
+
color: #333;
|
28
|
+
}
|
29
|
+
table.vertically-top td {
|
30
|
+
vertical-align: text-top;
|
31
|
+
}
|
32
|
+
table.vertically-top tr {
|
33
|
+
border: 1px solid #ccc;
|
34
|
+
}
|
35
|
+
|
36
|
+
.status-holder {
|
37
|
+
background: #F7F7F7;
|
38
|
+
border: 1px solid #E5E5E5;
|
39
|
+
padding: 20px;
|
40
|
+
font-size: 110%;
|
41
|
+
margin-bottom: 40px;
|
42
|
+
}
|
43
|
+
.status-progress {
|
44
|
+
width: 100%;
|
45
|
+
height: 30px;
|
46
|
+
border: 1px solid #CCC;
|
47
|
+
background: #E5E5E5;
|
48
|
+
position:relative;
|
49
|
+
margin: 5px 0px;
|
50
|
+
}
|
51
|
+
.status-progress-bar {
|
52
|
+
position:absolute;
|
53
|
+
top: 0px;
|
54
|
+
left: 0px;
|
55
|
+
height: 30px;
|
56
|
+
background: #CCC;
|
57
|
+
}
|
58
|
+
.status-progress p {
|
59
|
+
position:absolute;
|
60
|
+
top: 5px;
|
61
|
+
left: 10px;
|
62
|
+
z-index: 15;
|
63
|
+
display:block;
|
64
|
+
color: #FFF;
|
65
|
+
padding: 0px;
|
66
|
+
font-weight: bold;
|
67
|
+
}
|
68
|
+
.status-message {
|
69
|
+
font-weight: bold;
|
70
|
+
}
|
71
|
+
.status-time {
|
72
|
+
font-size: 70%;
|
73
|
+
padding: 10px 0px;
|
74
|
+
color: #999;
|
75
|
+
}
|
76
|
+
.status-progress-bar.status-completed {
|
77
|
+
background:#61BF55;
|
78
|
+
}
|
79
|
+
.status-progress-bar.status-failed {
|
80
|
+
background: #E47E74;
|
81
|
+
}
|
82
|
+
.status-progress-bar.status-working {
|
83
|
+
background: #528499;
|
84
|
+
}
|
85
|
+
.status-progress-bar.status-killed {
|
86
|
+
background: #B84F16;
|
87
|
+
}
|
88
|
+
.status-completed {
|
89
|
+
color:#61BF55;
|
90
|
+
}
|
91
|
+
.status-failed {
|
92
|
+
color: #E47E74;
|
93
|
+
}
|
94
|
+
.status-working {
|
95
|
+
color: #528499;
|
96
|
+
}
|
97
|
+
.status-killed {
|
98
|
+
color: #B84F16;
|
99
|
+
}
|
100
|
+
#main a.kill:link, #main a.kill:visited {
|
101
|
+
color: #B84F16;
|
102
|
+
font-weight: bold;
|
103
|
+
}
|
104
|
+
</style>
|
@@ -0,0 +1,79 @@
|
|
1
|
+
<%= status_view :state_styles, :layout => false %>
|
2
|
+
|
3
|
+
<h1 class='wi'>Statuses</h1>
|
4
|
+
|
5
|
+
<%unless @statuses.empty?%>
|
6
|
+
<form method="POST" action="<%= u(:statuses) %>/clear" class='clear-failed'>
|
7
|
+
<input type='submit' name='' value='Clear Statuses' onclick='return confirm("Are you absolutely sure? This cannot be undone.");' />
|
8
|
+
</form>
|
9
|
+
<form method="POST" action="<%= u(:statuses) %>/clear/completed" class='clear-failed'>
|
10
|
+
<input type='submit' name='' value='Clear Completed Statuses' onclick='return confirm("Are you absolutely sure? This cannot be undone.");' />
|
11
|
+
</form>
|
12
|
+
<form method="POST" action="<%= u(:statuses) %>/clear/failed" class='clear-failed'>
|
13
|
+
<input type='submit' name='' value='Clear Failed Statuses' onclick='return confirm("Are you absolutely sure? This cannot be undone.");' />
|
14
|
+
</form>
|
15
|
+
<%end%>
|
16
|
+
|
17
|
+
<p class='intro'>These are recent jobs created with the Resque::Plugins::State class</p>
|
18
|
+
<table class="vertically-top">
|
19
|
+
<tr>
|
20
|
+
<th>ID</th>
|
21
|
+
<th>Name</th>
|
22
|
+
<th>Status</th>
|
23
|
+
<th>Last Updated</th>
|
24
|
+
<th class="progress">% Complete</th>
|
25
|
+
<th>Message</th>
|
26
|
+
<th>Kill</th>
|
27
|
+
</tr>
|
28
|
+
<% unless @statuses.empty? %>
|
29
|
+
<% @statuses.each do |status| %>
|
30
|
+
<tr>
|
31
|
+
<td><a href="<%= u(:statuses) %>/<%= status.uuid %>"><%= status.uuid %></a></td>
|
32
|
+
<td><%= status.name %></td>
|
33
|
+
<td class="status status-<%= status.status %>"><%= status.status %></td>
|
34
|
+
<td class="time"><%= status.time.strftime("%Y/%m/%d %H:%M:%S %z") %></td>
|
35
|
+
<td class="progress">
|
36
|
+
<div class="progress-bar" style="width:<%= status.pct_complete %>%"> </div>
|
37
|
+
<div class="progress-pct"><%= status.pct_complete ? "#{status.pct_complete}%" : '' %></div>
|
38
|
+
</td>
|
39
|
+
<td><%= status.message %></td>
|
40
|
+
<td><% if status.killable? %><a href="<%= u(:statuses) %>/<%= status.uuid %>/kill" class="kill">Kill</a><% end %></td>
|
41
|
+
</tr>
|
42
|
+
<% end %>
|
43
|
+
<% else %>
|
44
|
+
<tr>
|
45
|
+
<td colspan="7" class='no-data'>No Statuses right now...</td>
|
46
|
+
</tr>
|
47
|
+
<% end %>
|
48
|
+
</table>
|
49
|
+
|
50
|
+
<% unless @statuses.empty? %>
|
51
|
+
<%= partial :next_more, :start => @start, :size => @size, :per_page => per_page %>
|
52
|
+
<% end %>
|
53
|
+
|
54
|
+
<%= status_poll(@start) %>
|
55
|
+
|
56
|
+
<script type="text/javascript" charset="utf-8">
|
57
|
+
jQuery(function($) {
|
58
|
+
|
59
|
+
$('a.kill').click(function(e) {
|
60
|
+
e.preventDefault();
|
61
|
+
var $link = $(this),
|
62
|
+
url = $link.attr('href'),
|
63
|
+
confirmed = confirm("Are you sure you want to kill this job? There is no undo.");
|
64
|
+
if (confirmed) {
|
65
|
+
$link.animate({opacity: 0.5});
|
66
|
+
$.ajax({
|
67
|
+
url: url,
|
68
|
+
type: 'post',
|
69
|
+
success: function() {
|
70
|
+
$link.remove();
|
71
|
+
}
|
72
|
+
});
|
73
|
+
} else {
|
74
|
+
return false
|
75
|
+
}
|
76
|
+
});
|
77
|
+
|
78
|
+
});
|
79
|
+
</script>
|
data/lib/resque/state.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'resque/server'
|
2
|
+
require 'resque-state'
|
3
|
+
|
4
|
+
module Resque
|
5
|
+
## Resque Server plugin for Resque Status
|
6
|
+
module StateServer
|
7
|
+
VIEW_PATH = File.join(File.dirname(__FILE__), 'server', 'views')
|
8
|
+
PER_PAGE = 50
|
9
|
+
|
10
|
+
def self.registered(app)
|
11
|
+
app.get '/state' do
|
12
|
+
@start = params[:start].to_i
|
13
|
+
@end = @start + (params[:per_page] || per_page) - 1
|
14
|
+
@statuses = Resque::Plugins::State::Hash.statuses(@start, @end)
|
15
|
+
@size = Resque::Plugins::State::Hash.count
|
16
|
+
status_view(:statuses)
|
17
|
+
end
|
18
|
+
|
19
|
+
app.get '/state/:id.js' do
|
20
|
+
@status = Resque::Plugins::State::Hash.get(params[:id])
|
21
|
+
content_type :js
|
22
|
+
@status.json
|
23
|
+
end
|
24
|
+
|
25
|
+
app.get '/state/:id' do
|
26
|
+
@status = Resque::Plugins::State::Hash.get(params[:id])
|
27
|
+
status_view(:status)
|
28
|
+
end
|
29
|
+
|
30
|
+
app.post '/state/:id/kill' do
|
31
|
+
Resque::Plugins::State::Hash.kill(params[:id])
|
32
|
+
redirect u(:statuses)
|
33
|
+
end
|
34
|
+
|
35
|
+
app.post '/state/clear' do
|
36
|
+
Resque::Plugins::State::Hash.clear
|
37
|
+
redirect u(:statuses)
|
38
|
+
end
|
39
|
+
|
40
|
+
app.post '/state/clear/completed' do
|
41
|
+
Resque::Plugins::State::Hash.clear_completed
|
42
|
+
redirect u(:statuses)
|
43
|
+
end
|
44
|
+
|
45
|
+
app.post '/state/clear/failed' do
|
46
|
+
Resque::Plugins::State::Hash.clear_failed
|
47
|
+
redirect u(:statuses)
|
48
|
+
end
|
49
|
+
|
50
|
+
app.get '/state.poll' do
|
51
|
+
content_type 'text/plain'
|
52
|
+
@polling = true
|
53
|
+
|
54
|
+
@start = params[:start].to_i
|
55
|
+
@end = @start + (params[:per_page] || per_page) - 1
|
56
|
+
@statuses = Resque::Plugins::State::Hash.statuses(@start, @end)
|
57
|
+
@size = Resque::Plugins::State::Hash.count
|
58
|
+
status_view(:statuses, layout: false)
|
59
|
+
end
|
60
|
+
|
61
|
+
app.helpers do
|
62
|
+
def per_page
|
63
|
+
PER_PAGE
|
64
|
+
end
|
65
|
+
|
66
|
+
def status_view(filename, options = {}, locals = {})
|
67
|
+
erb(File.read(File.join(::Resque::StateServer::VIEW_PATH, "#{filename}.erb")), options, locals)
|
68
|
+
end
|
69
|
+
|
70
|
+
def status_poll(start)
|
71
|
+
if @polling
|
72
|
+
text = "Last Updated: #{Time.now.strftime('%H:%M:%S')}"
|
73
|
+
else
|
74
|
+
text = "<a href='#{u(request.path_info)}.poll?start=#{start}' rel='poll'>Live Poll</a>"
|
75
|
+
end
|
76
|
+
"<p class='poll'>#{text}</p>"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
app.tabs << 'State'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Resque::Server.register Resque::StateServer
|
data/lib/resque-state.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/resque/state"
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: resque-state 1.0.0 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "resque-state"
|
9
|
+
s.version = "1.0.0"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib"]
|
13
|
+
s.authors = ["Aaron Quint", "Nathan V"]
|
14
|
+
s.date = "2016-08-27"
|
15
|
+
s.description = "resque-state is an extension to the resque queue system that provides simple trackable jobs. It provides a Resque::Plugins::State::Hash class which can set/get the statuses of jobs and a Resque::Plugins::State class that, when included, provides easily trackable/killable/pausable jobs."
|
16
|
+
s.email = "nathan.v@gmail.com"
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE",
|
19
|
+
"README.rdoc"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".travis.yml",
|
23
|
+
"Gemfile",
|
24
|
+
"Gemfile.lock",
|
25
|
+
"LICENSE",
|
26
|
+
"README.rdoc",
|
27
|
+
"Rakefile",
|
28
|
+
"examples/sleep_job.rb",
|
29
|
+
"init.rb",
|
30
|
+
"lib/resque-state.rb",
|
31
|
+
"lib/resque/job_with_state.rb",
|
32
|
+
"lib/resque/plugins/state.rb",
|
33
|
+
"lib/resque/plugins/state/hash.rb",
|
34
|
+
"lib/resque/server/views/state.erb",
|
35
|
+
"lib/resque/server/views/state_styles.erb",
|
36
|
+
"lib/resque/server/views/statuses.erb",
|
37
|
+
"lib/resque/state.rb",
|
38
|
+
"lib/resque/state_server.rb",
|
39
|
+
"resque-state.gemspec",
|
40
|
+
"test/test_helper.rb",
|
41
|
+
"test/test_resque_plugins_state.rb",
|
42
|
+
"test/test_resque_plugins_state_hash.rb"
|
43
|
+
]
|
44
|
+
s.homepage = "http://github.com/nathan-v/resque-state"
|
45
|
+
s.licenses = ["MIT"]
|
46
|
+
s.rubyforge_project = "nathan-v"
|
47
|
+
s.rubygems_version = "2.5.1"
|
48
|
+
s.summary = "resque-state is an extension to the resque queue system that provides simple trackable jobs."
|
49
|
+
|
50
|
+
if s.respond_to? :specification_version then
|
51
|
+
s.specification_version = 4
|
52
|
+
|
53
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
54
|
+
s.add_runtime_dependency(%q<resque>, ["~> 1.19"])
|
55
|
+
s.add_development_dependency(%q<jeweler>, ["~> 2.1"])
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<resque>, ["~> 1.19"])
|
58
|
+
s.add_dependency(%q<jeweler>, ["~> 2.1"])
|
59
|
+
end
|
60
|
+
else
|
61
|
+
s.add_dependency(%q<resque>, ["~> 1.19"])
|
62
|
+
s.add_dependency(%q<jeweler>, ["~> 2.1"])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
if ENV['COVERAGE']
|
2
|
+
require 'simplecov'
|
3
|
+
SimpleCov.start do
|
4
|
+
add_filter 'test'
|
5
|
+
command_name 'Mintest'
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'bundler/setup'
|
10
|
+
require 'resque-state'
|
11
|
+
require 'minitest/autorun'
|
12
|
+
require 'mocha/setup'
|
13
|
+
require 'fakeredis'
|
14
|
+
require 'codeclimate-test-reporter'
|
15
|
+
|
16
|
+
CodeClimate::TestReporter.start
|
17
|
+
|
18
|
+
Resque.redis = Redis.new
|
19
|
+
|
20
|
+
#### Fixtures
|
21
|
+
|
22
|
+
class WorkingJob
|
23
|
+
include Resque::Plugins::State
|
24
|
+
|
25
|
+
def perform
|
26
|
+
total = options['num']
|
27
|
+
(1..total).each do |num|
|
28
|
+
at(num, total, "At #{num}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class ErrorJob
|
34
|
+
include Resque::Plugins::State
|
35
|
+
|
36
|
+
def perform
|
37
|
+
raise "I'm a bad little job"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class ErrorJobOnFailure
|
42
|
+
include Resque::Plugins::State
|
43
|
+
|
44
|
+
def perform
|
45
|
+
raise "I'm a bad little job"
|
46
|
+
end
|
47
|
+
|
48
|
+
def on_failure(_e, *_args)
|
49
|
+
failed("I'm such a terrible failure")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class KillableJob
|
54
|
+
include Resque::Plugins::State
|
55
|
+
|
56
|
+
def perform
|
57
|
+
Resque.redis.set("#{uuid}:iterations", 0)
|
58
|
+
100.times do |num|
|
59
|
+
Resque.redis.incr("#{uuid}:iterations")
|
60
|
+
at(num, 100, "At #{num} of 100")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class SleeperJob
|
66
|
+
include Resque::Plugins::State
|
67
|
+
|
68
|
+
def perform
|
69
|
+
@testing = true
|
70
|
+
Resque.redis.set("#{uuid}:iterations", 0)
|
71
|
+
100.times do |num|
|
72
|
+
Resque.redis.incr("#{uuid}:iterations")
|
73
|
+
at(num, 100, "At #{num} of 100")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class BasicJob
|
79
|
+
include Resque::Plugins::State
|
80
|
+
end
|
81
|
+
|
82
|
+
class FailureJob
|
83
|
+
include Resque::Plugins::State
|
84
|
+
|
85
|
+
def perform
|
86
|
+
failed("I'm such a failure")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class NeverQueuedJob
|
91
|
+
include Resque::Plugins::State
|
92
|
+
|
93
|
+
def self.before_enqueue(*_args)
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
97
|
+
def perform
|
98
|
+
# will never get called
|
99
|
+
end
|
100
|
+
end
|