app/controllers/catalog_controller.rb
# frozen_string_literal: true
class CatalogController < ApplicationController
include Blacklight::Catalog
helper ArgoHelper
include DateFacetConfigurations
before_action :limit_facets_on_home_page, only: [:index]
before_action :adjust_lazy_limits, only: [:index]
# The subset of facets that are displayed on the home page.
# (Before a user clicks "Show more facets")
HOME_FACETS = [
'exploded_project_tag_ssim',
'exploded_nonproject_tag_ssim',
'objectType_ssim',
SolrDocument::FIELD_CONTENT_TYPE,
SolrDocument::FIELD_COLLECTION_TITLE,
'nonhydrus_apo_title_ssim',
'released_to_earthworks',
'released_to_searchworks',
'wf_wps_ssim',
'identifier_tesim'
].map(&:to_s).freeze
# Facets that are configured for lazy loading.
LAZY_FACETS = %w[
exploded_project_tag_ssim
exploded_nonproject_tag_ssim
wf_wps_ssim
].map(&:to_s).freeze
# NOTE: any Solr parameters configured here will override parameters in the Solr configuration files.
configure_blacklight do |config|
## Class for converting Blacklight's url parameters to into request parameters for the search index
config.search_builder_class = ::SearchBuilder
# this helps get around issues with a large URL being sent to solr over GET, e.g. see https://github.com/sul-dlss/argo/issues/321 and
# https://github.com/projectblacklight/blacklight/issues/1324 and more recent issues in Aug 2021 with large solr queries being sent over GET
config.http_method = :post
# Configure the SearchState to know that "druids_only", is part of the state we care about.
config.search_state_fields << :druids_only
# common helper method since search results and reports share most of this config
BlacklightConfigHelper.add_common_default_solr_params_to_config! config
config.default_solr_params[:rows] = 10
config.document_solr_request_handler = '/document'
# When we test with solr 6 we can have:
# config.document_solr_path = 'get'
config.index.document_presenter_class = ArgoIndexPresenter
config.show.document_presenter_class = ArgoShowPresenter
config.index.display_type_field = SolrDocument::FIELD_CONTENT_TYPE
config.show.display_type_field = 'objectType_ssim'
config.show.html_title_field = SolrDocument::FIELD_TITLE
config.index.thumbnail_method = :render_thumbnail_helper
config.add_index_field 'id', label: 'DRUID'
config.add_index_field SolrDocument::FIELD_OBJECT_TYPE, label: 'Object Type'
config.add_index_field SolrDocument::FIELD_CONTENT_TYPE, label: 'Content Type'
config.add_index_field SolrDocument::FIELD_APO_ID, label: 'Admin Policy', helper_method: :link_to_admin_policy
config.add_index_field SolrDocument::FIELD_COLLECTION_ID, label: 'Collection', helper_method: :links_to_collections
config.add_index_field SolrDocument::FIELD_PROJECT_TAG, label: 'Project', link_to_facet: true
config.add_index_field SolrDocument::FIELD_SOURCE_ID, label: 'Source'
config.add_index_field 'identifier_tesim', label: 'IDs', helper_method: :value_for_identifier_tesim
config.add_index_field SolrDocument::FIELD_RELEASED_TO, label: 'Released to'
config.add_index_field 'status_ssi', label: 'Status'
config.add_index_field SolrDocument::FIELD_WORKFLOW_ERRORS, label: 'Error', helper_method: :value_for_wf_error
config.add_index_field 'rights_descriptions_ssim', label: 'Access Rights'
config.add_show_field 'project_tag_ssim', label: 'Project', link_to_facet: true
config.add_show_field 'tag_ssim', label: 'Tags', link_to_facet: true
config.add_show_field SolrDocument::FIELD_WORKFLOW_ERRORS, label: 'Error', helper_method: :value_for_wf_error
# exploded_project_tag_ssim indexes all project tag prefixes for hierarchical facet display, whereas
# project tag_ssim only indexes whole tags
config.add_facet_field 'exploded_project_tag_ssim', label: 'Project', limit: 100_000,
component: LazyProjectTagFacetComponent,
unless: ->(controller, _config, _response) { controller.params[:no_tags] }
# exploded_nonproject_tag_ssim indexes all tag prefixes, except project tags, for hierarchical facet display,
# whereas tag_ssim only indexes whole tags.
config.add_facet_field 'exploded_nonproject_tag_ssim', label: 'Tag', limit: 100_000,
component: LazyNonprojectTagFacetComponent,
unless: ->(controller, _config, _response) { controller.params[:no_tags] }
config.add_facet_field 'objectType_ssim', label: 'Object Type', component: true, limit: 10
config.add_facet_field SolrDocument::FIELD_CONTENT_TYPE, label: 'Content Type', component: true, limit: 10
config.add_facet_field 'content_file_mimetypes_ssim', label: 'MIME Types', component: true, limit: 10
config.add_facet_field 'content_file_roles_ssim', label: 'File Role', component: true, limit: 10
config.add_facet_field 'rights_descriptions_ssim', label: 'Access Rights', component: true, limit: 1000,
sort: 'index'
config.add_facet_field SolrDocument::FIELD_LICENSE, label: 'License', component: true, limit: 10
config.add_facet_field SolrDocument::FIELD_COLLECTION_TITLE, label: 'Collection', component: true, limit: 10,
more_limit: 9999, sort: 'index'
config.add_facet_field 'nonhydrus_apo_title_ssim', label: 'Admin Policy', component: true, limit: 10,
more_limit: 9999, sort: 'index'
config.add_facet_field SolrDocument::FIELD_CURRENT_VERSION, label: 'Version', component: true, limit: 10
config.add_facet_field 'processing_status_text_ssi', label: 'Processing Status', component: true, limit: 10
config.add_facet_field 'released_to_earthworks',
component: true,
query: {
week: {
label: 'Last week',
fq: "#{SolrDocument::FIELD_RELEASED_TO_EARTHWORKS}:[NOW-7DAY/DAY TO NOW]"
},
month: {
label: 'Last month',
fq: "#{SolrDocument::FIELD_RELEASED_TO_EARTHWORKS}:[NOW-1MONTH/DAY TO NOW]"
},
year: {
label: 'Last year',
fq: "#{SolrDocument::FIELD_RELEASED_TO_EARTHWORKS}:[NOW-1YEAR/DAY TO NOW]"
},
ever: {
label: 'Currently released',
fq: "#{SolrDocument::FIELD_RELEASED_TO_EARTHWORKS}:[* TO *]"
},
never: {
label: 'Not released',
fq: "-#{SolrDocument::FIELD_RELEASED_TO_EARTHWORKS}:[* TO *]"
}
}
config.add_facet_field 'released_to_purl_sitemap',
component: true,
query: {
week: {
label: 'Last week',
fq: "#{SolrDocument::FIELD_RELEASED_TO_PURL_SITEMAP}:[NOW-7DAY/DAY TO NOW]"
},
month: {
label: 'Last month',
fq: "#{SolrDocument::FIELD_RELEASED_TO_PURL_SITEMAP}:[NOW-1MONTH/DAY TO NOW]"
},
year: {
label: 'Last year',
fq: "#{SolrDocument::FIELD_RELEASED_TO_PURL_SITEMAP}:[NOW-1YEAR/DAY TO NOW]"
},
ever: {
label: 'Currently released',
fq: "#{SolrDocument::FIELD_RELEASED_TO_PURL_SITEMAP}:[* TO *]"
},
never: {
label: 'Not released',
fq: "-#{SolrDocument::FIELD_RELEASED_TO_PURL_SITEMAP}:[* TO *]"
}
}
config.add_facet_field 'released_to_searchworks',
component: true,
query: {
week: {
label: 'Last week',
fq: "#{SolrDocument::FIELD_RELEASED_TO_SEARCHWORKS}:[NOW-7DAY/DAY TO NOW]"
},
month: {
label: 'Last month',
fq: "#{SolrDocument::FIELD_RELEASED_TO_SEARCHWORKS}:[NOW-1MONTH/DAY TO NOW]"
},
year: {
label: 'Last year',
fq: "#{SolrDocument::FIELD_RELEASED_TO_SEARCHWORKS}:[NOW-1YEAR/DAY TO NOW]"
},
ever: {
label: 'Currently released',
fq: "#{SolrDocument::FIELD_RELEASED_TO_SEARCHWORKS}:[* TO *]"
},
never: {
label: 'Not released',
fq: "-#{SolrDocument::FIELD_RELEASED_TO_SEARCHWORKS}:[* TO *]"
}
}
config.add_facet_field 'wf_wps_ssim', label: 'Workflows (WPS)', limit: 9999,
component: LazyWpsWorkflowFacetComponent
config.add_facet_field 'wf_wsp_ssim', label: 'Workflows (WSP)',
component: Blacklight::Hierarchy::FacetFieldListComponent,
limit: 9999
config.add_facet_field 'wf_swp_ssim', label: 'Workflows (SWP)',
component: Blacklight::Hierarchy::FacetFieldListComponent,
limit: 9999
config.add_facet_field 'metadata_source_ssim', label: 'Metadata Source', component: true
# common method since search results and reports all do the same configuration
add_common_date_facet_fields_to_config! config
config.add_facet_field SolrDocument::FIELD_CONSTITUENTS, label: 'Virtual Objects', component: true,
query: {
has_constituents: { label: 'Virtual Objects', fq: "#{SolrDocument::FIELD_CONSTITUENTS}:*" }
}
# This will help us find records that need to be fixed before we can move to cocina.
config.add_facet_field 'data_quality_ssim', label: 'Data Quality', component: true
config.add_facet_field 'identifiers', label: 'Identifiers',
component: true,
query: {
has_orcids: { label: 'Has contributor ORCIDs',
fq: '+contributor_orcids_ssim:*' },
has_doi: { label: 'Has DOI', fq: '+doi_ssim:*' },
has_barcode: { label: 'Has barcode', fq: '+barcode_id_ssim:*' }
}
config.add_facet_field 'empties', label: 'Empty Fields', component: true,
query: {
no_mods_typeOfResource_ssim: { label: 'No MODS typeOfResource',
fq: '-mods_typeOfResource_ssim:*' },
no_sw_format: { label: 'No SW Resource Type', fq: '-sw_format_ssim:*' }
}
config.add_facet_field 'sw_format_ssim', label: 'SW Resource Type', component: true, limit: 10
config.add_facet_field 'sw_pub_date_facet_ssi', label: 'SW Date', component: true, limit: 10
config.add_facet_field 'topic_ssim', label: 'SW Topic', component: true, limit: 10
config.add_facet_field 'sw_subject_geographic_ssim', label: 'SW Region', component: true, limit: 10
config.add_facet_field 'sw_subject_temporal_ssim', label: 'SW Era', component: true, limit: 10
config.add_facet_field 'sw_genre_ssim', label: 'SW Genre', component: true, limit: 10
config.add_facet_field 'sw_language_ssim', label: 'SW Language', component: true, limit: 10
config.add_facet_field 'mods_typeOfResource_ssim', label: 'MODS Resource Type', component: true, limit: 10
# Adding the facet field allows it to be queried (e.g., from value_helper)
config.add_facet_field 'is_governed_by_ssim', if: false
config.add_facet_field 'is_member_of_collection_ssim', if: false
config.add_facet_field 'tag_ssim', if: false
config.add_facet_field 'project_tag_ssim', if: false
config.add_facet_fields_to_solr_request! # deprecated in newer Blacklights
config.add_search_field 'text', label: 'All Fields'
config.add_sort_field 'score desc', label: 'Relevance', default: true
config.add_sort_field 'id asc', label: 'Druid'
config.spell_max = 5
config.facet_display = {
hierarchy: {
'wf_wps' => [['ssim'], ':'],
'wf_wsp' => [['ssim'], ':'],
'wf_swp' => [['ssim'], ':'],
'exploded_nonproject_tag' => [['ssim'], ':'],
'exploded_project_tag' => [['ssim'], ':']
}
}
config.add_results_collection_tool(:bulk_action_button)
config.add_results_collection_tool(:sort_widget)
config.add_results_collection_tool(:per_page_widget)
# config.add_results_collection_tool(:view_type_group)
config.add_results_collection_tool(:report_view_toggle)
##
# Configure document actions framework
config.index.document_actions.delete(:bookmark)
config.show.document_component = DocumentComponent
end
def index
@presenter = HomeTextPresenter.new(current_user)
# For comparison of search results with different Solr params:
# if qt param is passed to Argo, we can also change the params we send to Solr.
# This allows us to compare, e.g. search results with different Solr qf params.
# Request handlers can be configured:
# 1. in Solr, in which case you can remove default_solr_params to use only
# what is in the Solr configuration.
# 2. in Argo/Blacklight BUT the request handler (qt param) must match an existing request handler in Solr.
# 3. configured with url params, e.g. qt=wingnut BUT - if default_solr_params are in play,
# you cannot override those params with url params, and the qt param value has to match an
# existing request handler in Solr.
# 4. a combination of the above - some params can be configured in Solr, some in Argo/Blacklight, and some
# in the url.
if params.key?(:qt)
blacklight_config.default_solr_params = {
qt: params[:qt],
defType: 'dismax',
'q.alt': '*:*',
qf: %(
main_title_text_anchored_im^100
main_title_text_unstemmed_im^50
main_title_tenim^10
full_title_unstemmed_im^10
full_title_tenim^5
additional_titles_unstemmed_im^5
additional_titles_tenim^3
author_text_nostem_im^3
contributor_text_nostem_im
topic_tesim^2
tag_text_unstemmed_im
originInfo_place_placeTerm_tesim
originInfo_publisher_tesim
content_type_ssim
sw_format_ssim
object_type_ssim
descriptive_text_nostem_i
descriptive_tiv
descriptive_teiv
collection_title_tesim
id
druid_bare_ssi
druid_prefixed_ssi
obj_label_tesim
identifier_ssim
identifier_tesim
barcode_id_ssim
folio_instance_hrid_ssim
source_id_text_nostem_i^3
source_id_ssi
previous_ils_ids_ssim
doi_ssim
contributor_orcids_ssim
)
}
# NOTE: if you want to use the qf in solrconfig.xml, you can delete the qf param here:
# blacklight_config.default_solr_params.delete(:qf) # use what is in solrconfig.xml for the request handler
end
super
end
def lazy_nonproject_tag_facet
limit_facets_to(['exploded_nonproject_tag_ssim'])
(response,) = search_service.search_results
facet_config = facet_configuration_for_field('exploded_nonproject_tag_ssim')
display_facet = response.aggregations[facet_config.field]
@facet_field_presenter = facet_config.presenter.new(facet_config, display_facet, view_context)
render partial: 'lazy_nonproject_tag_facet'
end
def lazy_project_tag_facet
limit_facets_to(['exploded_project_tag_ssim'])
(response,) = search_service.search_results
facet_config = facet_configuration_for_field('exploded_project_tag_ssim')
display_facet = response.aggregations[facet_config.field]
@facet_field_presenter = facet_config.presenter.new(facet_config, display_facet, view_context)
render partial: 'lazy_project_tag_facet'
end
def lazy_wps_workflow_facet
limit_facets_to(['wf_wps_ssim'])
(response,) = search_service.search_results
facet_config = facet_configuration_for_field('wf_wps_ssim')
display_facet = response.aggregations[facet_config.field]
@facet_field_presenter = facet_config.presenter.new(facet_config, display_facet, view_context)
render partial: 'lazy_wps_workflow_facet'
end
def show
if user_version_param
@cocina = Repository.find_user_version(druid_param, user_version_param)
@document = SolrDocument.new(object_client.user_version.solr(user_version_param))
elsif version_param
@cocina = Repository.find_version(druid_param, version_param)
@document = SolrDocument.new(object_client.version.solr(version_param))
else
_deprecated_response, @document = search_service.fetch(druid_param)
@cocina = Repository.find_lite(druid_param, structural: false)
end
authorize! :read, @cocina
@workflows = WorkflowService.workflows_for(druid: druid_param)
@user_versions_presenter = UserVersionsPresenter.new(user_version_view: user_version_param, user_version_inventory: object_client.user_version.inventory)
@versions_presenter = VersionsPresenter.new(version_view: version_param, version_inventory:)
@milestones_presenter = MilestonesPresenter.new(druid: druid_param, version_inventory:)
@head_user_version = @user_versions_presenter.head_user_version
@release_tags = @cocina.admin_policy? ? [] : object_client.release_tags.list
# If you have this token, it indicates you have read access to the object
@verified_token_with_expiration = generate_token
respond_to do |format|
format.html { @search_context = setup_next_and_previous_documents }
format.json { render json: { response: { document: @document } } }
additional_export_formats(@document, format)
end
end
private
def limit_facets_on_home_page
return if has_search_parameters? || params[:all]
limit_facets_to(HOME_FACETS)
end
# Removes facets that won't be displayed for faster querying.
def limit_facets_to(fields)
blacklight_config.facet_fields.each do |field, params|
params.include_in_request = false unless fields.include?(field)
end
end
# Lowers the limits for facets that are configured for lazy loading for faster querying.
def adjust_lazy_limits
blacklight_config.facet_fields.each do |field, params|
params.limit = 1 if LAZY_FACETS.include?(field)
end
end
# do not add the druids_only search param to the blacklight search history (used in bulk actions only)
def blacklisted_search_session_params
super << :druids_only
end
# This overrides Blacklight to pass context to the search service
# @return [Hash] a hash of context information to pass through to the search service
def search_service_context
{ current_user: }
end
def generate_token
Argo.verifier.generate(
{ druid: druid_param, user_version_id: user_version_param, version_id: version_param },
expires_in: 1.hour,
purpose: :view_token
)
end
def object_client
@object_client ||= Dor::Services::Client.object(druid_param)
end
def version_inventory
@version_inventory ||= object_client.version.inventory
end
def user_version_param
@user_version_param ||= params[:user_version_id]
end
def version_param
@version_param ||= params[:version_id]
end
def druid_param
@druid_param ||= Druid.new(params[:item_id] || params[:id]).with_namespace
end
end