Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Metrics/ClassLength:
- 'app/presenters/solid_queue_monitor/job_details_presenter.rb'

Metrics/ParameterLists:
Max: 7
Max: 8

Metrics/ModuleLength:
Max: 200
Expand Down
31 changes: 31 additions & 0 deletions app/controllers/solid_queue_monitor/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -202,5 +202,36 @@ def filter_params
status: params[:status]
}
end

def sort_params
{
sort_by: params[:sort_by],
sort_direction: params[:sort_direction]
}
end

def apply_sorting(relation, allowed_columns, default_column, default_direction = :desc)
column = sort_params[:sort_by]
direction = sort_params[:sort_direction]
column = default_column unless allowed_columns.include?(column)
direction = %w[asc desc].include?(direction) ? direction.to_sym : default_direction
relation.order(column => direction)
end

def apply_execution_sorting(relation, allowed_columns, default_column, default_direction = :desc)
column = sort_params[:sort_by]
direction = sort_params[:sort_direction]
column = default_column unless allowed_columns.include?(column)
direction = %w[asc desc].include?(direction) ? direction.to_sym : default_direction

# Columns that exist on the jobs table, not on execution tables
job_table_columns = %w[class_name queue_name]

if job_table_columns.include?(column)
relation.joins(:job).order("solid_queue_jobs.#{column}" => direction)
else
relation.order(column => direction)
end
end
end
end
10 changes: 7 additions & 3 deletions app/controllers/solid_queue_monitor/failed_jobs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

module SolidQueueMonitor
class FailedJobsController < BaseController
SORTABLE_COLUMNS = %w[class_name queue_name created_at].freeze

def index
base_query = SolidQueue::FailedExecution.includes(:job).order(created_at: :desc)
@failed_jobs = paginate(filter_failed_jobs(base_query))
base_query = SolidQueue::FailedExecution.includes(:job)
sorted_query = apply_execution_sorting(filter_failed_jobs(base_query), SORTABLE_COLUMNS, 'created_at', :desc)
@failed_jobs = paginate(sorted_query)

render_page('Failed Jobs', SolidQueueMonitor::FailedJobsPresenter.new(@failed_jobs[:records],
current_page: @failed_jobs[:current_page],
total_pages: @failed_jobs[:total_pages],
filters: filter_params).render)
filters: filter_params,
sort: sort_params).render)
end

def retry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

module SolidQueueMonitor
class InProgressJobsController < BaseController
SORTABLE_COLUMNS = %w[class_name queue_name created_at].freeze

def index
base_query = SolidQueue::ClaimedExecution.includes(:job).order(created_at: :desc)
@in_progress_jobs = paginate(filter_in_progress_jobs(base_query))
base_query = SolidQueue::ClaimedExecution.includes(:job)
sorted_query = apply_execution_sorting(filter_in_progress_jobs(base_query), SORTABLE_COLUMNS, 'created_at', :desc)
@in_progress_jobs = paginate(sorted_query)

render_page('In Progress Jobs', SolidQueueMonitor::InProgressJobsPresenter.new(@in_progress_jobs[:records],
current_page: @in_progress_jobs[:current_page],
total_pages: @in_progress_jobs[:total_pages],
filters: filter_params).render)
filters: filter_params,
sort: sort_params).render)
end

private
Expand Down
10 changes: 7 additions & 3 deletions app/controllers/solid_queue_monitor/overview_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

module SolidQueueMonitor
class OverviewController < BaseController
SORTABLE_COLUMNS = %w[class_name queue_name created_at].freeze

def index
@stats = SolidQueueMonitor::StatsCalculator.calculate
@chart_data = SolidQueueMonitor::ChartDataService.new(time_range: time_range_param).calculate

recent_jobs_query = SolidQueue::Job.order(created_at: :desc).limit(100)
@recent_jobs = paginate(filter_jobs(recent_jobs_query))
recent_jobs_query = SolidQueue::Job.limit(100)
sorted_query = apply_sorting(filter_jobs(recent_jobs_query), SORTABLE_COLUMNS, 'created_at', :desc)
@recent_jobs = paginate(sorted_query)

preload_job_statuses(@recent_jobs[:records])

Expand All @@ -31,7 +34,8 @@ def generate_overview_content
SolidQueueMonitor::JobsPresenter.new(@recent_jobs[:records],
current_page: @recent_jobs[:current_page],
total_pages: @recent_jobs[:total_pages],
filters: filter_params).render
filters: filter_params,
sort: sort_params).render
end
end
end
29 changes: 21 additions & 8 deletions app/controllers/solid_queue_monitor/queues_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,26 @@

module SolidQueueMonitor
class QueuesController < BaseController
SORTABLE_COLUMNS = %w[queue_name job_count].freeze
QUEUE_DETAILS_SORTABLE_COLUMNS = %w[class_name created_at].freeze

