Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport #3367 to 8.x (Add a way to filter facets in the facets "more" modal) #3369

Merged
merged 6 commits into from
Dec 17, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -43,6 +43,7 @@ Metrics/BlockLength:

Metrics/ClassLength:
Exclude:
- "app/services/blacklight/search_service.rb"
- "lib/blacklight/configuration.rb"
- "lib/blacklight/search_builder.rb"
- "lib/blacklight/search_state.rb"
9 changes: 9 additions & 0 deletions app/components/blacklight/search/facet_suggest_input.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<label for="facet_suggest_<%= facet.key %>">
<%= I18n.t('blacklight.search.facets.suggest.label', field_label: presenter&.label) %>
</label>
<%= text_field_tag "facet_suggest_#{facet.key}",
nil,
class: "facet-suggest form-control",
data: {facet_field: facet.key},
placeholder: I18n.t('blacklight.search.form.search.placeholder')
%>
20 changes: 20 additions & 0 deletions app/components/blacklight/search/facet_suggest_input.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module Blacklight
module Search
class FacetSuggestInput < Blacklight::Component
def initialize(facet:, presenter:)
@facet = facet
@presenter = presenter
end

private

attr_accessor :facet, :presenter

def render?
facet&.suggest
end
end
end
end
8 changes: 7 additions & 1 deletion app/controllers/concerns/blacklight/catalog.rb
Original file line number Diff line number Diff line change
@@ -83,7 +83,11 @@ def facet
@facet = blacklight_config.facet_fields[params[:id]]
raise ActionController::RoutingError, 'Not Found' unless @facet

@response = search_service.facet_field_response(@facet.key)
@response = if params[:query_fragment].present?
search_service.facet_suggest_response(@facet.key, params[:query_fragment])
else
search_service.facet_field_response(@facet.key)
end
@display_facet = @response.aggregations[@facet.field]

@presenter = @facet.presenter.new(@facet, @display_facet, view_context)
@@ -92,6 +96,8 @@ def facet
format.html do
# Draw the partial for the "more" facet modal window:
return render layout: false if request.xhr?
# Only show the facet names and their values:
return render 'facet_values', layout: false if params[:only_values]
# Otherwise draw the facet selector for users who have javascript disabled.
end
format.json
15 changes: 15 additions & 0 deletions app/javascript/blacklight/debounce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Usage:
// ```
// const basicFunction = (entry) => console.log(entry)
// const debounced = debounce(basicFunction("I should only be called once"));
//
// debounced // does NOT print to the screen because it is invoked again less than 200 milliseconds later
// debounced // does print to the screen
// ```
export default function debounce(func, timeout = 200) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
26 changes: 26 additions & 0 deletions app/javascript/blacklight/facet_suggest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import debounce from "blacklight/debounce";

const FacetSuggest = async (e) => {
if (e.target.matches('.facet-suggest')) {
const queryFragment = e.target.value?.trim();
const facetField = e.target.dataset.facetField;
if (!facetField) { return; }

const urlToFetch = `/catalog/facet_suggest/${facetField}/${queryFragment}`
const response = await fetch(urlToFetch);
if (response.ok) {
const blob = await response.blob()
const text = await blob.text()

const facetArea = document.querySelector('.facet-extended-list');

if (text && facetArea) {
facetArea.innerHTML = text
}
}
}
};

document.addEventListener('input', debounce(FacetSuggest));

export default FacetSuggest
2 changes: 2 additions & 0 deletions app/javascript/blacklight/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import BookmarkToggle from 'blacklight/bookmark_toggle'
import ButtonFocus from 'blacklight/button_focus'
import FacetSuggest from 'blacklight/facet_suggest'
import Modal from 'blacklight/modal'
import SearchContext from 'blacklight/search_context'
import Core from 'blacklight/core'

