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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/exe/sqtui +37 -1
  3. data/lib/solid_queue_tui/actions/discard_job.rb +4 -21
  4. data/lib/solid_queue_tui/actions/discard_scheduled_job.rb +5 -21
  5. data/lib/solid_queue_tui/actions/dispatch_scheduled_job.rb +4 -23
  6. data/lib/solid_queue_tui/actions/enqueue_recurring_task.rb +12 -0
  7. data/lib/solid_queue_tui/actions/retry_job.rb +14 -59
  8. data/lib/solid_queue_tui/actions/toggle_queue_pause.rb +19 -0
  9. data/lib/solid_queue_tui/application.rb +109 -21
  10. data/lib/solid_queue_tui/cli.rb +15 -8
  11. data/lib/solid_queue_tui/components/header.rb +26 -27
  12. data/lib/solid_queue_tui/components/help_bar.rb +1 -0
  13. data/lib/solid_queue_tui/components/job_table.rb +13 -2
  14. data/lib/solid_queue_tui/data/failed_query.rb +37 -91
  15. data/lib/solid_queue_tui/data/jobs_query.rb +119 -121
  16. data/lib/solid_queue_tui/data/processes_query.rb +32 -33
  17. data/lib/solid_queue_tui/data/queues_query.rb +6 -15
  18. data/lib/solid_queue_tui/data/recurring_tasks_query.rb +36 -0
  19. data/lib/solid_queue_tui/data/stats.rb +9 -27
  20. data/lib/solid_queue_tui/formatting_helpers.rb +63 -0
  21. data/lib/solid_queue_tui/railtie.rb +9 -0
  22. data/lib/solid_queue_tui/version.rb +1 -1
  23. data/lib/solid_queue_tui/views/blocked_view.rb +85 -74
  24. data/lib/solid_queue_tui/views/concerns/confirmable.rb +53 -0
  25. data/lib/solid_queue_tui/views/concerns/filterable.rb +128 -0
  26. data/lib/solid_queue_tui/views/concerns/paginatable.rb +79 -0
  27. data/lib/solid_queue_tui/views/dashboard_view.rb +4 -5
  28. data/lib/solid_queue_tui/views/failed_view.rb +65 -179
  29. data/lib/solid_queue_tui/views/finished_view.rb +33 -114
  30. data/lib/solid_queue_tui/views/in_progress_view.rb +85 -69
  31. data/lib/solid_queue_tui/views/job_detail_view.rb +179 -31
  32. data/lib/solid_queue_tui/views/processes_view.rb +2 -24
  33. data/lib/solid_queue_tui/views/queues_view.rb +250 -30
  34. data/lib/solid_queue_tui/views/recurring_tasks_view.rb +155 -0
  35. data/lib/solid_queue_tui/views/scheduled_view.rb +69 -107
  36. data/lib/solid_queue_tui.rb +18 -4
  37. data/lib/tasks/solid_queue_tui.rake +8 -0
  38. metadata +20 -25
  39. 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
- text = " #{@title} [#{@rows.size}]"
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: 200)
14
- conn = ActiveRecord::Base.connection
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
- sql = <<~SQL
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.fetch_one(id)
62
- conn = ActiveRecord::Base.connection
63
-
64
- row = conn.select_one(<<~SQL)
65
- SELECT
66
- fe.id,
67
- fe.job_id,
68
- j.queue_name,
69
- j.class_name,
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
- error = parse_json(row["error"])
35
+ def self.fetch_one(id)
36
+ fe = SolidQueue::FailedExecution.includes(:job).find_by(id: id)
37
+ return nil unless fe
84
38
 
85
- FailedJob.new(
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
- private_class_method def self.parse_json(value)
104
- return {} if value.nil?
105
- value.is_a?(Hash) || value.is_a?(Array) ? value : JSON.parse(value.to_s)
106
- rescue JSON::ParserError
107
- {}
108
- end
109
-
110
- private_class_method def self.parse_time(value)
111
- return nil if value.nil?
112
- value.is_a?(Time) ? value : Time.parse(value.to_s)
113
- rescue
114
- nil
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: 200)
14
+ def self.fetch(status:, filter: nil, queue: nil, limit: 100, offset: 0)
15
15
  case status