def index
@queues = SolidQueue::Job.group(:queue_name)
.select('queue_name, COUNT(*) as job_count')
.order('job_count DESC')
base_query = SolidQueue::Job.group(:queue_name)
.select('queue_name, COUNT(*) as job_count')
@queues = apply_queue_sorting(base_query)
@paused_queues = QueuePauseService.paused_queues

render_page('Queues', SolidQueueMonitor::QueuesPresenter.new(@queues, @paused_queues).render)
render_page('Queues', SolidQueueMonitor::QueuesPresenter.new(@queues, @paused_queues, sort: sort_params).render)
end

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

# Get all jobs for this queue with filtering and pagination
base_query = SolidQueue::Job.where(queue_name: @queue_name).order(created_at: :desc)
filtered_query = filter_queue_jobs(base_query)
@jobs = paginate(filtered_query)
base_query = SolidQueue::Job.where(queue_name: @queue_name)
sorted_query = apply_sorting(filter_queue_jobs(base_query), QUEUE_DETAILS_SORTABLE_COLUMNS, 'created_at', :desc)
@jobs = paginate(sorted_query)
preload_job_statuses(@jobs[:records])

@counts = calculate_queue_counts(@queue_name)
Expand All @@ -31,7 +34,8 @@ def show
counts: @counts,
current_page: @jobs[:current_page],
total_pages: @jobs[:total_pages],
filters: queue_filter_params
filters: queue_filter_params,
sort: sort_params
).render)
end

Expand Down Expand Up @@ -97,5 +101,14 @@ def queue_filter_params
status: params[:status]
}
end

def apply_queue_sorting(relation)
column = sort_params[:sort_by]
direction = sort_params[:sort_direction]
column = 'job_count' unless SORTABLE_COLUMNS.include?(column)
direction = 'desc' unless %w[asc desc].include?(direction)

relation.order("#{column} #{direction}")
end
end
end
10 changes: 7 additions & 3 deletions app/controllers/solid_queue_monitor/ready_jobs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

module SolidQueueMonitor
class ReadyJobsController < BaseController
SORTABLE_COLUMNS = %w[class_name queue_name priority created_at].freeze

def index
base_query = SolidQueue::ReadyExecution.includes(:job).order(created_at: :desc)
@ready_jobs = paginate(filter_ready_jobs(base_query))
base_query = SolidQueue::ReadyExecution.includes(:job)
sorted_query = apply_execution_sorting(filter_ready_jobs(base_query), SORTABLE_COLUMNS, 'created_at', :desc)
@ready_jobs = paginate(sorted_query)

render_page('Ready Jobs', SolidQueueMonitor::ReadyJobsPresenter.new(@ready_jobs[:records],
current_page: @ready_jobs[:current_page],
total_pages: @ready_jobs[:total_pages],
filters: filter_params).render)
filters: filter_params,
sort: sort_params).render)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

module SolidQueueMonitor
class RecurringJobsController < BaseController
SORTABLE_COLUMNS = %w[key class_name queue_name priority].freeze

def index
base_query = filter_recurring_jobs(SolidQueue::RecurringTask.order(:key))
@recurring_jobs = paginate(base_query)
base_query = filter_recurring_jobs(SolidQueue::RecurringTask.all)
sorted_query = apply_sorting(base_query, SORTABLE_COLUMNS, 'key', :asc)
@recurring_jobs = paginate(sorted_query)

render_page('Recurring Jobs', SolidQueueMonitor::RecurringJobsPresenter.new(@recurring_jobs[:records],
current_page: @recurring_jobs[:current_page],
total_pages: @recurring_jobs[:total_pages],
filters: filter_params).render)
filters: filter_params,
sort: sort_params).render)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

module SolidQueueMonitor
class ScheduledJobsController < BaseController
SORTABLE_COLUMNS = %w[class_name queue_name scheduled_at].freeze

def index
base_query = SolidQueue::ScheduledExecution.includes(:job).order(scheduled_at: :asc)
@scheduled_jobs = paginate(filter_scheduled_jobs(base_query))
base_query = SolidQueue::ScheduledExecution.includes(:job)
sorted_query = apply_execution_sorting(filter_scheduled_jobs(base_query), SORTABLE_COLUMNS, 'scheduled_at', :asc)
@scheduled_jobs = paginate(sorted_query)

render_page('Scheduled Jobs', SolidQueueMonitor::ScheduledJobsPresenter.new(@scheduled_jobs[:records],
current_page: @scheduled_jobs[:current_page],
total_pages: @scheduled_jobs[:total_pages],
filters: filter_params).render)
filters: filter_params,
sort: sort_params).render)
end

def create
Expand Down
11 changes: 7 additions & 4 deletions app/controllers/solid_queue_monitor/workers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@

module SolidQueueMonitor
class WorkersController < BaseController
SORTABLE_COLUMNS = %w[hostname last_heartbeat_at].freeze