export default {
BookmarkToggle,
ButtonFocus,
FacetSuggest,
Modal,
SearchContext,
Core,
3 changes: 2 additions & 1 deletion app/presenters/blacklight/facet_field_presenter.rb
Original file line number Diff line number Diff line change
@@ -31,7 +31,8 @@ def in_advanced_search?
end

def in_modal?
search_state.params[:action] == "facet"
modal_like_actions = %w[facet facet_suggest]
modal_like_actions.include? search_state.params[:action]
end

def modal_path
5 changes: 5 additions & 0 deletions app/services/blacklight/search_service.rb
Original file line number Diff line number Diff line change
@@ -62,6 +62,11 @@ def facet_field_response(facet_field, extra_controller_params = {})
repository.search(query.merge(extra_controller_params))
end

def facet_suggest_response(facet_field, facet_suggestion_query, extra_controller_params = {})
query = search_builder.with(search_state).facet(facet_field).facet_suggestion_query(facet_suggestion_query)
repository.search(query.merge(extra_controller_params))
end

# Get the previous and next document from a search result
# @return [Blacklight::Solr::Response, Array<Blacklight::SolrDocument>] the solr response and a list of the first and last document
def previous_and_next_documents_for_search(index, request_params, extra_controller_params = {})
1 change: 1 addition & 0 deletions app/views/catalog/facet.html.erb
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
<% end %>

<% component.with_title { facet_field_label(@facet.key) } %>
<%= render Blacklight::Search::FacetSuggestInput.new(facet: @facet, presenter: @presenter) %>
sandbergja marked this conversation as resolved.
Show resolved Hide resolved

<%= render partial: 'facet_index_navigation' if @facet.index_range && @display_facet.index? %>

3 changes: 3 additions & 0 deletions app/views/catalog/facet_values.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<%= render Blacklight::FacetComponent.new(display_facet: @display_facet,
blacklight_config: blacklight_config,
layout: false) %>
3 changes: 3 additions & 0 deletions config/locales/blacklight.ar.yml
Original file line number Diff line number Diff line change
@@ -111,6 +111,9 @@ ar:
sort:
count: ترتيب رقمي
index: ترتيب أبجدي
suggest:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it expected that these be in the target language? Or is it ok to start with English and then have a future edit with the real translation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out. It looks like I used the add_missing task from i18n-tasks, when I should have used translate_missing, which grabs a preliminary translation from Google Translate.

label: الفلتر %{field_label}
placeholder: فلتر...
title: تحديد نطاق البحث
filters:
label: "%{label}:"
3 changes: 3 additions & 0 deletions config/locales/blacklight.de.yml
Original file line number Diff line number Diff line change
@@ -102,6 +102,9 @@ de:
sort:
count: Numerisch ordnen
index: A-Z Ordnen
suggest:
label: Filter %{field_label}
placeholder: Filter...
title: Suche beschränken
filters:
label: "%{label}:"
3 changes: 3 additions & 0 deletions config/locales/blacklight.en.yml
Original file line number Diff line number Diff line change
@@ -120,6 +120,9 @@ en:
sort:
count: Numerical Sort
index: A-Z Sort
suggest:
label: "Filter %{field_label}"
placeholder: Filter...
title: Limit your search
filters:
label: "%{label}:"
3 changes: 3 additions & 0 deletions config/locales/blacklight.es.yml
Original file line number Diff line number Diff line change
@@ -101,6 +101,9 @@ es:
sort:
count: Ordenación numérica
index: Ordenación A-Z
suggest:
label: Filtro %{field_label}
placeholder: Filtrar...
title: Limite su búsqueda
filters:
label: "%{label}:"
3 changes: 3 additions & 0 deletions config/locales/blacklight.fr.yml
Original file line number Diff line number Diff line change
@@ -101,6 +101,9 @@ fr:
sort:
count: Du + au - fréquent
index: Tri de A à Z
suggest:
label: Filtre %{field_label}
placeholder: Filtre...
title: Limiter votre recherche
filters:
label: "%{label}:"
3 changes: 3 additions & 0 deletions config/locales/blacklight.hu.yml
Original file line number Diff line number Diff line change
@@ -99,6 +99,9 @@ hu:
sort:
count: Numerikus rendezés
index: A-Z rendezés
suggest:
label: Szűrő %{field_label}
placeholder: Szűrő...
title: Szűrje tovább a keresését
filters:
label: "%{label}:"
3 changes: 3 additions & 0 deletions config/locales/blacklight.it.yml
Original file line number Diff line number Diff line change
@@ -102,6 +102,9 @@ it:
sort:
count: Ordina per numero
index: Ordina A-Z
suggest:
label: Filtro %{field_label}
placeholder: Filtro...
title: Affina la ricerca
filters:
label: "%{label}:"
3 changes: 3 additions & 0 deletions config/locales/blacklight.nl.yml
Original file line number Diff line number Diff line change
@@ -99,6 +99,9 @@ nl:
sort:
count: Numeriek sorteren
index: A-Z Sorteren
suggest:
label: Filteren %{field_label}
placeholder: Filter...
title: Verfijn uw zoekopdracht
filters:
label: "%{label}:"
3 changes: 3 additions & 0 deletions config/locales/blacklight.pt-BR.yml
Original file line number Diff line number Diff line change
@@ -100,6 +100,9 @@ pt-BR:
sort:
count: Ordenar por Número
index: Ordem Alfabética A-Z
suggest:
label: Filtro %{field_label}
placeholder: Filtro...
title: Filtre sua busca
filters:
label: "%{label}:"
3 changes: 3 additions & 0 deletions config/locales/blacklight.sq.yml
Original file line number Diff line number Diff line change
@@ -99,6 +99,9 @@ sq:
sort:
count: Renditja numerike
index: A-Z Renditja
suggest:
label: Filtri %{field_label}
placeholder: Filtro...
title: Kufizo këkimin
filters:
label: "%{label}:"
3 changes: 3 additions & 0 deletions config/locales/blacklight.zh.yml
Original file line number Diff line number Diff line change
@@ -99,6 +99,9 @@ zh:
sort:
count: 按数量排序
index: 按字母排序
suggest:
label: 过滤器 %{field_label}
placeholder: 筛选...
title: 限定搜索
filters:
label: "%{label}:"
3 changes: 2 additions & 1 deletion lib/blacklight/configuration.rb
Original file line number Diff line number Diff line change
@@ -35,7 +35,8 @@ def initialized_default_configuration?
end
end

BASIC_SEARCH_PARAMETERS = [:q, :qt, :page, :per_page, :search_field, :sort, :controller, :action, :'facet.page', :'facet.prefix', :'facet.sort', :rows, :format, :view].freeze
BASIC_SEARCH_PARAMETERS = [:q, :qt, :page, :per_page, :search_field, :sort, :controller, :action, :'facet.page', :'facet.prefix', :'facet.sort', :rows, :format, :view, :id, :facet_id,
:query_fragment, :only_values].freeze
ADVANCED_SEARCH_PARAMETERS = [{ clause: {} }, :op].freeze

# rubocop:disable Metrics/BlockLength
1 change: 1 addition & 0 deletions lib/blacklight/routes/searchable.rb
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ def call(mapper, _options = {})
mapper.get "opensearch"
mapper.get 'suggest', as: 'suggest_index'
mapper.get "facet/:id", action: 'facet', as: 'facet'
mapper.get "facet_suggest/:id/(:query_fragment)", action: 'facet', as: 'facet_suggest', defaults: { only_values: true }
end
end
end
13 changes: 13 additions & 0 deletions lib/blacklight/search_builder.rb
Original file line number Diff line number Diff line change
@@ -224,6 +224,19 @@ def facet(value = nil)
@facet
end

def facet_suggestion_query=(value)
params_will_change!
@facet_suggestion_query = value
end

def facet_suggestion_query(value = nil)
if value
self.facet_suggestion_query = value
return self
end
@facet_suggestion_query
end

# Decode the user provided 'sort' parameter into a sort string that can be
# passed to the search. This sanitizes the input by ensuring only
# configured search values are passed through to the search.
10 changes: 9 additions & 1 deletion lib/blacklight/solr/search_builder_behavior.rb
Original file line number Diff line number Diff line change
@@ -10,7 +10,8 @@ module SearchBuilderBehavior
:add_query_to_solr, :add_facet_fq_to_solr,
:add_facetting_to_solr, :add_solr_fields_to_query, :add_paging_to_solr,
:add_sorting_to_solr, :add_group_config_to_solr,
:add_facet_paging_to_solr, :add_adv_search_clauses,
:add_facet_paging_to_solr, :add_facet_suggestion_parameters,
:add_adv_search_clauses,
:add_additional_filters
]
end
@@ -276,6 +277,13 @@ def add_facet_paging_to_solr(solr_params)
solr_params[:"f.#{facet_config.field}.facet.prefix"] = prefix if prefix
end

