mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-02 21:57:17 +00:00
Add "None" option to tags filter and update search functionality
- Implemented apply_tags_filter method to handle "None" option in tag searches. - Updated tags select field to include "None" option in the filters. - Enhanced search_by_tag method in specs to accept multiple tags and raise an error if none are provided. - Added tests for searching by "None" tag and combinations with other tags.
This commit is contained in:
@@ -179,6 +179,8 @@ module Admin
|
||||
product_query = OpenFoodNetwork::Permissions.new(spree_current_user)
|
||||
.editable_products.merge(product_scope_with_includes).ransack(ransack_query).result
|
||||
|
||||
product_query = apply_tags_filter(product_query)
|
||||
|
||||
# Postgres requires ORDER BY expressions to appear in the SELECT list when using DISTINCT.
|
||||
# When the current ransack sort uses the computed stock columns, include them in the select
|
||||
# so the generated COUNT/DISTINCT query is valid.
|
||||
@@ -225,12 +227,54 @@ module Admin
|
||||
query.merge!(Spree::Variant::SEARCH_KEY => @search_term)
|
||||
end
|
||||
query.merge!(variants_primary_taxon_id_in: @category_id) if @category_id.present?
|
||||
query.merge!(variants_tags_name_in: @tags) if @tags.present?
|
||||
query.merge!(@q) if @q
|
||||
|
||||
query
|
||||
end
|
||||
|
||||
# Apply tags filter with OR logic:
|
||||
# - Products with variants having selected tags
|
||||
# - OR products with variants having no tags (when "None" is selected)
|
||||
#
|
||||
# Note: This cannot be implemented using Ransack because Ransack applies
|
||||
# AND semantics across associations and cannot express OR logic that combines
|
||||
# the presence and absence of the same associated records.
|
||||
def apply_tags_filter(base_query)
|
||||
return base_query if @tags.blank?
|
||||
|
||||
tags = Array(@tags)
|
||||
none_key = I18n.t('admin.products_v3.filters.tags.none')
|
||||
|
||||
has_none = tags.include?(none_key)
|
||||
tag_names = tags.reject { |t| t == none_key }
|
||||
|
||||
queries = []
|
||||
|
||||
if tag_names.any?
|
||||
# Products with at least one variant having one of the selected tags
|
||||
tagged_product_ids = Spree::Variant
|
||||
.joins(taggings: :tag)
|
||||
.where(tags: { name: tag_names })
|
||||
.select(:product_id)
|
||||
|
||||
queries << base_query.where(id: tagged_product_ids)
|
||||
end
|
||||
|
||||
if has_none
|
||||
# Products where no variants have any tags
|
||||
tagged_product_ids = Spree::Variant
|
||||
.joins(:taggings)
|
||||
.select(:product_id)
|
||||
|
||||
queries << base_query.where.not(id: tagged_product_ids)
|
||||
end
|
||||
|
||||
return base_query if queries.empty?
|
||||
|
||||
# Combine queries using ActiveRecord's or method
|
||||
queries.reduce { |combined, query| combined.or(query) }
|
||||
end
|
||||
|
||||
# Optimise by pre-loading required columns
|
||||
def product_query_includes
|
||||
[
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
- select_tag_options = { class: "fullwidth",
|
||||
multiple: true ,
|
||||
data: { controller: "tom-select", "tom-select-placeholder-value": t(".select_tag"), "tom-select-options-value": '{ "maxItems": 5 , "plugins": { "remove_button": {} , "no_active_items": {}, "checkbox_options": { "checkedClassNames": ["ts-checked"], "uncheckedClassNames": ["ts-unchecked"] } } }' } }
|
||||
= select_tag :tags_name_in, options_for_select(available_tags, tags), select_tag_options
|
||||
= select_tag :tags_name_in, options_for_select(available_tags.unshift(t('.tags.none')), tags), select_tag_options
|
||||
.submit
|
||||
.search-button
|
||||
= button_tag t(".search"), class: "secondary icon-search relaxed", name: nil
|
||||
|
||||
@@ -977,6 +977,7 @@ en:
|
||||
label: Categories
|
||||
tags:
|
||||
label: Tags
|
||||
none: None
|
||||
search: Search
|
||||
sort:
|
||||
pagination:
|
||||
|
||||
@@ -34,8 +34,12 @@ module ProductsHelper
|
||||
click_button "Search"
|
||||
end
|
||||
|
||||
def search_by_tag(tag)
|
||||
tomselect_multiselect tag, from: "tags_name_in"
|
||||
def search_by_tag(*tags)
|
||||
if tags.empty?
|
||||
raise ArgumentError, "Please provide at least one tag to search for"
|
||||
end
|
||||
|
||||
tags.each { |tag| tomselect_multiselect tag, from: "tags_name_in" }
|
||||
click_button "Search"
|
||||
end
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ module TomselectHelper
|
||||
tomselect_wrapper.find(:css, '.ts-dropdown.multi .ts-dropdown-content .option',
|
||||
text: value).click
|
||||
# Close the dropdown
|
||||
tomselect_wrapper.find(".ts-control").click
|
||||
page.find("body").click
|
||||
end
|
||||
|
||||
def tomselect_search_and_select(value, options)
|
||||
|
||||
@@ -417,16 +417,49 @@ RSpec.describe 'As an enterprise user, I can manage my products' do
|
||||
context "with variant tag", feature: :variant_tag do
|
||||
before do
|
||||
create(:variant, tag_list: "organic")
|
||||
create_products 1
|
||||
create(:variant) # without tags
|
||||
create(:variant)
|
||||
end
|
||||
|
||||
it "can search by tag" do
|
||||
visit admin_products_url
|
||||
search_by_tag "organic"
|
||||
shared_examples "tag search" do
|
||||
it description do
|
||||
visit admin_products_url
|
||||
search_by_tag(*search_tags)
|
||||
|
||||
expect(page).to have_select "tags_name_in", selected: "organic"
|
||||
expect(page).to have_content "1 product found for your search criteria. Showing 1 to 1."
|
||||
expect_products_count_to_be 1
|
||||
expect(page).to have_select("tags_name_in", selected: selected_tags)
|
||||
expect(page).to have_content(result_text)
|
||||
expect_products_count_to_be(expected_count)
|
||||
end
|
||||
end
|
||||
|
||||
context "when searching by a single tag" do
|
||||
let(:description) { "returns variants with that tag" }
|
||||
let(:search_tags) { ["organic"] }
|
||||
let(:selected_tags) { "organic" }
|
||||
let(:expected_count) { 1 }
|
||||
let(:result_text) { "1 product found for your search criteria. Showing 1 to 1." }
|
||||
|
||||
include_examples "tag search"
|
||||
end
|
||||
|
||||
context "when searching by None tag" do
|
||||
let(:description) { "returns variants without tags" }
|
||||
let(:search_tags) { ["None"] }
|
||||
let(:selected_tags) { "None" }
|
||||
let(:expected_count) { 2 }
|
||||
let(:result_text) { "2 products found for your search criteria. Showing 1 to 2." }
|
||||
|
||||
include_examples "tag search"
|
||||
end
|
||||
|
||||
context "when searching by None and another tag" do
|
||||
let(:description) { "returns variants with either no tags or the given tag" }
|
||||
let(:search_tags) { ["None", "organic"] }
|
||||
let(:selected_tags) { ["None", "organic"] }
|
||||
let(:expected_count) { 3 }
|
||||
let(:result_text) { "3 products found for your search criteria. Showing 1 to 3." }
|
||||
|
||||
include_examples "tag search"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user