Skip to content

Commit cd0f822

Browse files
authored
feat: add sortable column headers to all job list tables (#24)
Add clickable column headers for sorting across all job tables: - Ready Jobs: class_name, queue_name, priority, created_at - Scheduled Jobs: class_name, queue_name, scheduled_at - Failed Jobs: class_name, queue_name, created_at - In Progress Jobs: class_name, queue_name, created_at - Recurring Jobs: key, class_name, queue_name, priority - Jobs: class_name, queue_name, status, created_at - Queues: queue_name, job_count - Workers: hostname, last_heartbeat_at Features: - Click to sort ascending, click again for descending - Visual indicators: ↑ (asc), ↓ (desc), ⇅ (default/sortable) - Sort state preserved across pagination and filtering - Special handling for execution tables with JOIN queries - Special handling for GROUP BY queries on queues page Includes comprehensive test coverage with 27 new specs.
1 parent 01e20be commit cd0f822

22 files changed

Lines changed: 465 additions & 73 deletions

.rubocop.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Metrics/ClassLength:
2727
- 'app/presenters/solid_queue_monitor/job_details_presenter.rb'
2828

2929
Metrics/ParameterLists:
30-
Max: 7
30+
Max: 8
3131

3232
Metrics/ModuleLength:
3333
Max: 200

app/controllers/solid_queue_monitor/base_controller.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,5 +202,36 @@ def filter_params
202202
status: params[:status]
203203
}
204204
end
205+
206+
def sort_params
207+
{
208+
sort_by: params[:sort_by],
209+
sort_direction: params[:sort_direction]
210+
}
211+
end
212+
213+
def apply_sorting(relation, allowed_columns, default_column, default_direction = :desc)
214+
column = sort_params[:sort_by]
215+
direction = sort_params[:sort_direction]
216+
column = default_column unless allowed_columns.include?(column)
217+
direction = %w[asc desc].include?(direction) ? direction.to_sym : default_direction
218+
relation.order(column => direction)
219+
end
220+
221+
def apply_execution_sorting(relation, allowed_columns, default_column, default_direction = :desc)
222+
column = sort_params[:sort_by]
223+
direction = sort_params[:sort_direction]
224+
column = default_column unless allowed_columns.include?(column)
225+
direction = %w[asc desc].include?(direction) ? direction.to_sym : default_direction
226+
227+
# Columns that exist on the jobs table, not on execution tables
228+
job_table_columns = %w[class_name queue_name]
229+
230+
if job_table_columns.include?(column)
231+
relation.joins(:job).order("solid_queue_jobs.#{column}" => direction)
232+
else
233+
relation.order(column => direction)
234+
end
235+
end
205236
end
206237
end

app/controllers/solid_queue_monitor/failed_jobs_controller.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22

33
module SolidQueueMonitor
44
class FailedJobsController < BaseController
5+
SORTABLE_COLUMNS = %w[class_name queue_name created_at].freeze
6+
57
def index
6-
base_query = SolidQueue::FailedExecution.includes(:job).order(created_at: :desc)
7-
@failed_jobs = paginate(filter_failed_jobs(base_query))
8+
base_query = SolidQueue::FailedExecution.includes(:job)
9+
sorted_query = apply_execution_sorting(filter_failed_jobs(base_query), SORTABLE_COLUMNS, 'created_at', :desc)
10+
@failed_jobs = paginate(sorted_query)
811

912
render_page('Failed Jobs', SolidQueueMonitor::FailedJobsPresenter.new(@failed_jobs[:records],
1013
current_page: @failed_jobs[:current_page],
1114
total_pages: @failed_jobs[:total_pages],
12-
filters: filter_params).render)
15+
filters: filter_params,
16+
sort: sort_params).render)
1317
end
1418

1519
def retry

app/controllers/solid_queue_monitor/in_progress_jobs_controller.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22

33
module SolidQueueMonitor
44
class InProgressJobsController < BaseController
5+
SORTABLE_COLUMNS = %w[class_name queue_name created_at].freeze
6+
57
def index
6-
base_query = SolidQueue::ClaimedExecution.includes(:job).order(created_at: :desc)
7-
@in_progress_jobs = paginate(filter_in_progress_jobs(base_query))
8+
base_query = SolidQueue::ClaimedExecution.includes(:job)
9+
sorted_query = apply_execution_sorting(filter_in_progress_jobs(base_query), SORTABLE_COLUMNS, 'created_at', :desc)
10+
@in_progress_jobs = paginate(sorted_query)
811