def add_facet_suggestion_parameters(solr_params)
return if facet.blank? || facet_suggestion_query.blank?

solr_params[:'facet.contains'] = facet_suggestion_query[0..50]
solr_params[:'facet.contains.ignoreCase'] = true
end

def with_ex_local_param(ex, value)
if ex
"{!ex=#{ex}}#{value}"
49 changes: 49 additions & 0 deletions spec/components/blacklight/search/facet_suggest_input_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Blacklight::Search::FacetSuggestInput, type: :component do
let(:facet) { Blacklight::Configuration::FacetField.new key: 'language_facet', suggest: true }
let(:presenter) { instance_double(Blacklight::FacetFieldPresenter) }

before do
allow(presenter).to receive(:label).and_return 'Language'
end

it 'has an input with the facet-suggest class, which the javascript needs to find it' do
rendered = render_inline(described_class.new(facet: facet, presenter: presenter))
expect(rendered.css("input.facet-suggest").count).to eq 1
end

it 'has an input with the data-facet-field attribute, which the javascript needs to determine the correct query' do
rendered = render_inline(described_class.new(facet: facet, presenter: presenter))
expect(rendered.css('input[data-facet-field="language_facet"]').count).to eq 1
end

it 'has a visible label that is associated with the input' do
rendered = render_inline(described_class.new(facet: facet, presenter: presenter))
label = rendered.css('label').first
expect(label.text.strip).to eq 'Filter Language'

id_in_label_for = label.attribute('for').text
expect(id_in_label_for).to eq('facet_suggest_language_facet')

expect(rendered.css('input').first.attribute('id').text).to eq id_in_label_for
end

context 'when the facet is explicitly configured to suggest: false' do
let(:facet) { Blacklight::Configuration::FacetField.new key: 'language_facet', suggest: false }

it 'does not display' do
expect(render_inline(described_class.new(facet: facet, presenter: presenter)).to_s).to eq ''
end
end

context 'when the facet is not explicitly configured with a suggest key' do
let(:facet) { Blacklight::Configuration::FacetField.new key: 'language_facet' }

it 'does not display' do
expect(render_inline(described_class.new(facet: facet, presenter: presenter)).to_s).to eq ''
end
end
end
Loading