16
- when "claimed" then fetch_claimed(filter: filter, queue: queue, limit: limit)
17
- when "blocked" then fetch_blocked(filter: filter, queue: queue, limit: limit)
18
- when "scheduled" then fetch_scheduled(filter: filter, queue: queue, limit: limit)
19
- when "completed" then fetch_finished(filter: filter, queue: queue, limit: limit)
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.fetch_claimed(filter: nil, queue: nil, limit: 200)
27
- conn = ActiveRecord::Base.connection
28
-
29
- sql = <<~SQL
30
- SELECT
31
- j.id, j.queue_name, j.class_name, j.priority,
32
- j.active_job_id, j.concurrency_key, j.created_at,
33
- ce.process_id AS worker_id,
34
- ce.created_at AS started_at
35
- FROM solid_queue_claimed_executions ce
36
- JOIN solid_queue_jobs j ON j.id = ce.job_id
37
- SQL
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
- sql += " WHERE #{conditions.join(' AND ')}" unless conditions.empty?
44
- sql += " ORDER BY ce.job_id ASC LIMIT #{limit.to_i}"
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
- conn.select_all(sql).map do |row|
46
+ scope.includes(:job).map do |re|
47
+ job = re.job
47
48
  Job.new(
48
- id: row["id"].to_i,
49
- queue_name: row["queue_name"],
50
- class_name: row["class_name"],
51
- priority: row["priority"].to_i,
52
- status: "claimed",
53
- active_job_id: row["active_job_id"],
54
- concurrency_key: row["concurrency_key"],
55
- created_at: parse_time(row["created_at"]),
56
- worker_id: row["worker_id"]&.to_i,
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.fetch_blocked(filter: nil, queue: nil, limit: 200)
63
- conn = ActiveRecord::Base.connection
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
- sql = <<~SQL
66
- SELECT
67
- j.id, j.queue_name, j.class_name, j.priority,
68
- j.active_job_id, j.concurrency_key, j.created_at,
69
- be.expires_at,
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
- conditions = []
76
- conditions << "j.queue_name = #{conn.quote(queue)}" if queue
77
- conditions << "j.class_name LIKE #{conn.quote("%#{filter}%")}" if filter && !filter.empty?
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
- sql += " WHERE #{conditions.join(' AND ')}" unless conditions.empty?
80
- sql += " ORDER BY be.job_id ASC LIMIT #{limit.to_i}"
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
- conn.select_all(sql).map do |row|
98
+ scope.includes(:job).map do |be|
99
+ job = be.job
83
100
  Job.new(
84
- id: row["id"].to_i,
85
- queue_name: row["queue_name"],
86
- class_name: row["class_name"],
87
- priority: row["priority"].to_i,
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: row["active_job_id"],
90
- concurrency_key: row["concurrency_key"],
91
- created_at: parse_time(row["blocked_since"]),
92
- expires_at: parse_time(row["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
- # Scheduled: query from scheduled_executions JOIN jobs
98
- def self.fetch_scheduled(filter: nil, queue: nil, limit: 200)
99
- conn = ActiveRecord::Base.connection
100
-
101
- sql = <<~SQL
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
- sql += " WHERE #{conditions.join(' AND ')}" unless conditions.empty?
115
- sql += " ORDER BY se.scheduled_at ASC, se.priority ASC LIMIT #{limit.to_i}"
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: row["id"].to_i,
120
- queue_name: row["queue_name"],
121
- class_name: row["class_name"],
122
- priority: row["priority"].to_i,
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: row["active_job_id"],
125
- arguments: parse_json(row["arguments"]),
126
- scheduled_at: parse_time(row["scheduled_at"]),
127
- created_at: parse_time(row["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
- # Finished: query from jobs WHERE finished_at IS NOT NULL
133
- def self.fetch_finished(filter: nil, queue: nil, limit: 200)
134
- conn = ActiveRecord::Base.connection
135
-
136
- sql = <<~SQL
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
- conn.select_all(sql).map do |row|
142
+ scope.map do |job|
149
143
  Job.new(
150
- id: row["id"].to_i,
151
- queue_name: row["queue_name"],
152
- class_name: row["class_name"],
153
- priority: row["priority"].to_i,
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: row["active_job_id"],
156
- arguments: parse_json(row["arguments"]),
157
- finished_at: parse_time(row["finished_at"]),
158
- created_at: parse_time(row["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.parse_time(value)
164
- return nil if value.nil?
165
- value.is_a?(Time) ? value : Time.parse(value.to_s)
166
- rescue
167
- nil
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.parse_json(value)
171
- return nil if value.nil?
172
- value.is_a?(String) ? JSON.parse(value) : value
173
- rescue
174
- nil
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
- def self.fetch
33
- conn = ActiveRecord::Base.connection
34
-
35
- rows = conn.select_all(
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
- rows.map do |row|
42
- metadata = parse_json(row["metadata"])
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: row["id"].to_i,
46
- kind: row["kind"],
47
- pid: row["pid"].to_i,
48
- hostname: row["hostname"],
49
- name: row["name"],
50
- last_heartbeat_at: parse_time(row["last_heartbeat_at"]),
51
- supervisor_id: row["supervisor_id"]&.to_i,
52
- metadata: metadata,
53
- created_at: parse_time(row["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
- conn = ActiveRecord::Base.connection
12
+ queues = SolidQueue::Queue.all
13
+ pauses = SolidQueue::Pause.where(queue_name: queues.map(&:name)).index_by(&:queue_name)
13
14
 
14
- queue_sizes = conn.select_rows(
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: queue_sizes[name] || 0,
28
- paused: paused.include?(name)
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