912
render_page('In Progress Jobs', SolidQueueMonitor::InProgressJobsPresenter.new(@in_progress_jobs[:records],
1013
current_page: @in_progress_jobs[:current_page],
1114
total_pages: @in_progress_jobs[:total_pages],
12-
filters: filter_params).render)
15+
filters: filter_params,
16+
sort: sort_params).render)
1317
end
1418

1519
private

app/controllers/solid_queue_monitor/overview_controller.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
module SolidQueueMonitor
44
class OverviewController < BaseController
5+
SORTABLE_COLUMNS = %w[class_name queue_name created_at].freeze
6+
57
def index
68
@stats = SolidQueueMonitor::StatsCalculator.calculate
79
@chart_data = SolidQueueMonitor::ChartDataService.new(time_range: time_range_param).calculate
810

9-
recent_jobs_query = SolidQueue::Job.order(created_at: :desc).limit(100)
10-
@recent_jobs = paginate(filter_jobs(recent_jobs_query))
11+
recent_jobs_query = SolidQueue::Job.limit(100)
12+
sorted_query = apply_sorting(filter_jobs(recent_jobs_query), SORTABLE_COLUMNS, 'created_at', :desc)
13+
@recent_jobs = paginate(sorted_query)
1114

1215
preload_job_statuses(@recent_jobs[:records])
1316

@@ -31,7 +34,8 @@ def generate_overview_content
3134
SolidQueueMonitor::JobsPresenter.new(@recent_jobs[:records],
3235
current_page: @recent_jobs[:current_page],
3336
total_pages: @recent_jobs[:total_pages],
34-
filters: filter_params).render
37+
filters: filter_params,
38+
sort: sort_params).render
3539
end
3640
end
3741
end

app/controllers/solid_queue_monitor/queues_controller.rb

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,26 @@
22

33
module SolidQueueMonitor
44
class QueuesController < BaseController
5+
SORTABLE_COLUMNS = %w[queue_name job_count].freeze
6+
QUEUE_DETAILS_SORTABLE_COLUMNS = %w[class_name created_at].freeze
7+
58
def index
6-
@queues = SolidQueue::Job.group(:queue_name)
7-
.select('queue_name, COUNT(*) as job_count')
8-
.order('job_count DESC')
9+
base_query = SolidQueue::Job.group(:queue_name)
10+
.select('queue_name, COUNT(*) as job_count')
11+
@queues = apply_queue_sorting(base_query)
912
@paused_queues = QueuePauseService.paused_queues
1013

11-
render_page('Queues', SolidQueueMonitor::QueuesPresenter.new(@queues, @paused_queues).render)
14+
render_page('Queues', SolidQueueMonitor::QueuesPresenter.new(@queues, @paused_queues, sort: sort_params).render)
1215
end
1316

1417
def show
1518
@queue_name = params[:queue_name]
1619
@paused = QueuePauseService.paused_queues.include?(@queue_name)
1720

1821
# Get all jobs for this queue with filtering and pagination
19-
base_query = SolidQueue::Job.where(queue_name: @queue_name).order(created_at: :desc)
20-
filtered_query = filter_queue_jobs(base_query)
21-
@jobs = paginate(filtered_query)
22+
base_query = SolidQueue::Job.where(queue_name: @queue_name)
23+
sorted_query = apply_sorting(filter_queue_jobs(base_query), QUEUE_DETAILS_SORTABLE_COLUMNS, 'created_at', :desc)
24+
@jobs = paginate(sorted_query)
2225
preload_job_statuses(@jobs[:records])
2326

2427
@counts = calculate_queue_counts(@queue_name)
@@ -31,7 +34,8 @@ def show
3134
counts: @counts,
3235
current_page: @jobs[:current_page],
3336
total_pages: @jobs[:total_pages],
34-
filters: queue_filter_params
37+
filters: queue_filter_params,
38+
sort: sort_params
3539
).render)
3640
end
3741

@@ -97,5 +101,14 @@ def queue_filter_params
97101
status: params[:status]
98102
}
99103
end
104+
105+
def apply_queue_sorting(relation)
106+
column = sort_params[:sort_by]
107+
direction = sort_params[:sort_direction]
108+
column = 'job_count' unless SORTABLE_COLUMNS.include?(column)
109+
direction = 'desc' unless %w[asc desc].include?(direction)
110+
111+
relation.order("#{column} #{direction}")
112+
end
100113
end
101114
end

