solid_queue_tui 0.1.1 → 0.1.3
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 +4 -4
- data/exe/sqtui +37 -1
- data/lib/solid_queue_tui/actions/discard_job.rb +4 -21
- data/lib/solid_queue_tui/actions/discard_scheduled_job.rb +5 -21
- data/lib/solid_queue_tui/actions/dispatch_scheduled_job.rb +4 -23
- data/lib/solid_queue_tui/actions/enqueue_recurring_task.rb +12 -0
- data/lib/solid_queue_tui/actions/retry_job.rb +14 -59
- data/lib/solid_queue_tui/actions/toggle_queue_pause.rb +19 -0
- data/lib/solid_queue_tui/application.rb +109 -21
- data/lib/solid_queue_tui/cli.rb +15 -8
- data/lib/solid_queue_tui/components/header.rb +26 -27
- data/lib/solid_queue_tui/components/help_bar.rb +1 -0
- data/lib/solid_queue_tui/components/job_table.rb +13 -2
- data/lib/solid_queue_tui/data/failed_query.rb +37 -91
- data/lib/solid_queue_tui/data/jobs_query.rb +119 -121
- data/lib/solid_queue_tui/data/processes_query.rb +32 -33
- data/lib/solid_queue_tui/data/queues_query.rb +6 -15
- data/lib/solid_queue_tui/data/recurring_tasks_query.rb +36 -0
- data/lib/solid_queue_tui/data/stats.rb +9 -27
- data/lib/solid_queue_tui/formatting_helpers.rb +63 -0
- data/lib/solid_queue_tui/railtie.rb +9 -0
- data/lib/solid_queue_tui/version.rb +1 -1
- data/lib/solid_queue_tui/views/blocked_view.rb +85 -74
- data/lib/solid_queue_tui/views/concerns/confirmable.rb +53 -0
- data/lib/solid_queue_tui/views/concerns/filterable.rb +128 -0
- data/lib/solid_queue_tui/views/concerns/paginatable.rb +79 -0
- data/lib/solid_queue_tui/views/dashboard_view.rb +4 -5
- data/lib/solid_queue_tui/views/failed_view.rb +65 -179
- data/lib/solid_queue_tui/views/finished_view.rb +33 -114
- data/lib/solid_queue_tui/views/in_progress_view.rb +85 -69
- data/lib/solid_queue_tui/views/job_detail_view.rb +179 -31
- data/lib/solid_queue_tui/views/processes_view.rb +2 -24
- data/lib/solid_queue_tui/views/queues_view.rb +250 -30
- data/lib/solid_queue_tui/views/recurring_tasks_view.rb +155 -0
- data/lib/solid_queue_tui/views/scheduled_view.rb +69 -107
- data/lib/solid_queue_tui.rb +18 -4
- data/lib/tasks/solid_queue_tui.rake +8 -0
- metadata +20 -25
- data/lib/solid_queue_tui/connection.rb +0 -58
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module SolidQueueTui
|
|
4
4
|
module Components
|
|
5
5
|
class JobTable
|
|
6
|
+
include FormattingHelpers
|
|
7
|
+
|
|
6
8
|
STATUS_COLORS = {
|
|
7
9
|
"ready" => :green,
|
|
8
10
|
"claimed" => :yellow,
|
|
@@ -12,15 +14,18 @@ module SolidQueueTui
|
|
|
12
14
|
"completed" => :dark_gray,
|
|
13
15
|
"active" => :green,
|
|
14
16
|
"paused" => :red,
|
|
17
|
+
"delayed" => :red,
|
|
18
|
+
"pending" => :dark_gray,
|
|
15
19
|
"unknown" => :white
|
|
16
20
|
}.freeze
|
|
17
21
|
|
|
18
|
-
def initialize(tui, title:, columns:, rows:, selected_row: nil, empty_message: "No data")
|
|
22
|
+
def initialize(tui, title:, columns:, rows:, selected_row: nil, total_count: nil, empty_message: "No data")
|
|
19
23
|
@tui = tui
|
|
20
24
|
@title = title
|
|
21
25
|
@columns = columns
|
|
22
26
|
@rows = rows
|
|
23
27
|
@selected_row = selected_row
|
|
28
|
+
@total_count = total_count
|
|
24
29
|
@empty_message = empty_message
|
|
25
30
|
end
|
|
26
31
|
|
|
@@ -36,11 +41,17 @@ module SolidQueueTui
|
|
|
36
41
|
private
|
|
37
42
|
|
|
38
43
|
def title_text
|
|
39
|
-
|
|
44
|
+
count_text = if @total_count && @total_count > @rows.size
|
|
45
|
+
"#{@rows.size}/#{format_number(@total_count)}"
|
|
46
|
+
else
|
|
47
|
+
@rows.size.to_s
|
|
48
|
+
end
|
|
49
|
+
text = " #{@title} [#{count_text}]"
|
|
40
50
|
text += " #{@selected_row + 1}/#{@rows.size}" if @selected_row && @rows.size > 0
|
|
41
51
|
text + " "
|
|
42
52
|
end
|
|
43
53
|
|
|
54
|
+
|
|
44
55
|
def render_table(frame, area, table_state)
|
|
45
56
|
widths = @columns.map do |col|
|
|
46
57
|
case col[:width]
|
|
@@ -10,109 +10,55 @@ module SolidQueueTui
|
|
|
10
10
|
keyword_init: true
|
|
11
11
|
)
|
|
12
12
|
|
|
13
|
-
def self.fetch(filter: nil, limit:
|
|
14
|
-
|
|
13
|
+
def self.fetch(filter: nil, queue: nil, limit: 100, offset: 0)
|
|
14
|
+
scope = SolidQueue::FailedExecution.joins(:job).includes(:job)
|
|
15
|
+
scope = scope.merge(SolidQueue::Job.where("class_name LIKE ?", "%#{filter}%")) if filter.present?
|
|
16
|
+
scope = scope.merge(SolidQueue::Job.where(queue_name: queue)) if queue.present?
|
|
17
|
+
scope = scope.order(created_at: :desc).offset(offset).limit(limit)
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
SELECT
|
|
18
|
-
fe.id,
|
|
19
|
-
fe.job_id,
|
|
20
|
-
j.queue_name,
|
|
21
|
-
j.class_name,
|
|
22
|
-
j.priority,
|
|
23
|
-
j.active_job_id,
|
|
24
|
-
j.arguments,
|
|
25
|
-
j.created_at AS job_created_at,
|
|
26
|
-
fe.error,
|
|
27
|
-
fe.created_at AS failed_at
|
|
28
|
-
FROM solid_queue_failed_executions fe
|
|
29
|
-
JOIN solid_queue_jobs j ON j.id = fe.job_id
|
|
30
|
-
SQL
|
|
31
|
-
|
|
32
|
-
if filter && !filter.empty?
|
|
33
|
-
sql += " WHERE j.class_name LIKE #{conn.quote("%#{filter}%")}"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
sql += " ORDER BY fe.created_at DESC LIMIT #{limit.to_i}"
|
|
37
|
-
|
|
38
|
-
rows = conn.select_all(sql)
|
|
39
|
-
rows.map do |row|
|
|
40
|
-
error = parse_json(row["error"])
|
|
41
|
-
|
|
42
|
-
FailedJob.new(
|
|
43
|
-
id: row["id"].to_i,
|
|
44
|
-
job_id: row["job_id"].to_i,
|
|
45
|
-
queue_name: row["queue_name"],
|
|
46
|
-
class_name: row["class_name"],
|
|
47
|
-
priority: row["priority"].to_i,
|
|
48
|
-
error_class: error["exception_class"] || error["class"] || "Unknown",
|
|
49
|
-
error_message: error["message"] || "No message",
|
|
50
|
-
backtrace: error["backtrace"] || [],
|
|
51
|
-
active_job_id: row["active_job_id"],
|
|
52
|
-
arguments: parse_json(row["arguments"]),
|
|
53
|
-
failed_at: parse_time(row["failed_at"]),
|
|
54
|
-
created_at: parse_time(row["job_created_at"])
|
|
55
|
-
)
|
|
56
|
-
end
|
|
19
|
+
scope.map { |fe| build_failed_job(fe) }
|
|
57
20
|
rescue => e
|
|
21
|
+
Rails.logger.tagged("SQTUI") { Rails.logger.error("FailedQuery.fetch error: #{e.class}: #{e.message}") } if defined?(Rails) && Rails.logger
|
|
58
22
|
[]
|
|
59
23
|
end
|
|
60
24
|
|
|
61
|
-
def self.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
j.priority,
|
|
71
|
-
j.active_job_id,
|
|
72
|
-
j.arguments,
|
|
73
|
-
j.created_at AS job_created_at,
|
|
74
|
-
fe.error,
|
|
75
|
-
fe.created_at AS failed_at
|
|
76
|
-
FROM solid_queue_failed_executions fe
|
|
77
|
-
JOIN solid_queue_jobs j ON j.id = fe.job_id
|
|
78
|
-
WHERE fe.id = #{conn.quote(id.to_i)}
|
|
79
|
-
SQL
|
|
80
|
-
|
|
81
|
-
return nil unless row
|
|
25
|
+
def self.count(filter: nil, queue: nil)
|
|
26
|
+
scope = SolidQueue::FailedExecution.joins(:job)
|
|
27
|
+
scope = scope.merge(SolidQueue::Job.where("class_name LIKE ?", "%#{filter}%")) if filter.present?
|
|
28
|
+
scope = scope.merge(SolidQueue::Job.where(queue_name: queue)) if queue.present?
|
|
29
|
+
scope.count
|
|
30
|
+
rescue => e
|
|
31
|
+
Rails.logger.tagged("SQTUI") { Rails.logger.error("FailedQuery.count error: #{e.class}: #{e.message}") } if defined?(Rails) && Rails.logger
|
|
32
|
+
0
|
|
33
|
+
end
|
|
82
34
|
|
|
83
|
-
|
|
35
|
+
def self.fetch_one(id)
|
|
36
|
+
fe = SolidQueue::FailedExecution.includes(:job).find_by(id: id)
|
|
37
|
+
return nil unless fe
|
|
84
38
|
|
|
85
|
-
|
|
86
|
-
id: row["id"].to_i,
|
|
87
|
-
job_id: row["job_id"].to_i,
|
|
88
|
-
queue_name: row["queue_name"],
|
|
89
|
-
class_name: row["class_name"],
|
|
90
|
-
priority: row["priority"].to_i,
|
|
91
|
-
error_class: error["exception_class"] || error["class"] || "Unknown",
|
|
92
|
-
error_message: error["message"] || "No message",
|
|
93
|
-
backtrace: error["backtrace"] || [],
|
|
94
|
-
active_job_id: row["active_job_id"],
|
|
95
|
-
arguments: parse_json(row["arguments"]),
|
|
96
|
-
failed_at: parse_time(row["failed_at"]),
|
|
97
|
-
created_at: parse_time(row["job_created_at"])
|
|
98
|
-
)
|
|
39
|
+
build_failed_job(fe)
|
|
99
40
|
rescue => e
|
|
100
41
|
nil
|
|
101
42
|
end
|
|
102
43
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
44
|
+
def self.build_failed_job(fe)
|
|
45
|
+
job = fe.job
|
|
46
|
+
FailedJob.new(
|
|
47
|
+
id: fe.id,
|
|
48
|
+
job_id: job.id,
|
|
49
|
+
queue_name: job.queue_name,
|
|
50
|
+
class_name: job.class_name,
|
|
51
|
+
priority: job.priority,
|
|
52
|
+
error_class: fe.exception_class || "Unknown",
|
|
53
|
+
error_message: fe.message || "No message",
|
|
54
|
+
backtrace: fe.backtrace || [],
|
|
55
|
+
active_job_id: job.active_job_id,
|
|
56
|
+
arguments: job.arguments,
|
|
57
|
+
failed_at: fe.created_at,
|
|
58
|
+
created_at: job.created_at
|
|
59
|
+
)
|
|
115
60
|
end
|
|
61
|
+
private_class_method :build_failed_job
|
|
116
62
|
end
|
|
117
63
|
end
|
|
118
64
|
end
|
|
@@ -11,167 +11,165 @@ module SolidQueueTui
|
|
|
11
11
|
keyword_init: true
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
-
def self.fetch(status:, filter: nil, queue: nil, limit:
|
|
14
|
+
def self.fetch(status:, filter: nil, queue: nil, limit: 100, offset: 0)
|
|
15
15
|
case status
|
|
16
|
-
when "
|
|
17
|
-
when "
|
|
18
|
-
when "
|
|
19
|
-
when "
|
|
16
|
+
when "pending" then fetch_pending(queue: queue, filter: filter, limit: limit, offset: offset)
|
|
17
|
+
when "claimed" then fetch_claimed(filter: filter, queue: queue, limit: limit, offset: offset)
|
|
18
|
+
when "blocked" then fetch_blocked(filter: filter, queue: queue, limit: limit, offset: offset)
|
|
19
|
+
when "scheduled" then fetch_scheduled(filter: filter, queue: queue, limit: limit, offset: offset)
|
|
20
|
+
when "completed" then fetch_finished(filter: filter, queue: queue, limit: limit, offset: offset)
|
|
20
21
|
else []
|
|
21
22
|
end
|
|
22
23
|
rescue => e
|
|
23
24
|
[]
|
|
24
25
|
end
|
|
25
26
|
|
|
26
|
-
def self.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
conditions = []
|
|
40
|
-
conditions << "j.queue_name = #{conn.quote(queue)}" if queue
|
|
41
|
-
conditions << "j.class_name LIKE #{conn.quote("%#{filter}%")}" if filter && !filter.empty?
|
|
27
|
+
def self.count(status:, filter: nil, queue: nil)
|
|
28
|
+
case status
|
|
29
|
+
when "pending" then count_pending(queue: queue, filter: filter)
|
|
30
|
+
when "claimed" then count_scope(SolidQueue::ClaimedExecution.joins(:job), filter: filter, queue: queue)
|
|
31
|
+
when "blocked" then count_scope(SolidQueue::BlockedExecution.joins(:job), filter: filter, queue: queue)
|
|
32
|
+
when "scheduled" then count_scope(SolidQueue::ScheduledExecution.joins(:job), filter: filter, queue: queue)
|
|
33
|
+
when "completed" then count_finished(filter: filter, queue: queue)
|
|
34
|
+
else 0
|
|
35
|
+
end
|
|
36
|
+
rescue => e
|
|
37
|
+
0
|
|
38
|
+
end
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
def self.fetch_pending(queue:, filter: nil, limit: 100, offset: 0)
|
|
41
|
+
scope = SolidQueue::ReadyExecution.joins(:job)
|
|
42
|
+
scope = scope.where(queue_name: queue) if queue
|
|
43
|
+
scope = apply_class_name_filter(scope, filter)
|
|
44
|
+
scope = scope.order(priority: :asc, job_id: :asc).offset(offset).limit(limit)
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
scope.includes(:job).map do |re|
|
|
47
|
+
job = re.job
|
|
47
48
|
Job.new(
|
|
48
|
-
id:
|
|
49
|
-
queue_name:
|
|
50
|
-
class_name:
|
|
51
|
-
priority:
|
|
52
|
-
status: "
|
|
53
|
-
active_job_id:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
started_at: parse_time(row["started_at"])
|
|
49
|
+
id: job.id,
|
|
50
|
+
queue_name: job.queue_name,
|
|
51
|
+
class_name: job.class_name,
|
|
52
|
+
priority: job.priority,
|
|
53
|
+
status: "pending",
|
|
54
|
+
active_job_id: job.active_job_id,
|
|
55
|
+
arguments: job.arguments,
|
|
56
|
+
scheduled_at: job.scheduled_at,
|
|
57
|
+
created_at: job.created_at
|
|
58
58
|
)
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
def self.
|
|
63
|
-
|
|
62
|
+
def self.count_pending(queue:, filter: nil)
|
|
63
|
+
scope = SolidQueue::ReadyExecution.joins(:job)
|
|
64
|
+
scope = scope.where(queue_name: queue) if queue
|
|
65
|
+
scope = apply_class_name_filter(scope, filter)
|
|
66
|
+
scope.count
|
|
67
|
+
end
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
be.created_at AS blocked_since
|
|
71
|
-
FROM solid_queue_blocked_executions be
|
|
72
|
-
JOIN solid_queue_jobs j ON j.id = be.job_id
|
|
73
|
-
SQL
|
|
69
|
+
def self.fetch_claimed(filter: nil, queue: nil, limit: 100, offset: 0)
|
|
70
|
+
scope = SolidQueue::ClaimedExecution.joins(:job)
|
|
71
|
+
scope = scope.merge(SolidQueue::Job.where(queue_name: queue)) if queue
|
|
72
|
+
scope = apply_class_name_filter(scope, filter)
|
|
73
|
+
scope = scope.order(job_id: :asc).offset(offset).limit(limit)
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
scope.includes(:job).map do |ce|
|
|
76
|
+
job = ce.job
|
|
77
|
+
Job.new(
|
|
78
|
+
id: job.id,
|
|
79
|
+
queue_name: job.queue_name,
|
|
80
|
+
class_name: job.class_name,
|
|
81
|
+
priority: job.priority,
|
|
82
|
+
status: "claimed",
|
|
83
|
+
active_job_id: job.active_job_id,
|
|
84
|
+
concurrency_key: job.concurrency_key,
|
|
85
|
+
created_at: job.created_at,
|
|
86
|
+
worker_id: ce.process_id,
|
|
87
|
+
started_at: ce.created_at
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
78
91
|
|
|
79
|
-
|
|
80
|
-
|
|
92
|
+
def self.fetch_blocked(filter: nil, queue: nil, limit: 100, offset: 0)
|
|
93
|
+
scope = SolidQueue::BlockedExecution.joins(:job)
|
|
94
|
+
scope = scope.merge(SolidQueue::Job.where(queue_name: queue)) if queue
|
|
95
|
+
scope = apply_class_name_filter(scope, filter)
|
|
96
|
+
scope = scope.order(job_id: :asc).offset(offset).limit(limit)
|
|
81
97
|
|
|
82
|
-
|
|
98
|
+
scope.includes(:job).map do |be|
|
|
99
|
+
job = be.job
|
|
83
100
|
Job.new(
|
|
84
|
-
id:
|
|
85
|
-
queue_name:
|
|
86
|
-
class_name:
|
|
87
|
-
priority:
|
|
101
|
+
id: job.id,
|
|
102
|
+
queue_name: job.queue_name,
|
|
103
|
+
class_name: job.class_name,
|
|
104
|
+
priority: job.priority,
|
|
88
105
|
status: "blocked",
|
|
89
|
-
active_job_id:
|
|
90
|
-
concurrency_key:
|
|
91
|
-
created_at:
|
|
92
|
-
expires_at:
|
|
106
|
+
active_job_id: job.active_job_id,
|
|
107
|
+
concurrency_key: job.concurrency_key,
|
|
108
|
+
created_at: be.created_at,
|
|
109
|
+
expires_at: be.expires_at
|
|
93
110
|
)
|
|
94
111
|
end
|
|
95
112
|
end
|
|
96
113
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
SELECT
|
|
103
|
-
j.id, j.queue_name, j.class_name, j.priority,
|
|
104
|
-
j.active_job_id, j.arguments, j.created_at,
|
|
105
|
-
se.scheduled_at
|
|
106
|
-
FROM solid_queue_scheduled_executions se
|
|
107
|
-
JOIN solid_queue_jobs j ON j.id = se.job_id
|
|
108
|
-
SQL
|
|
109
|
-
|
|
110
|
-
conditions = []
|
|
111
|
-
conditions << "j.queue_name = #{conn.quote(queue)}" if queue
|
|
112
|
-
conditions << "j.class_name LIKE #{conn.quote("%#{filter}%")}" if filter && !filter.empty?
|
|
114
|
+
def self.fetch_scheduled(filter: nil, queue: nil, limit: 100, offset: 0)
|
|
115
|
+
scope = SolidQueue::ScheduledExecution.joins(:job)
|
|
116
|
+
scope = scope.merge(SolidQueue::Job.where(queue_name: queue)) if queue
|
|
117
|
+
scope = apply_class_name_filter(scope, filter)
|
|
118
|
+
scope = scope.order(scheduled_at: :asc, priority: :asc).offset(offset).limit(limit)
|
|
113
119
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
conn.select_all(sql).map do |row|
|
|
120
|
+
scope.includes(:job).map do |se|
|
|
121
|
+
job = se.job
|
|
118
122
|
Job.new(
|
|
119
|
-
id:
|
|
120
|
-
queue_name:
|
|
121
|
-
class_name:
|
|
122
|
-
priority:
|
|
123
|
+
id: job.id,
|
|
124
|
+
queue_name: job.queue_name,
|
|
125
|
+
class_name: job.class_name,
|
|
126
|
+
priority: job.priority,
|
|
123
127
|
status: "scheduled",
|
|
124
|
-
active_job_id:
|
|
125
|
-
arguments:
|
|
126
|
-
scheduled_at:
|
|
127
|
-
created_at:
|
|
128
|
+
active_job_id: job.active_job_id,
|
|
129
|
+
arguments: job.arguments,
|
|
130
|
+
scheduled_at: se.scheduled_at,
|
|
131
|
+
created_at: job.created_at
|
|
128
132
|
)
|
|
129
133
|
end
|
|
130
134
|
end
|
|
131
135
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
SELECT
|
|
138
|
-
j.id, j.queue_name, j.class_name, j.priority,
|
|
139
|
-
j.active_job_id, j.arguments, j.finished_at, j.created_at
|
|
140
|
-
FROM solid_queue_jobs j
|
|
141
|
-
WHERE j.finished_at IS NOT NULL
|
|
142
|
-
SQL
|
|
143
|
-
|
|
144
|
-
sql += " AND j.queue_name = #{conn.quote(queue)}" if queue
|
|
145
|
-
sql += " AND j.class_name LIKE #{conn.quote("%#{filter}%")}" if filter && !filter.empty?
|
|
146
|
-
sql += " ORDER BY j.finished_at DESC LIMIT #{limit.to_i}"
|
|
136
|
+
def self.fetch_finished(filter: nil, queue: nil, limit: 100, offset: 0)
|
|
137
|
+
scope = SolidQueue::Job.finished
|
|
138
|
+
scope = scope.where(queue_name: queue) if queue
|
|
139
|
+
scope = scope.where("class_name LIKE ?", "%#{filter}%") if filter.present?
|
|
140
|
+
scope = scope.order(finished_at: :desc).offset(offset).limit(limit)
|
|
147
141
|
|
|
148
|
-
|
|
142
|
+
scope.map do |job|
|
|
149
143
|
Job.new(
|
|
150
|
-
id:
|
|
151
|
-
queue_name:
|
|
152
|
-
class_name:
|
|
153
|
-
priority:
|
|
144
|
+
id: job.id,
|
|
145
|
+
queue_name: job.queue_name,
|
|
146
|
+
class_name: job.class_name,
|
|
147
|
+
priority: job.priority,
|
|
154
148
|
status: "completed",
|
|
155
|
-
active_job_id:
|
|
156
|
-
arguments:
|
|
157
|
-
finished_at:
|
|
158
|
-
created_at:
|
|
149
|
+
active_job_id: job.active_job_id,
|
|
150
|
+
arguments: job.arguments,
|
|
151
|
+
finished_at: job.finished_at,
|
|
152
|
+
created_at: job.created_at
|
|
159
153
|
)
|
|
160
154
|
end
|
|
161
155
|
end
|
|
162
156
|
|
|
163
|
-
private_class_method def self.
|
|
164
|
-
return
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
157
|
+
private_class_method def self.apply_class_name_filter(scope, filter)
|
|
158
|
+
return scope if filter.blank?
|
|
159
|
+
scope.merge(SolidQueue::Job.where("class_name LIKE ?", "%#{filter}%"))
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private_class_method def self.count_scope(scope, filter: nil, queue: nil)
|
|
163
|
+
scope = scope.merge(SolidQueue::Job.where(queue_name: queue)) if queue
|
|
164
|
+
scope = apply_class_name_filter(scope, filter)
|
|
165
|
+
scope.count
|
|
168
166
|
end
|
|
169
167
|
|
|
170
|
-
private_class_method def self.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
168
|
+
private_class_method def self.count_finished(filter: nil, queue: nil)
|
|
169
|
+
scope = SolidQueue::Job.finished
|
|
170
|
+
scope = scope.where(queue_name: queue) if queue
|
|
171
|
+
scope = scope.where("class_name LIKE ?", "%#{filter}%") if filter.present?
|
|
172
|
+
scope.count
|
|
175
173
|
end
|
|
176
174
|
end
|
|
177
175
|
end
|
|
@@ -29,47 +29,46 @@ module SolidQueueTui
|
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"SELECT id, kind, pid, hostname, name, last_heartbeat_at, " \
|
|
37
|
-
"supervisor_id, metadata, created_at " \
|
|
38
|
-
"FROM solid_queue_processes WHERE kind = 'Worker' ORDER BY id"
|
|
39
|
-
)
|
|
32
|
+
RunningJob = Struct.new(
|
|
33
|
+
:job_id, :class_name, :queue_name, :started_at,
|
|
34
|
+
keyword_init: true
|
|
35
|
+
)
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
def self.fetch_running_jobs(process_id:)
|
|
38
|
+
SolidQueue::ClaimedExecution
|
|
39
|
+
.where(process_id: process_id)
|
|
40
|
+
.joins(:job).includes(:job)
|
|
41
|
+
.order(:created_at)
|
|
42
|
+
.map do |ce|
|
|
43
|
+
job = ce.job
|
|
44
|
+
RunningJob.new(
|
|
45
|
+
job_id: job.id,
|
|
46
|
+
class_name: job.class_name,
|
|
47
|
+
queue_name: job.queue_name,
|
|
48
|
+
started_at: ce.created_at
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
rescue => e
|
|
52
|
+
[]
|
|
53
|
+
end
|
|
43
54
|
|
|
55
|
+
def self.fetch
|
|
56
|
+
SolidQueue::Process.where(kind: "Worker").order(:id).map do |proc|
|
|
44
57
|
Process.new(
|
|
45
|
-
id:
|
|
46
|
-
kind:
|
|
47
|
-
pid:
|
|
48
|
-
hostname:
|
|
49
|
-
name:
|
|
50
|
-
last_heartbeat_at:
|
|
51
|
-
supervisor_id:
|
|
52
|
-
metadata: metadata,
|
|
53
|
-
created_at:
|
|
58
|
+
id: proc.id,
|
|
59
|
+
kind: proc.kind,
|
|
60
|
+
pid: proc.pid,
|
|
61
|
+
hostname: proc.hostname,
|
|
62
|
+
name: proc.name,
|
|
63
|
+
last_heartbeat_at: proc.last_heartbeat_at,
|
|
64
|
+
supervisor_id: proc.supervisor_id,
|
|
65
|
+
metadata: proc.metadata,
|
|
66
|
+
created_at: proc.created_at
|
|
54
67
|
)
|
|
55
68
|
end
|
|
56
69
|
rescue => e
|
|
57
70
|
[]
|
|
58
71
|
end
|
|
59
|
-
|
|
60
|
-
private_class_method def self.parse_json(value)
|
|
61
|
-
return {} if value.nil?
|
|
62
|
-
value.is_a?(Hash) ? value : JSON.parse(value.to_s)
|
|
63
|
-
rescue JSON::ParserError
|
|
64
|
-
{}
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
private_class_method def self.parse_time(value)
|
|
68
|
-
return nil if value.nil?
|
|
69
|
-
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
70
|
-
rescue
|
|
71
|
-
nil
|
|
72
|
-
end
|
|
73
72
|
end
|
|
74
73
|
end
|
|
75
74
|
end
|
|
@@ -9,23 +9,14 @@ module SolidQueueTui
|
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
def self.fetch
|
|
12
|
-
|
|
12
|
+
queues = SolidQueue::Queue.all
|
|
13
|
+
pauses = SolidQueue::Pause.where(queue_name: queues.map(&:name)).index_by(&:queue_name)
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
"SELECT queue_name, COUNT(*) FROM solid_queue_ready_executions GROUP BY queue_name ORDER BY queue_name"
|
|
16
|
-
).to_h { |name, count| [name, count.to_i] }
|
|
17
|
-
|
|
18
|
-
all_queues = conn.select_values(
|
|
19
|
-
"SELECT DISTINCT queue_name FROM solid_queue_jobs WHERE queue_name IS NOT NULL ORDER BY queue_name"
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
paused = conn.select_values("SELECT queue_name FROM solid_queue_pauses")
|
|
23
|
-
|
|
24
|
-
all_queues.map do |name|
|
|
15
|
+
queues.map do |queue|
|
|
25
16
|
QueueInfo.new(
|
|
26
|
-
name: name,
|
|
27
|
-
size:
|
|
28
|
-
paused:
|
|
17
|
+
name: queue.name,
|
|
18
|
+
size: queue.size,
|
|
19
|
+
paused: pauses[queue.name].present?
|
|
29
20
|
)
|
|
30
21
|
end
|
|
31
22
|
rescue => e
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueTui
|
|
4
|
+
module Data
|
|
5
|
+
class RecurringTasksQuery
|
|
6
|
+
Task = Struct.new(
|
|
7
|
+
:key, :class_name, :command, :schedule, :queue_name,
|
|
8
|
+
:priority, :last_enqueued_at, :next_time,
|
|
9
|
+
keyword_init: true
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
def self.fetch
|
|
13
|
+
tasks = SolidQueue::RecurringTask.all.to_a
|
|
14
|
+
return [] if tasks.empty?
|
|
15
|
+
|
|
16
|
+
last_enqueued = SolidQueue::RecurringExecution
|
|
17
|
+
.where(task_key: tasks.map(&:key))
|
|
18
|
+
.group(:task_key)
|
|
19
|
+
.maximum(:run_at)
|
|
20
|
+
|
|
21
|
+
tasks.map do |task|
|
|
22
|
+
Task.new(
|
|
23
|
+
key: task.key,
|
|
24
|
+
class_name: task.class_name,
|
|
25
|
+
command: task.command,
|
|
26
|
+
schedule: task.schedule,
|
|
27
|
+
queue_name: task.queue_name,
|
|
28
|
+
priority: task.priority,
|
|
29
|
+
last_enqueued_at: last_enqueued[task.key],
|
|
30
|
+
next_time: task.next_time
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|