diff --git a/app/components/blacklight/facet_field_pagination_component.html.erb b/app/components/blacklight/facet_field_pagination_component.html.erb new file mode 100644 index 000000000..ba97dbb22 --- /dev/null +++ b/app/components/blacklight/facet_field_pagination_component.html.erb @@ -0,0 +1,61 @@ + + + +
+ <% if @facet_field.paginator.sort == 'index' -%> + <%= t('blacklight.search.facets.sort.index') %> + <%= helpers.link_to(t('blacklight.search.facets.sort.count'), sort_facet_url('count'), class: "sort_change numeric btn btn-outline-secondary", data: { blacklight_modal: "preserve" }) %> + <% elsif @facet_field.paginator.sort == 'count' -%> + <%= helpers.link_to(t('blacklight.search.facets.sort.index'), sort_facet_url('index'), class: "sort_change az btn btn-outline-secondary", data: { blacklight_modal: "preserve" }) %> + <%= t('blacklight.search.facets.sort.count') %> + <% end -%> +
\ No newline at end of file diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb index 36c8b9d03..b2882d0a3 100755 --- a/app/controllers/catalog_controller.rb +++ b/app/controllers/catalog_controller.rb @@ -37,6 +37,46 @@ def single_item_search_builder(id) single_item_search_builder_class.new(self, id).with(params.except(:q, :page)) end + def facet_total_count(field_name) + query = '*:*' + params = { + facet: true, + 'facet.field': field_name, + rows: 0, + 'facet.limit': -1, # Retrieve all facets + wt: 'json' + } + + # Perform the Solr query using Hyrax::SolrService + # Using spread operator to convert object into keyword arguments + response = Hyrax::SolrService.get(query, **params) + + # Parse the response to extract the total unique facets + facet_values = response.dig('facet_counts', 'facet_fields', field_name) + # Facet counts are included with names in the list, so divide by 2 + total_unique_facets = facet_values ? (facet_values.length / 2) : 0 + total_unique_facets + rescue StandardError => e + Rails.logger.error("Error retrieving facets for '#{field_name}': #{e.message}") + Rails.logger.error(e.backtrace.join("\n")) + 0 + end + + def facet + begin + facet_field_name = params[:id] + targeted_facet_list = ['affiliation_label_sim'] + super + # Only calculate total unique facets for facets in the target list + @total_unique_facets = targeted_facet_list.include?(facet_field_name) ? facet_total_count(facet_field_name) : 0 + rescue StandardError => e + # Capture any errors that occur and log them + Rails.logger.error("Error during facet action: #{e.message}") + Rails.logger.error(e.backtrace.join("\n")) + end + end + + configure_blacklight do |config| # Advanced search configuration config.advanced_search ||= Blacklight::OpenStructWithHashAccess.new diff --git a/app/overrides/components/blacklight/facet_field_pagination_component_override.rb b/app/overrides/components/blacklight/facet_field_pagination_component_override.rb new file mode 100644 index 000000000..e9ee1132b --- /dev/null +++ b/app/overrides/components/blacklight/facet_field_pagination_component_override.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +# [hyc-override] https://github.com/projectblacklight/blacklight/blob/v7.33.1/app/components/blacklight/facet_field_pagination_component.rb +Blacklight::FacetFieldPaginationComponent.class_eval do + attr_reader :facet_field, :total_unique_facets + def initialize(facet_field:, total_unique_facets: nil) + @facet_field = facet_field + # Integrate total unique facets as an attribute for pagination + @total_unique_facets = total_unique_facets + end +end diff --git a/app/overrides/lib/kanamari/helpers/helper_methods_override.rb b/app/overrides/lib/kanamari/helpers/helper_methods_override.rb new file mode 100644 index 000000000..0dea36a15 --- /dev/null +++ b/app/overrides/lib/kanamari/helpers/helper_methods_override.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true +# [hyc-override] https://github.com/kaminari/kaminari/blob/v1.2.2/kaminari-core/lib/kaminari/helpers/helper_methods.rb +Kaminari::Helpers::HelperMethods.module_eval do + # Helper to generate a link to a specific page + def link_to_specific_page(scope, page, total_entries, **options) + specific_page_path = path_to_specific_page(scope, page.to_i, total_entries, options) + + # Remove unnecessary keys :params and :param_name from the options hash before generating the link + options.except! :params, :param_name + + # Setting aria instead of rel for accessibility + options[:aria] ||= { label: "Go to page #{page.to_i}" } + + if specific_page_path + link_to("#{page.to_i}", specific_page_path, options) + else + Rails.logger.warn "Specific page path could not be generated for page: #{page.to_i}" + nil + end + end + + # Helper to generate the path for a specific page + def path_to_specific_page(scope, page_integer, total_entries, options = {}) + begin + # Calculate total pages manually + limit = scope.instance_variable_get(:@limit) + total_pages = (total_entries.to_f / limit).ceil + + Rails.logger.debug "path_to_specific_page: total_entries=#{total_entries}, limit=#{limit}, calculated total_pages=#{total_pages}, page=#{page_integer}" + + # Validate inputs + raise ArgumentError, 'Page number must be a positive integer' unless page_integer.positive? + raise ArgumentError, "Page number exceeds total pages (#{total_pages})" if page_integer > total_pages + # Generate URL using Kaminari's Page helper + Kaminari::Helpers::Page.new(self, **options.reverse_merge(page: page_integer)).url + rescue ArgumentError => e + Rails.logger.error "Error in path_to_specific_page: #{e.message}" + nil + rescue => e + Rails.logger.error "Unexpected error in path_to_specific_page: #{e.message}\n#{e.backtrace.join("\n")}" + nil + end + end +end diff --git a/app/views/catalog/_facet_pagination.html.erb b/app/views/catalog/_facet_pagination.html.erb new file mode 100644 index 000000000..516ecc64a --- /dev/null +++ b/app/views/catalog/_facet_pagination.html.erb @@ -0,0 +1 @@ +<%= render(Blacklight::FacetFieldPaginationComponent.new(facet_field: facet_field_presenter(@facet, @display_facet), total_unique_facets: @total_unique_facets)) %> \ No newline at end of file diff --git a/spec/components/blacklight/facet_field_pagination_component_spec.rb b/spec/components/blacklight/facet_field_pagination_component_spec.rb new file mode 100644 index 000000000..28c6e1fd4 --- /dev/null +++ b/spec/components/blacklight/facet_field_pagination_component_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +require 'rails_helper' + +RSpec.describe Blacklight::FacetFieldPaginationComponent, type: :component do + let(:facet_field) { double('facet_field') } + let(:total_unique_facets) { 42 } + + describe '#initialize' do + context 'when total_unique_facets is provided' do + it 'sets the facet_field and total_unique_facets attributes' do + component = Blacklight::FacetFieldPaginationComponent.new(facet_field: facet_field, total_unique_facets: total_unique_facets) + + expect(component.facet_field).to eq(facet_field) + expect(component.total_unique_facets).to eq(total_unique_facets) + end + end + + context 'when total_unique_facets is not provided' do + it 'sets the total_unique_facets attribute to nil' do + component = Blacklight::FacetFieldPaginationComponent.new(facet_field: facet_field) + + expect(component.facet_field).to eq(facet_field) + expect(component.total_unique_facets).to be_nil + end + end + end +end diff --git a/spec/lib/kanamari/helpers/helper_methods_spec.rb b/spec/lib/kanamari/helpers/helper_methods_spec.rb new file mode 100644 index 000000000..85c675e9d --- /dev/null +++ b/spec/lib/kanamari/helpers/helper_methods_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true +require 'rails_helper' + +RSpec.describe Kaminari::Helpers::HelperMethods do + let(:dummy_class) do + Class.new do + extend ActionView::Helpers::UrlHelper + extend Kaminari::Helpers::HelperMethods + end + end + let(:scope) { double('FacetPaginator', instance_variable_get: 20) } + let(:valid_page) { 1 } + let(:total_entries) { 100 } + let(:options) { { some_option: 'value' } } + + describe '#link_to_specific_page' do + before do + # Stub Kaminari::Helpers::Page.new to return a mock URL + allow(Kaminari::Helpers::Page).to receive(:new).and_return(double(url: '/some_path')) + end + + it 'generates a valid link for correct input' do + expect(Rails.logger).to_not receive(:error) + allow(dummy_class).to receive(:link_to).and_return('link') + # Mock link_to to check its arguments + dummy_class.link_to_specific_page(scope, valid_page, total_entries, **options) + # Expect the method to return the mocked URL + end + + + it 'logs and returns nil for invalid page input' do + invalid_page = -1 + expect(Rails.logger).to receive(:error).with(/Page number must be a positive integer/) + expect(Rails.logger).to receive(:warn).with(/Specific page path could not be generated for page/) + expect(dummy_class.link_to_specific_page(scope, invalid_page, total_entries, **options)) + .to be_nil + end + + it 'logs and returns nil if page exceeds total pages' do + invalid_page = 999 + expect(Rails.logger).to receive(:error).with(/Page number exceeds total pages/) + expect(Rails.logger).to receive(:warn).with(/Specific page path could not be generated for page/) + expect(dummy_class.link_to_specific_page(scope, invalid_page, total_entries, **options)) + .to be_nil + end + + it 'logs and returns nil if an unexpected error occurs' do + allow(Kaminari::Helpers::Page).to receive(:new).and_raise(StandardError, 'Simulated Kaminari Error') + expect(Rails.logger).to receive(:error).with(/Unexpected error in path_to_specific_page/) + expect(Rails.logger).to receive(:warn).with(/Specific page path could not be generated for page/) + expect(dummy_class.link_to_specific_page(scope, valid_page, total_entries, **options)) + .to be_nil + end + end +end