def index
base_query = SolidQueue::Process.order(created_at: :desc)
filtered_query = filter_workers(base_query)
@processes = paginate(filtered_query)
base_query = SolidQueue::Process.all
sorted_query = apply_sorting(filter_workers(base_query), SORTABLE_COLUMNS, 'last_heartbeat_at', :desc)
@processes = paginate(sorted_query)

render_page('Workers', SolidQueueMonitor::WorkersPresenter.new(
@processes[:records],
current_page: @processes[:current_page],
total_pages: @processes[:total_pages],
filters: worker_filter_params
filters: worker_filter_params,
sort: sort_params
).render)
end

Expand Down
52 changes: 47 additions & 5 deletions app/presenters/solid_queue_monitor/base_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,34 @@ def queue_link(queue_name, css_class: nil)
"<a href=\"#{queue_details_path(queue_name: queue_name)}\" class=\"#{classes}\">#{queue_name}</a>"
end

def sortable_header(column, label)
return "<th>#{label}</th>" unless @sort

column_str = column.to_s
is_active = @sort[:sort_by] == column_str
next_direction = is_active && @sort[:sort_direction] == 'asc' ? 'desc' : 'asc'
arrow = sort_arrow(is_active)
css_class = is_active ? 'sortable-header active' : 'sortable-header'

"<th><a href=\"?sort_by=#{column}&sort_direction=#{next_direction}#{filter_query_string}\" class=\"#{css_class}\">#{label}#{arrow}</a></th>"
end

def sort_arrow(is_active)
return ' &udarr;' unless is_active

@sort[:sort_direction] == 'asc' ? ' &uarr;' : ' &darr;'
end

def filter_query_string
params = []
params << "class_name=#{@filters[:class_name]}" if @filters && @filters[:class_name].present?
params << "queue_name=#{@filters[:queue_name]}" if @filters && @filters[:queue_name].present?
params << "arguments=#{@filters[:arguments]}" if @filters && @filters[:arguments].present?
params << "status=#{@filters[:status]}" if @filters && @filters[:status].present?

params.empty? ? '' : "&#{params.join('&')}"
end

def request_path
if defined?(controller) && controller.respond_to?(:request)
controller.request.path
Expand All @@ -138,14 +166,28 @@ def engine_mount_point
private

def query_params
params = []
params << "class_name=#{@filters[:class_name]}" if @filters && @filters[:class_name].present?
params << "queue_name=#{@filters[:queue_name]}" if @filters && @filters[:queue_name].present?
params << "status=#{@filters[:status]}" if @filters && @filters[:status].present?

params = build_filter_params + build_sort_params
params.empty? ? '' : "&#{params.join('&')}"
end

def build_filter_params
return [] unless @filters

filter_keys = %i[class_name queue_name status]
filter_keys.filter_map do |key|
"#{key}=#{@filters[key]}" if @filters[key].present?
end
end

def build_sort_params
return [] unless @sort

sort_keys = %i[sort_by sort_direction]
sort_keys.filter_map do |key|
"#{key}=#{@sort[key]}" if @sort[key].present?
end
end

def full_path(route_name, *args)
SolidQueueMonitor::Engine.routes.url_helpers.send(route_name, *args)
rescue NoMethodError
Expand Down
12 changes: 6 additions & 6 deletions app/presenters/solid_queue_monitor/failed_jobs_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ class FailedJobsPresenter < BasePresenter
include Rails.application.routes.url_helpers
include SolidQueueMonitor::Engine.routes.url_helpers

def initialize(jobs, current_page: 1, total_pages: 1, filters: {})
def initialize(jobs, current_page: 1, total_pages: 1, filters: {}, sort: {})
@jobs = jobs
@current_page = current_page
@total_pages = total_pages
@filters = filters
@sort = sort
end

def render
Expand Down Expand Up @@ -60,10 +61,11 @@ def generate_table
<thead>
<tr>
<th><input type="checkbox" id="select-all" class="select-all-checkbox"></th>
<th>Job</th>
<th>Queue</th>
#{sortable_header('class_name', 'Job')}
#{sortable_header('queue_name', 'Queue')}
<th>Error</th>
<th>Arguments</th>
#{sortable_header('created_at', 'Failed At')}
<th>Actions</th>
</tr>
</thead>
Expand Down Expand Up @@ -261,11 +263,9 @@ def generate_row(failed_execution)
</td>
<td>
<div class="error-message">#{error[:message].to_s.truncate(100)}</div>
<div class="job-meta">
<span class="job-timestamp">Failed at: #{format_datetime(failed_execution.created_at)}</span>
</div>
</td>
<td>#{format_arguments(job.arguments)}</td>
<td>#{format_datetime(failed_execution.created_at)}</td>
<td class="actions-cell">
<div class="job-actions">
<a href="javascript:void(0)"#{' '}
Expand Down
Loading