app/controllers/solid_queue_monitor/ready_jobs_controller.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22

33
module SolidQueueMonitor
44
class ReadyJobsController < BaseController
5+
SORTABLE_COLUMNS = %w[class_name queue_name priority created_at].freeze
6+
57
def index
6-
base_query = SolidQueue::ReadyExecution.includes(:job).order(created_at: :desc)
7-
@ready_jobs = paginate(filter_ready_jobs(base_query))
8+
base_query = SolidQueue::ReadyExecution.includes(:job)
9+
sorted_query = apply_execution_sorting(filter_ready_jobs(base_query), SORTABLE_COLUMNS, 'created_at', :desc)
10+
@ready_jobs = paginate(sorted_query)
811

912
render_page('Ready Jobs', SolidQueueMonitor::ReadyJobsPresenter.new(@ready_jobs[:records],
1013
current_page: @ready_jobs[:current_page],
1114
total_pages: @ready_jobs[:total_pages],
12-
filters: filter_params).render)
15+
filters: filter_params,
16+
sort: sort_params).render)
1317
end
1418
end
1519
end

app/controllers/solid_queue_monitor/recurring_jobs_controller.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22

33
module SolidQueueMonitor
44
class RecurringJobsController < BaseController
5+
SORTABLE_COLUMNS = %w[key class_name queue_name priority].freeze
6+
57
def index
6-
base_query = filter_recurring_jobs(SolidQueue::RecurringTask.order(:key))
7-
@recurring_jobs = paginate(base_query)
8+
base_query = filter_recurring_jobs(SolidQueue::RecurringTask.all)
9+
sorted_query = apply_sorting(base_query, SORTABLE_COLUMNS, 'key', :asc)
10+
@recurring_jobs = paginate(sorted_query)
811

912
render_page('Recurring Jobs', SolidQueueMonitor::RecurringJobsPresenter.new(@recurring_jobs[:records],
1013
current_page: @recurring_jobs[:current_page],
1114
total_pages: @recurring_jobs[:total_pages],
12-
filters: filter_params).render)
15+
filters: filter_params,
16+
sort: sort_params).render)
1317
end
1418
end
1519
end

app/controllers/solid_queue_monitor/scheduled_jobs_controller.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22

33
module SolidQueueMonitor
44
class ScheduledJobsController < BaseController
5+
SORTABLE_COLUMNS = %w[class_name queue_name scheduled_at].freeze
6+
57
def index
6-
base_query = SolidQueue::ScheduledExecution.includes(:job).order(scheduled_at: :asc)
7-
@scheduled_jobs = paginate(filter_scheduled_jobs(base_query))
8+
base_query = SolidQueue::ScheduledExecution.includes(:job)
9+
sorted_query = apply_execution_sorting(filter_scheduled_jobs(base_query), SORTABLE_COLUMNS, 'scheduled_at', :asc)
10+
@scheduled_jobs = paginate(sorted_query)
811

912
render_page('Scheduled Jobs', SolidQueueMonitor::ScheduledJobsPresenter.new(@scheduled_jobs[:records],
1013
current_page: @scheduled_jobs[:current_page],
1114
total_pages: @scheduled_jobs[:total_pages],
12-
filters: filter_params).render)
15+
filters: filter_params,
16+
sort: sort_params).render)
1317
end
1418

1519
def create

app/controllers/solid_queue_monitor/workers_controller.rb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@
22

33
module SolidQueueMonitor
44
class WorkersController < BaseController
5+
SORTABLE_COLUMNS = %w[hostname last_heartbeat_at].freeze
6+
57
def index
6-
base_query = SolidQueue::Process.order(created_at: :desc)
7-
filtered_query = filter_workers(base_query)
8-
@processes = paginate(filtered_query)
8+
base_query = SolidQueue::Process.all
9+
sorted_query = apply_sorting(filter_workers(base_query), SORTABLE_COLUMNS, 'last_heartbeat_at', :desc)
10+
@processes = paginate(sorted_query)
911

1012
render_page('Workers', SolidQueueMonitor::WorkersPresenter.new(
1113
@processes[:records],
1214
current_page: @processes[:current_page],
1315
total_pages: @processes[:total_pages],
14-
filters: worker_filter_params
16+
filters: worker_filter_params,
17+
sort: sort_params
1518
).render)
1619
end
1720

0 commit comments

Comments
 (0)