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.
@@ -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 %>%">&nbsp;</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>
@@ -0,0 +1,8 @@
1
+ require 'resque'
2
+
3
+ module Resque
4
+ autoload :JobWithState, "#{File.dirname(__FILE__)}/job_with_state"
5
+ module Plugins
6
+ autoload :State, "#{File.dirname(__FILE__)}/plugins/state"
7
+ end
8
+ end
@@ -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
@@ -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
+
@@ -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