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)