-
- <%= form_for :filter, url: application_jobs_path(MissionControl::Jobs::Current.application, jobs_status), method: :get,
- data: { controller: "form", action: "input->form#debouncedSubmit" } do |form| %>
+
+ <%= form_for :filter, url: application_jobs_path(MissionControl::Jobs::Current.application, jobs_status), method: :get,
+ data: { controller: "form", action: "input->form#debouncedSubmit" } do |form| %>
+
+
+ <%= form.label :job_class_name, class: "label" %>
<%= form.text_field :job_class_name, value: @job_filters[:job_class_name], class: "input", list: "job-classes", placeholder: "Filter by job class...", autocomplete: "off" %>
+
+
+ <%= form.label :queue_name, class: "label" %>
<%= form.text_field :queue_name, value: @job_filters[:queue_name], class: "input", list: "queue-names", placeholder: "Filter by queue name...", autocomplete: "off" %>
+
- <% if jobs_status == "finished" %>
+ <% if jobs_status == "finished" %>
+
+ <%= form.label :finished_at_start, class: "label" %>
<%= form.datetime_field :finished_at_start, value: @job_filters[:finished_at]&.begin, class: "input", placeholder: "Finished from" %>
+
+
+ <%= form.label :finished_at_end, class: "label" %>
+ <%# TODO: add max date of today? %>
<%= form.datetime_field :finished_at_end, value: @job_filters[:finished_at]&.end, class: "input", placeholder: "Finished to" %>
- <% end %>
+
+ <% end %>
- <%= hidden_field_tag :server_id, MissionControl::Jobs::Current.server.id %>
+
+ <%= form.label :enqueued_at_start, class: "label" %>
+
+ <%= form.datetime_field :enqueued_at_start, value: @job_filters[:enqueued_at]&.begin, class: "input", placeholder: "Enqueued from" %>
+
+
-
+
+ <%= form.label :enqueued_at_end, class: "label" %>
+
+ <%# TODO: add max date of today? %>
+ <%= form.datetime_field :enqueued_at_end, value: @job_filters[:enqueued_at]&.end, class: "input", placeholder: "Enqueued to" %>
+
+
-
- <% end %>
-
+
+ <%= form.label :scheduled_at_start, class: "label" %>
+
+ <%= form.datetime_field :scheduled_at_start, value: @job_filters[:scheduled_at]&.begin, class: "input", placeholder: "Scheduled from" %>
+
+
+
+
+ <%= form.label :scheduled_at_start, class: "label" %>
+
+ <%# TODO: add max date of today? %>
+ <%= form.datetime_field :scheduled_at_end, value: @job_filters[:scheduled_at]&.end, class: "input", placeholder: "Scheduled to" %>
+
+
+
+ <%= hidden_field_tag :server_id, MissionControl::Jobs::Current.server.id %>
+
+
+
+
-
- <%= link_to "Clear", application_jobs_path(MissionControl::Jobs::Current.application, jobs_status, job_class_name: nil, queue_name: nil, finished_at: nil..nil), class: "button" %>
+
+ <%= link_to "Clear", application_jobs_path(MissionControl::Jobs::Current.application, jobs_status, job_class_name: nil, queue_name: nil, finished_at: nil..nil), class: "button" %>
+
-
+ <% end %>
diff --git a/app/views/mission_control/jobs/jobs/pending/_job.html.erb b/app/views/mission_control/jobs/jobs/pending/_job.html.erb
new file mode 100644
index 00000000..06c4efed
--- /dev/null
+++ b/app/views/mission_control/jobs/jobs/pending/_job.html.erb
@@ -0,0 +1,2 @@
+
<%= link_to job.queue_name, application_queue_path(@application, job.queue) %> |
+
Ready to run |
\ No newline at end of file
diff --git a/lib/active_job/jobs_relation.rb b/lib/active_job/jobs_relation.rb
index 0d71e5e4..246d15a7 100644
--- a/lib/active_job/jobs_relation.rb
+++ b/lib/active_job/jobs_relation.rb
@@ -23,9 +23,9 @@ class ActiveJob::JobsRelation
include Enumerable
STATUSES = %i[ pending failed in_progress blocked scheduled finished ]
- FILTERS = %i[ queue_name job_class_name ]
+ FILTERS = %i[ queue_name job_class_name finished_at scheduled_at enqueued_at ]
- PROPERTIES = %i[ queue_name status offset_value limit_value job_class_name worker_id recurring_task_id finished_at ]
+ PROPERTIES = %i[ queue_name status offset_value limit_value job_class_name worker_id recurring_task_id finished_at scheduled_at enqueued_at ]
attr_reader *PROPERTIES, :default_page_size
delegate :last, :[], :reverse, to: :to_a
@@ -52,15 +52,21 @@ def initialize(queue_adapter: ActiveJob::Base.queue_adapter, default_page_size:
# *
:worker_id - To only include the jobs processed by the provided worker.
# *
:recurring_task_id - To only include the jobs corresponding to runs of a recurring task.
# *
:finished_at - (Range) To only include the jobs finished between the provided range
- def where(job_class_name: nil, queue_name: nil, worker_id: nil, recurring_task_id: nil, finished_at: nil)
+ # *
:scheduled_at - (Range) To only include the jobs scheduled between the provided range
+ # *
:enqueued_at - (Range) To only include the jobs enqueued between the provided range
+ def where(job_class_name: nil, queue_name: nil, worker_id: nil, recurring_task_id: nil, finished_at: nil, scheduled_at: nil, enqueued_at: nil)
# Remove nil arguments to avoid overriding parameters when concatenating +where+ clauses
arguments = { job_class_name: job_class_name,
queue_name: queue_name&.to_s,
worker_id: worker_id,
recurring_task_id: recurring_task_id,
- finished_at: finished_at
+ finished_at: finished_at,
+ scheduled_at: scheduled_at,
+ enqueued_at: enqueued_at
}.compact
+ # TODO: is this collect needed? .collect { |key, value| [ key, value.to_s ] }.to_h
+
clone_with **arguments
end
@@ -268,11 +274,22 @@ def loaded?
# Filtering for not natively supported filters is performed in memory
def filter(jobs)
- jobs.filter { |job| satisfy_filter?(job) }
+ jobs.filter { |job| satisfies_filters?(job) }
+ end
+
+ def satisfies_filter?(filter_value, job_value)
+ return filter_value.cover?(job_value) if filter_value.is_a?(Range) # TODO: needed? && job_value.is_a?(ActiveSupport::TimeWithZone)
+
+ filter_value == job_value
end
- def satisfy_filter?(job)
- filters.all? { |property| public_send(property) == job.public_send(property) }
+ def satisfies_filters?(job)
+ filters.all? do |property|
+ filter_value = public_send(property)
+ job_value = job.public_send(property)
+
+ satisfies_filter?(filter_value, job_value)
+ end
end
def filters
diff --git a/test/controllers/jobs_controller_test.rb b/test/controllers/jobs_controller_test.rb
index 54a36120..fedebb27 100644
--- a/test/controllers/jobs_controller_test.rb
+++ b/test/controllers/jobs_controller_test.rb
@@ -63,6 +63,48 @@ class MissionControl::Jobs::JobsControllerTest < ActionDispatch::IntegrationTest
assert_select "tr.job", 1
end
+ test "get pending jobs filtered by enqueued_at date" do
+ job = DummyJob.perform_later(42)
+
+ get mission_control_jobs.application_jobs_url(@application, :pending)
+ assert_response :ok
+ assert_select "tr.job", 1
+
+ get mission_control_jobs.application_jobs_url(@application, :pending, filter: { enqueued_at_start: 1.hour.from_now.to_s })
+ assert_response :ok
+ assert_select "tr.job", 0
+
+ get mission_control_jobs.application_jobs_url(@application, :pending, filter: { enqueued_at_start: 1.hour.ago.to_s, enqueued_at_end: 1.hour.from_now })
+ assert_response :ok
+ assert_select "tr.job", 1
+
+ get mission_control_jobs.application_jobs_url(@application, :pending, filter: { enqueued_at_end: 1.hour.from_now })
+ assert_response :ok
+ assert_select "tr.job", 1
+ end
+
+ test "get scheduled jobs filtered by scheduled_at date" do
+ job = DummyJob.set(wait: 30.minutes).perform_later(42)
+
+ get mission_control_jobs.application_jobs_url(@application, :scheduled)
+ assert_response :ok
+ assert_select "tr.job", 1
+
+ get mission_control_jobs.application_jobs_url(@application, :scheduled, filter: { scheduled_at_start: 1.hour.from_now.to_s })
+ assert_response :ok
+ assert_select "tr.job", 0
+
+ get mission_control_jobs.application_jobs_url(@application, :scheduled, filter: { scheduled_at_start: 15.minutes.from_now.to_s, scheduled_at_end: 45.minutes.from_now })
+ assert_response :ok
+ assert_select "tr.job", 1
+
+ get mission_control_jobs.application_jobs_url(@application, :scheduled, filter: { scheduled_at_end: 45.minutes.from_now })
+ assert_response :ok
+ assert_select "tr.job", 1
+ end
+
+ # TODO: hitting clear on the date input should refresh the list?
+
test "redirect to queue when job doesn't exist" do
job = DummyJob.perform_later(42)