resque-job_history 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,88 @@
1
+ module Resque
2
+ module Plugins
3
+ module JobHistory
4
+ # JobHistory cleanup functions to allow the user to cleanup Redis for histories.
5
+ class HistoryList < HistoryBase
6
+ attr_accessor :list_name
7
+
8
+ def initialize(class_name, list_name)
9
+ super(class_name)
10
+
11
+ @list_name = list_name
12
+ end
13
+
14
+ def add_job(job_id)
15
+ add_to_history
16
+
17
+ job_count = redis.lpush(job_list_key, job_id)
18
+ redis.incr(total_jobs_key)
19
+
20
+ delete_old_jobs(job_count)
21
+
22
+ job_count
23
+ end
24
+
25
+ def remove_job(job_id)
26
+ redis.lrem(job_list_key, 0, job_id)
27
+ end
28
+
29
+ def paged_jobs(page_num = 1, page_size = nil)
30
+ page_size ||= class_page_size
31
+ page_size = Resque::Plugins::JobHistory::HistoryBase::PAGE_SIZE if page_size.to_i < 1
32
+ start = (page_num - 1) * page_size
33
+ start = 0 if start >= num_jobs
34
+
35
+ jobs(start, start + page_size - 1)
36
+ end
37
+
38
+ def jobs(start = 0, stop = -1)
39
+ job_ids(start, stop).map do |job_id|
40
+ Resque::Plugins::JobHistory::Job.new(class_name, job_id)
41
+ end
42
+ end
43
+
44
+ def job_ids(start = 0, stop = -1)
45
+ redis.lrange(job_list_key, start, stop)
46
+ end
47
+
48
+ def num_jobs
49
+ redis.llen(job_list_key)
50
+ end
51
+
52
+ def total
53
+ redis.get(total_jobs_key).to_i
54
+ end
55
+
56
+ def latest_job
57
+ job_id = redis.lrange(job_list_key, 0, 0).first
58
+
59
+ Resque::Plugins::JobHistory::Job.new(class_name, job_id) if job_id
60
+ end
61
+
62
+ private
63
+
64
+ def add_to_history
65
+ redis.sadd(job_history_key, class_name)
66
+ end
67
+
68
+ def delete_old_jobs(job_count)
69
+ max_jobs = class_history_len
70
+
71
+ while job_count > max_jobs
72
+ Resque::Plugins::JobHistory::Job.new(class_name, redis.rpop(job_list_key)).purge
73
+
74
+ job_count -= 1
75
+ end
76
+ end
77
+
78
+ def total_jobs_key
79
+ "#{job_history_base_key}.total_#{list_name}_jobs"
80
+ end
81
+
82
+ def job_list_key
83
+ "#{job_history_base_key}.#{list_name}_jobs"
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,144 @@
1
+ module Resque
2
+ module Plugins
3
+ module JobHistory
4
+ # a class encompassing a single job.
5
+ class Job < HistoryBase
6
+ attr_accessor :job_id
7
+
8
+ def initialize(class_name, job_id)
9
+ super(class_name)
10
+
11
+ @job_id = job_id
12
+ end
13
+
14
+ def job_key
15
+ "#{job_history_base_key}.#{job_id}"
16
+ end
17
+
18
+ def start_time
19
+ stored_values[:start_time].try(:to_time)
20
+ end
21
+
22
+ def finished?
23
+ stored_values[:end_time].present?
24
+ end
25
+
26
+ def succeeded?
27
+ error.blank?
28
+ end
29
+
30
+ def duration
31
+ (end_time || Time.now) - start_time
32
+ end
33
+
34
+ def end_time
35
+ stored_values[:end_time].try(:to_time)
36
+ end
37
+
38
+ def args
39
+ decode_args(stored_values[:args])
40
+ end
41
+
42
+ def error
43
+ stored_values[:error]
44
+ end
45
+
46
+ def start(*args)
47
+ num_jobs = running_list.add_job(job_id)
48
+
49
+ record_num_jobs(num_jobs)
50
+ record_job_start(*args)
51
+ end
52
+
53
+ def finish
54
+ redis.hset(job_key, "end_time", Time.now.utc.to_s)
55
+
56
+ finished_list.add_job(job_id)
57
+ running_list.remove_job(job_id)
58
+ end
59
+
60
+ def failed(exception)
61
+ redis.hset(job_key, "error", exception.message)
62
+
63
+ finish
64
+ end
65
+
66
+ def cancel
67
+ redis.hset(job_key,
68
+ "error",
69
+ "Unknown - Job failed to signal ending after the configured purge time or "\
70
+ "was canceled manually.")
71
+
72
+ finish
73
+ end
74
+
75
+ def retry
76
+ return unless described_class
77
+
78
+ Resque.enqueue described_class, *args
79
+ end
80
+
81
+ def purge
82
+ running_list.remove_job(job_id)
83
+ finished_list.remove_job(job_id)
84
+
85
+ redis.del(job_key)
86
+ end
87
+
88
+ def max_jobs
89
+ redis.get(max_running_key).to_i
90
+ end
91
+
92
+ private
93
+
94
+ def record_job_start(*args)
95
+ redis.hset(job_key, "start_time", Time.now.utc.to_s)
96
+ redis.hset(job_key, "args", encode_args(*args))
97
+ end
98
+
99
+ def stored_values
100
+ unless @stored_values
101
+ @stored_values = redis.hgetall(job_key).with_indifferent_access
102
+ end
103
+
104
+ @stored_values
105
+ end
106
+
107
+ def encode_args(*args)
108
+ Resque.encode(args)
109
+ end
110
+
111
+ def decode_args(args_string)
112
+ Resque.decode(args_string)
113
+ end
114
+
115
+ def record_num_jobs(num_jobs)
116
+ if redis.get(max_running_key).to_i < num_jobs
117
+ redis.set(max_running_key, num_jobs)
118
+ end
119
+
120
+ return unless num_jobs > class_history_len
121
+
122
+ clean_old_running_jobs
123
+ end
124
+
125
+ def max_running_key
126
+ "#{job_history_base_key}.max_jobs"
127
+ end
128
+
129
+ def clean_old_running_jobs
130
+ too_old_time = class_purge_age.ago
131
+
132
+ running_list.jobs.each do |job|
133
+ job_start = job.start_time
134
+
135
+ if job_start.blank? || job_start.to_time < too_old_time
136
+ job.start(*job.args) if job_start.blank?
137
+ job.cancel
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,90 @@
1
+ module Resque
2
+ module Plugins
3
+ module JobHistory
4
+ # A class encompassing tasks about the jobs as a whole.
5
+ #
6
+ # This class gets a list of the classes and can provide a summary for each.
7
+ class JobList < HistoryBase
8
+ def initialize
9
+ super("")
10
+ end
11
+
12
+ def order_param(sort_option, current_sort, current_order)
13
+ current_order ||= "asc"
14
+
15
+ if sort_option == current_sort
16
+ current_order == "asc" ? "desc" : "asc"
17
+ else
18
+ "asc"
19
+ end
20
+ end
21
+
22
+ def job_summaries(sort_key = :class_name,
23
+ sort_order = "asc",
24
+ page_num = 1,
25
+ page_size = Resque::Plugins::JobHistory::HistoryBase::PAGE_SIZE)
26
+ jobs = sorted_job_summaries(sort_key)
27
+
28
+ page_start = (page_num - 1) * page_size
29
+ page_start = 0 if page_start > jobs.length
30
+
31
+ (sort_order == "desc" ? jobs.reverse : jobs)[page_start..page_start + page_size - 1]
32
+ end
33
+
34
+ def job_classes
35
+ redis.smembers(job_history_key)
36
+ end
37
+
38
+ def job_class_summary(class_name)
39
+ history = Resque::Plugins::JobHistory::HistoryBase.new(class_name)
40
+
41
+ running_list = history.running_list
42
+ finished_list = history.finished_list
43
+
44
+ class_summary_hash(class_name, finished_list, running_list)
45
+ end
46
+
47
+ private
48
+
49
+ def latest_job(running_list, finished_list)
50
+ running_list.latest_job || finished_list.latest_job
51
+ end
52
+
53
+ def sorted_job_summaries(sort_key)
54
+ job_classes.map { |class_name| job_class_summary(class_name) }.sort_by do |job_summary|
55
+ summary_sort_value(job_summary, sort_key)
56
+ end
57
+ end
58
+
59
+ def summary_sort_value(job_summary, sort_key)
60
+ case sort_key.to_sym
61
+ when :class_name,
62
+ :running_jobs,
63
+ :finished_jobs,
64
+ :total_finished_jobs,
65
+ :total_run_jobs,
66
+ :max_running_jobs
67
+ job_summary[sort_key.to_sym]
68
+ when :start_time
69
+ job_summary[:last_run].start_time
70
+ when :durration
71
+ job_summary[:last_run].duration
72
+ when :success
73
+ job_summary[:last_run].succeeded?
74
+ end
75
+ end
76
+
77
+ def class_summary_hash(class_name, finished_list, running_list)
78
+ { class_name: class_name,
79
+ class_name_valid: running_list.class_name_valid?,
80
+ running_jobs: running_list.num_jobs,
81
+ finished_jobs: finished_list.num_jobs,
82
+ total_run_jobs: running_list.total,
83
+ total_finished_jobs: finished_list.total,
84
+ max_running_jobs: Resque::Plugins::JobHistory::Job.new(class_name, "").max_jobs,
85
+ last_run: latest_job(running_list, finished_list) }
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,7 @@
1
+ module Resque
2
+ module Plugins
3
+ module JobHistory
4
+ VERSION = "0.0.2"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ .job_history_pagination_block {
2
+ width: 100%;
3
+ display: flex;
4
+ justify-content: space-between;
5
+ margin-bottom: 5px;
6
+ }
7
+
8
+ .job_history_first_page {
9
+ margin-right: 5px;
10
+ }
11
+
12
+ .job_history_prev_page {
13
+ margin-right: 5px;
14
+ margin-left: 5px;
15
+ }
16
+
17
+ .job_history_page {
18
+ margin-right: 5px;
19
+ margin-left: 5px;
20
+ }
21
+
22
+ .job_history_next_page {
23
+ margin-right: 5px;
24
+ margin-left: 5px;
25
+ }
26
+
27
+ .job_history_last_page {
28
+ margin-left: 5px;
29
+ }
@@ -0,0 +1,67 @@
1
+ <div class="job_history_pagination_block">
2
+ <% total_pages = job_list.job_classes.length / page_size %>
3
+ <% total_pages += 1 if job_list.job_classes.length % page_size > 0 %>
4
+ <% page_num = 1 if page_num > total_pages %>
5
+ <% first_page = [1, page_num - 3].max %>
6
+ <% last_page = [total_pages, page_num + 3].min %>
7
+ <% last_page = page_num < 4 ? [total_pages, last_page + (4 - page_num)].min : last_page %>
8
+ <% first_page = page_num > total_pages - 3 ? [1, first_page + ((total_pages - page_num) - 3)].max : first_page %>
9
+
10
+ <% if total_pages > 1 %>
11
+ <div class="job_history_prev_links">
12
+ <a href="job%20history?<%= { sort: @sort_by,
13
+ page_size: page_size,
14
+ page_num: 1,
15
+ order: @sort_order }.to_param %>"
16
+ class="job_history_first_page"
17
+ disabled="<%= first_page > 1 %>">
18
+ &lt;&lt; First
19
+ </a>
20
+
21
+ <a href="job%20history?<%= { sort: @sort_by,
22
+ page_size: page_size,
23
+ page_num: page_num - 1,
24
+ order: @sort_order }.to_param %>"
25
+ class="job_history_prev_page"
26
+ disabled="<%= page_num > 1 %>">
27
+ &lt; Prev
28
+ </a>
29
+ </div>
30
+
31
+ <div class="job_history_pages">
32
+ <% (first_page..last_page).each do |page_number| %>
33
+ <% if page_number != page_num %>
34
+ <a href="job%20history?<%= { sort: @sort_by,
35
+ page_size: page_size,
36
+ page_num: page_number,
37
+ order: @sort_order }.to_param %>"
38
+ class="job_history_page">
39
+ <%= page_number %>
40
+ </a>
41
+ <% else %>
42
+ <%= page_number %>
43
+ <% end %>
44
+ <% end %>
45
+ </div>
46
+
47
+ <div class="job_history_next_links">
48
+ <a href="job%20history?<%= { sort: @sort_by,
49
+ page_size: page_size,
50
+ page_num: page_num + 1,
51
+ order: @sort_order }.to_param %>"
52
+ class="job_history_next_page"
53
+ disabled="<%= page_num < last_page %>">
54
+ Next &gt;
55
+ </a>
56
+
57
+ <a href="job%20history?<%= { sort: @sort_by,
58
+ page_size: page_size,
59
+ page_num: total_pages,
60
+ order: @sort_order }.to_param %>"
61
+ class="job_history_last_page"
62
+ disabled="<%= last_page < total_pages %>">
63
+ Last &gt;&gt;
64
+ </a>
65
+ </div>
66
+ <% end %>
67
+ </div>
@@ -0,0 +1,71 @@
1
+ <div class="job_history_pagination_block">
2
+ <% total_pages = history_list.num_jobs / page_size %>
3
+ <% total_pages += 1 if history_list.num_jobs % page_size > 0 %>
4
+ <% first_page = [1, page_num - 3].max %>
5
+ <% last_page = [total_pages, page_num + 3].min %>
6
+ <% last_page = page_num < 4 ? [total_pages, last_page + (4 - page_num)].min : last_page %>
7
+ <% first_page = page_num > total_pages - 3 ? [1, first_page + ((total_pages - page_num) - 3)].max : first_page %>
8
+
9
+ <% if total_pages > 1 %>
10
+ <div class="job_history_prev_links">
11
+ <a href="job_class_details?<%= { class_name: class_name,
12
+ "#{primary_type}_page_size" => page_size,
13
+ "#{primary_type}_page_num" => 1,
14
+ "#{secondary_type}_page_size" => secondary_page_size,
15
+ "#{secondary_type}_page_num" => secondary_page_num }.to_param %>"
16
+ class="job_history_first_page"
17
+ disabled="<%= first_page > 1 %>">
18
+ &lt;&lt; First
19
+ </a>
20
+
21
+ <a href="job_class_details?<%= { class_name: class_name,
22
+ "#{primary_type}_page_size" => page_size,
23
+ "#{primary_type}_page_num" => [1, page_num - 1].max,
24
+ "#{secondary_type}_page_size" => secondary_page_size,
25
+ "#{secondary_type}_page_num" => secondary_page_num }.to_param %>"
26
+ class="job_history_prev_page"
27
+ disabled="<%= page_num > 1 %>">
28
+ &lt; Prev
29
+ </a>
30
+ </div>
31
+
32
+ <div class="job_history_pages">
33
+ <% (first_page..last_page).each do |page_number| %>
34
+ <% if page_number != page_num %>
35
+ <a href="job_class_details?<%= { class_name: class_name,
36
+ "#{primary_type}_page_size" => page_size,
37
+ "#{primary_type}_page_num" => page_number,
38
+ "#{secondary_type}_page_size" => secondary_page_size,
39
+ "#{secondary_type}_page_num" => secondary_page_num }.to_param %>"
40
+ class="job_history_page">
41
+ <%= page_number %>
42
+ </a>
43
+ <% else %>
44
+ <%= page_number %>
45
+ <% end %>
46
+ <% end %>
47
+ </div>
48
+
49
+ <div class="job_history_next_links">
50
+ <a href="job_class_details?<%= { class_name: class_name,
51
+ "#{primary_type}_page_size" => page_size,
52
+ "#{primary_type}_page_num" => [total_pages, page_num + 1].min,
53
+ "#{secondary_type}_page_size" => secondary_page_size,
54
+ "#{secondary_type}_page_num" => secondary_page_num }.to_param %>"
55
+ class="job_history_next_page"
56
+ disabled="<%= page_num < last_page %>">
57
+ Next &gt;
58
+ </a>
59
+
60
+ <a href="job_class_details?<%= { class_name: class_name,
61
+ "#{primary_type}_page_size" => page_size,
62
+ "#{primary_type}_page_num" => total_pages,
63
+ "#{secondary_type}_page_size" => secondary_page_size,
64
+ "#{secondary_type}_page_num" => secondary_page_num }.to_param %>"
65
+ class="job_history_last_page"
66
+ disabled="<%= last_page < total_pages %>">
67
+ Last &gt;&gt;
68
+ </a>
69
+ </div>
70
+ <% end %>
71
+ </div>