Files
openfoodnetwork/app/controllers/concerns/reports/ajax_search.rb
Ahmed Ejaz 99e238d92d Improve reports AJAX search safety, permissions, and TomSelect UX
- Sanitize AJAX search queries to safely support wildcard searches (ILIKE)
- Centralize reports search actions in Spree::Ability and reuse across roles
- Fix TomSelect remote loading to fetch on dropdown open and typing
- Surface HTTP errors in TomSelect via showHttpError and improve error handling
- Update dropdown behavior to show proper “no results” feedback
- Move reports AJAX specs to request specs and expand pagination coverage
- Simplify searchable dropdown component attribute passing
2026-02-04 02:25:41 +05:00

109 lines
3.0 KiB
Ruby

# frozen_string_literal: true
module Reports
module AjaxSearch
extend ActiveSupport::Concern
def search_enterprise_fees
report = report_class.new(spree_current_user, params, render: false)
fee_ids = enterprise_fee_ids(report.search.result)
query = EnterpriseFee.where(id: fee_ids)
render json: build_search_response(query)
end
def search_enterprise_fee_owners
report = report_class.new(spree_current_user, params, render: false)
owner_ids = enterprise_fee_owner_ids(report.search.result)
query = Enterprise.where(id: owner_ids)
render json: build_search_response(query)
end
def search_distributors
query = frontend_data.distributors
render json: build_search_response(query)
end
def search_order_cycles
query = frontend_data.order_cycles
render json: build_search_response(query)
end
def search_order_customers
query = frontend_data.order_customers
render json: build_search_response(query)
end
def search_suppliers
query = frontend_data.orders_suppliers
render json: build_search_response(query)
end
private
def build_search_response(query)
page = (params[:page] || 1).to_i
per_page = 30
filtered_query = apply_search_filter(query)
total_count = filtered_query.size
items = paginated_items(filtered_query, page, per_page)
results = format_results(items)
{ results: results, pagination: { more: (page * per_page) < total_count } }
end
def apply_search_filter(query)
search_term = params[:q]
return query if search_term.blank?
escaped_search_term = ActiveRecord::Base.sanitize_sql_like(search_term)
pattern = "%#{escaped_search_term}%"
# Handle different model types
if query.model == OrderCycle
query.where("order_cycles.name ILIKE ?", pattern)
elsif query.model == Customer
query.where("customers.email ILIKE ?", pattern)
else
query.where("name ILIKE ?", pattern)
end
end
def paginated_items(query, page, per_page)
if query.model == Customer
query.order(:email).offset((page - 1) * per_page).limit(per_page).pluck(:email, :id)
elsif query.model == OrderCycle
query.order('order_cycles.orders_close_at DESC')
.offset((page - 1) * per_page)
.limit(per_page).pluck(
:name, :id
)
else
query.order(:name).offset((page - 1) * per_page).limit(per_page).pluck(:name, :id)
end
end
def format_results(items)
items.map { |label, value| { value:, label: } }
end
def frontend_data
@frontend_data ||= Reporting::FrontendData.new(spree_current_user)
end
def enterprise_fee_owner_ids(orders)
EnterpriseFee.where(id: enterprise_fee_ids(orders)).select(:enterprise_id)
end
def enterprise_fee_ids(orders)
Spree::Adjustment.enterprise_fee.where(order_id: orders.select(:id)).select(:originator_id)
end
end
end