diff --git a/app/filters/good_job/base_filter.rb b/app/filters/good_job/base_filter.rb
index effffe86b..a4ef6b7e9 100644
--- a/app/filters/good_job/base_filter.rb
+++ b/app/filters/good_job/base_filter.rb
@@ -31,6 +31,15 @@ def queues
.to_h
end
+ def labels
+ filtered_query(params.slice(:queue_name))
+ .joins("CROSS JOIN unnest(labels) AS label")
+ .group("label")
+ .order("label")
+ .count
+ .to_h
+ end
+
def job_classes
filtered_query(params.slice(:queue_name)).unscope(:select)
.group(GoodJob::Job.params_job_class).count
@@ -53,6 +62,7 @@ def to_params(override = {})
queue_name: params[:queue_name],
query: params[:query],
state: params[:state],
+ labels: params[:labels],
cron_key: params[:cron_key],
finished_since: params[:finished_since],
}.merge(override).delete_if { |_, v| v.blank? }
diff --git a/app/filters/good_job/jobs_filter.rb b/app/filters/good_job/jobs_filter.rb
index 514541703..51035cc43 100644
--- a/app/filters/good_job/jobs_filter.rb
+++ b/app/filters/good_job/jobs_filter.rb
@@ -27,6 +27,7 @@ def filtered_query(filter_params = params)
query = query.where(queue_name: filter_params[:queue_name]) if filter_params[:queue_name].present?
query = query.search_text(filter_params[:query]) if filter_params[:query].present?
query = query.where(cron_key: filter_params[:cron_key]) if filter_params[:cron_key].present?
+ query = query.where("labels && ARRAY[?]::text[]", filter_params[:labels]) if filter_params[:labels].present?
query = query.where(finished_at: finished_since(filter_params[:finished_since])..) if filter_params[:finished_since].present?
if filter_params[:state]
diff --git a/app/views/good_job/shared/_filter.erb b/app/views/good_job/shared/_filter.erb
index cd59e0564..169e1f3c4 100644
--- a/app/views/good_job/shared/_filter.erb
+++ b/app/views/good_job/shared/_filter.erb
@@ -31,12 +31,23 @@
+
+ <%= label_tag "label_filter", t(".job_name"), class: "visually-hidden" %>
+
+
+
<%= label_tag "query", t(".search"), class: "visually-hidden" %>
<%= search_field_tag "query", params[:query], class: "form-control form-control-sm", placeholder: t(".placeholder"), autocomplete: "off" %>
-
+
<%= form.submit t(".search"), name: nil, class: "btn btn-primary btn-sm" %>
<%= link_to filter.to_params(job_class: nil, state: nil, queue_name: nil, query: nil), class: "btn btn-secondary btn-sm" do %>
@@ -71,5 +82,29 @@
});
})
})
+
+ document.addEventListener("DOMContentLoaded", () => {
+ const labelFilter = document.getElementById('label_filter');
+
+ labelFilter.addEventListener('change', () => {
+ const selectedValues = Array.from(labelFilter.selectedOptions).map(option => option.value);
+ const allLabelsOption = labelFilter.querySelector('option[value="_all"]');
+
+ if (selectedValues.includes('_all')) {
+ // If "All labels" is selected, remove the name attribute so as not to pass params[:labels]
+ // & deselect other options
+ labelFilter.removeAttribute('name');
+
+ Array.from(labelFilter.options).forEach(option => {
+ if (option.value !== '_all') { option.selected = false; }
+ });
+ } else {
+ // Restore the name attribute & deselect "All labels" if other options are selected
+ labelFilter.setAttribute('name', 'labels[]');
+
+ if (allLabelsOption.selected) { allLabelsOption.selected = false; }
+ }
+ });
+ });
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 3e90e8122..e2ab967b1 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -243,6 +243,7 @@ en:
filter:
all: All
all_jobs: All jobs
+ all_labels: All labels
all_queues: All queues
clear: Clear
job_name: Job name
diff --git a/demo/app/jobs/example_job.rb b/demo/app/jobs/example_job.rb
index 491eff8bf..2d946d081 100644
--- a/demo/app/jobs/example_job.rb
+++ b/demo/app/jobs/example_job.rb
@@ -1,4 +1,5 @@
class ExampleJob < ApplicationJob
+ include GoodJob::ActiveJobExtensions::Labels
ExpectedError = Class.new(StandardError)
DeadError = Class.new(StandardError)
diff --git a/spec/app/filters/good_job/jobs_filter_spec.rb b/spec/app/filters/good_job/jobs_filter_spec.rb
index ae7a26ac3..876ec4c20 100644
--- a/spec/app/filters/good_job/jobs_filter_spec.rb
+++ b/spec/app/filters/good_job/jobs_filter_spec.rb
@@ -15,8 +15,8 @@
GoodJob::Job.order(created_at: :asc).last.update!(cron_key: "frequent_cron")
ActiveJob::Base.queue_adapter = GoodJob::Adapter.new(execution_mode: :inline)
- ExampleJob.set(queue: 'default').perform_later(ExampleJob::SUCCESS_TYPE)
- ExampleJob.set(queue: 'mice').perform_later(ExampleJob::ERROR_ONCE_TYPE)
+ ExampleJob.set(queue: 'default', good_job_labels: ["label1", "label 2"]).perform_later(ExampleJob::SUCCESS_TYPE)
+ ExampleJob.set(queue: 'mice', good_job_labels: ["label 2"]).perform_later(ExampleJob::ERROR_ONCE_TYPE)
Timecop.travel 1.hour.ago
ExampleJob.set(queue: 'elephants').perform_later(ExampleJob::DEAD_TYPE)
@@ -53,6 +53,15 @@
end
end
+ describe '#labels' do
+ it 'is a valid result' do
+ expect(filter.labels).to eq({
+ "label1" => 1,
+ "label 2" => 2,
+ })
+ end
+ end
+
describe '#state_names' do
it 'matches states' do
expect(filter.state_names).to match_array(filter.states.keys)