diff --git a/.env.test b/.env.test index 90ff698a..46e4faa8 100644 --- a/.env.test +++ b/.env.test @@ -16,3 +16,5 @@ BOOTSNAP_CACHE_DIR=./tmp # turn of functionality during FOLIO update FOLIO_UPDATE_IN_PROGRESS= + +CATALOGUE_SERVICES_API_BASE_URL=http://catservices.test diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb index 31b095d1..03bd52a5 100644 --- a/app/controllers/catalog_controller.rb +++ b/app/controllers/catalog_controller.rb @@ -302,6 +302,7 @@ class CatalogController < ApplicationController config.add_summary_field "extent", field: "extent_ssm" config.add_summary_field "language", field: "language_ssim" config.add_summary_field "prefercite", field: "prefercite_html_tesm", helper_method: :render_html_tags + config.add_summary_field "availability", field: "availability_status", accessor: :availability_status config.add_summary_field "cult_sens_adv_notice", label: I18n.t("ead_notes.cultural_sens_adv_notice"), helper_method: :render_html_tags, diff --git a/app/models/solr_document.rb b/app/models/solr_document.rb index 2570e04e..45ebbf6b 100644 --- a/app/models/solr_document.rb +++ b/app/models/solr_document.rb @@ -111,4 +111,12 @@ def wrap_in_paragraph(value) # rubocop:enable Rails/OutputSafety end end + + def availability_status + holdings, item = CatalogueServicesClient.new.get_item_ids(instance_id: self["folio_instance_id_ssi"]) + Rails.logger.debug "solrdoc" + Rails.logger.debug [holdings, item] + + CatalogueServicesClient.new.get_requestable(instance_id: self["folio_instance_id_ssi"], holdings_id: holdings, item_id: item) + end end diff --git a/app/services/catalogue_services_client.rb b/app/services/catalogue_services_client.rb new file mode 100644 index 00000000..59493718 --- /dev/null +++ b/app/services/catalogue_services_client.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +class ServiceTokenError < StandardError; end + +class HoldingsRequestError < StandardError; end + +class ItemRequestError < StandardError; end + +class RequestDetailsError < StandardError; end + +class UserDetailsError < StandardError; end + +class CatalogueServicesClient + MAX_TOKEN_RETRIES = 3 + + def get_holdings(instance_id:) + conn = setup_connection + + res = conn.get("/catalogue-services/folio/instance/#{instance_id}") + if res.status == 200 + if res.body.present? + res.body["holdingsRecords"] + end + else + Rails.logger.error "Failed to retrieve holdings for #{instance_id}" + raise HoldingsRequestError.new("Failed to retrieve holdings for #{instance_id}") + end + rescue => e + Rails.logger.error "get_holdings - Failed to connect catalogue-service: #{e.message}" + raise HoldingsRequestError.new("Failed to retrieve holdings for #{instance_id}") + end + + def get_holding(instance_id:, holdings_id:, item_id:) + all_holdings = get_holdings(instance_id: instance_id) + + # find holdings record + holding = all_holdings.find { |h| h["id"] == holdings_id } + + # find item record + item = holding["itemRecords"].find { |i| i["id"] == item_id } + + [holding, item] + end + + def get_requestable(instance_id:, holdings_id:, item_id:) + all_holdings = get_holdings(instance_id: instance_id) + + # find holdings record + holding = all_holdings.find { |h| h["id"] == holdings_id } + + # find item record + item = holding["itemRecords"].find { |i| i["id"] == item_id } + + item["requestable"] ? item["displayStatus"] : "Not for loan" + end + + def get_item_ids(instance_id:) + all_holdings = get_holdings(instance_id: instance_id) + + item_id = all_holdings.first["itemRecords"].first["holdingsRecordId"] if all_holdings.first["itemRecords"].any? + + holding_id = all_holdings.first["itemRecords"].first["id"] if all_holdings.first["itemRecords"].any? + Rails.logger.debug "catservices" + Rails.logger.debug [item_id, holding_id] + [item_id, holding_id] + end + + private + + def setup_connection + Faraday.new(url: ENV["CATALOGUE_SERVICES_API_BASE_URL"]) do |f| + f.response :json + end + end +end diff --git a/config/initializers/faraday.rb b/config/initializers/faraday.rb new file mode 100644 index 00000000..f207835a --- /dev/null +++ b/config/initializers/faraday.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "faraday" + +# Make sure Faraday requests include a non-default User-Agent, since it will break the specs +# every time Faraday is upgraded. +Faraday.default_connection_options = {headers: {user_agent: "nla-arclight/#{Rails.configuration.version}"}} diff --git a/spec/features/document_component_spec.rb b/spec/features/document_component_spec.rb index 595f73a4..ab0f7142 100644 --- a/spec/features/document_component_spec.rb +++ b/spec/features/document_component_spec.rb @@ -13,6 +13,11 @@ } ) .to_return(status: 200, body: solr_response, headers: {}) + + cat_response = IO.read("spec/files/catalogue_services/08aed703-3648-54d0-80ef-fddb3c635731.json") + + WebMock.stub_request(:get, /catalogue-services\/folio\/instance\/(.*)/) + .to_return(status: 200, body: cat_response, headers: {"Content-Type" => "application/json"}) end it "has correct tab title, including the collection prefix" do diff --git a/spec/files/catalogue_services/08aed703-3648-54d0-80ef-fddb3c635731.json b/spec/files/catalogue_services/08aed703-3648-54d0-80ef-fddb3c635731.json new file mode 100644 index 00000000..5162bc2a --- /dev/null +++ b/spec/files/catalogue_services/08aed703-3648-54d0-80ef-fddb3c635731.json @@ -0,0 +1,370 @@ +{ + "instanceId": "08aed703-3648-54d0-80ef-fddb3c635731", + "holdingsRecords": [ + { + "id": "d6c97d9e-dfe6-5faa-9f0b-020b2bddbf8c", + "instanceId": "08aed703-3648-54d0-80ef-fddb3c635731", + "permanentLocationId": "e041308f-c160-48b6-b6e7-cd5d0d582122", + "effectiveLocationId": "e041308f-c160-48b6-b6e7-cd5d0d582122", + "callNumber": "NL 919.4 NAT", + "holdingsStatements": [ + { + "statement": "", + "note": "v. 124, no. 3 (Sept. 1963)", + "staffNote": "" + } + ], + "holdingsStatementsForIndexes": [], + "holdingsStatementsForSupplements": [], + "discoverySuppress": false, + "itemRecords": [ + { + "id": "7460acfb-72b9-5dba-9089-603921fb47c7", + "holdingsRecordId": "d6c97d9e-dfe6-5faa-9f0b-020b2bddbf8c", + "barcode": "78000000165101", + "effectiveLocationId": "e041308f-c160-48b6-b6e7-cd5d0d582122", + "effectiveLocationDisplayName": "Main Reading Room", + "effectiveCallNumberComponents": { + "callNumber": "NL 919.4 NAT" + }, + "status": { + "name": "Available", + "date": "2023-06-28T04:08:47.034+00:00" + }, + "pickupLocation": { + "id": "325aea8b-f5f2-4160-88ca-63d803856a70", + "name": "Main Reading Room", + "code": "MRR-SP", + "discoveryDisplayName": "Main Reading Room", + "pickupLocation": true, + "holdShelfExpiryPeriod": { + "duration": 6, + "intervalId": "Days" + } + }, + "materialTypeId": "237cc266-f677-4b8e-aaae-846ed7e66361", + "permanentLoanTypeId": "ec500417-ea68-484f-858d-e8f6166e6237", + "yearCaption": [], + "enumeration": "NL pbk", + "itemCategory": "monograph", + "discoverySuppress": false, + "requestable": true, + "displayStatus": "Available" + } + ], + "notes": [], + "checkedOutItems": [] + }, + { + "id": "44e5936d-0bd9-5df5-a86a-5091d04bd84d", + "instanceId": "08aed703-3648-54d0-80ef-fddb3c635731", + "permanentLocationId": "14b466b6-86b3-499b-9ddb-262fa0ac56ce", + "effectiveLocationId": "14b466b6-86b3-499b-9ddb-262fa0ac56ce", + "callNumber": "N 919.4 NAT", + "holdingsStatements": [ + { + "statement": "", + "note": "N pbk", + "staffNote": "" + } + ], + "holdingsStatementsForIndexes": [], + "holdingsStatementsForSupplements": [], + "discoverySuppress": false, + "itemRecords": [ + { + "id": "52209e0d-00bd-5f19-91e8-55e1314ef637", + "holdingsRecordId": "44e5936d-0bd9-5df5-a86a-5091d04bd84d", + "barcode": "78000000175193", + "effectiveLocationId": "14b466b6-86b3-499b-9ddb-262fa0ac56ce", + "effectiveLocationDisplayName": "Main Reading Room", + "effectiveCallNumberComponents": { + "callNumber": "N 919.4 NAT" + }, + "status": { + "name": "Available", + "date": "2023-06-28T04:01:37.430+00:00" + }, + "pickupLocation": { + "id": "325aea8b-f5f2-4160-88ca-63d803856a70", + "name": "Main Reading Room", + "code": "MRR-SP", + "discoveryDisplayName": "Main Reading Room", + "pickupLocation": true, + "holdShelfExpiryPeriod": { + "duration": 6, + "intervalId": "Days" + } + }, + "materialTypeId": "237cc266-f677-4b8e-aaae-846ed7e66361", + "permanentLoanTypeId": "ec500417-ea68-484f-858d-e8f6166e6237", + "yearCaption": [], + "enumeration": "N pbk", + "itemCategory": "monograph", + "discoverySuppress": false, + "requestable": true, + "displayStatus": "Available" + } + ], + "notes": [], + "checkedOutItems": [] + }, + { + "id": "fc59f835-c340-5ea4-978d-4bfe4193cee0", + "instanceId": "08aed703-3648-54d0-80ef-fddb3c635731", + "permanentLocationId": "4ac9fcf1-8288-41b5-ac14-e15648f97158", + "effectiveLocationId": "4ac9fcf1-8288-41b5-ac14-e15648f97158", + "callNumber": "HSW 2071", + "holdingsStatements": [ + { + "statement": "", + "note": "Vol. 118, no. 2 (Aug. 1960); v. 118, no. 6 (Dec. 1960); v. 121, no. 1 (Jan. 1962); v. 126, no. 4 (Oct. 1964)-v. 126, no. 5 (Nov. 1964); v. 131, no. 2 (Feb. 1967); v. 132, no. 3 (Sept. 1967); v. 134, no. 6 (Dec. 1968)-v. 135, no. 1 (Jan. 1969); v. 135, no. 3 (March 1969)-v. 135, no. 4 (April 1969); v. 136, no. 3 (Sept. 1969); v. 137, no. 3 (March 1970); v. 138, no. 5 (Nov. 1970); v. 140, no. 1 (July 1971); v. 149, no. 6 (June 1976); v. 152, no. 6 (Dec. 1977)", + "staffNote": "" + } + ], + "holdingsStatementsForIndexes": [], + "holdingsStatementsForSupplements": [], + "discoverySuppress": false, + "itemRecords": [ + { + "id": "95d87c3b-ef17-5440-9dbb-befcc2b7670b", + "holdingsRecordId": "fc59f835-c340-5ea4-978d-4bfe4193cee0", + "barcode": "31508019674725", + "effectiveLocationId": "4ac9fcf1-8288-41b5-ac14-e15648f97158", + "effectiveLocationDisplayName": "Special Collections Reading Room", + "effectiveCallNumberComponents": { + "callNumber": "HSW 2071" + }, + "status": { + "name": "Available", + "date": "2023-06-28T03:59:00.959+00:00" + }, + "pickupLocation": { + "id": "d1624450-96b9-48ac-9457-47d7d4924c66", + "name": "Special Collections Reading Room", + "code": "SCRR-SP", + "discoveryDisplayName": "Special Collections Reading Room", + "pickupLocation": true, + "holdShelfExpiryPeriod": { + "duration": 6, + "intervalId": "Days" + } + }, + "materialTypeId": "237cc266-f677-4b8e-aaae-846ed7e66361", + "permanentLoanTypeId": "ec500417-ea68-484f-858d-e8f6166e6237", + "yearCaption": [], + "enumeration": "FOR REQUESTS, USE THE REQUEST OPTION BELOW", + "itemCategory": "journal", + "discoverySuppress": false, + "requestable": true, + "displayStatus": "Available" + } + ], + "notes": [ + { + "note": "(Aug.1960 - Dec.1977) impf", + "staffOnly": false, + "holdingsNoteTypeId": "b160f13a-ddba-4053-b9c4-60ec5ea45d56", + "holdingsNoteType": "Note" + } + ], + "checkedOutItems": [] + }, + { + "id": "37fbc2dd-3b37-58b8-b447-b538ba7265b9", + "instanceId": "08aed703-3648-54d0-80ef-fddb3c635731", + "permanentLocationId": "7b4822a2-9aa7-442d-8ed5-8da3cc8c01a2", + "effectiveLocationId": "7b4822a2-9aa7-442d-8ed5-8da3cc8c01a2", + "callNumber": "NKA 1132", + "holdingsStatements": [ + { + "statement": "", + "note": "Vol. 148, no. 5 (Nov. 1975)-vol. 148, no. 6 (Dec. 1975)", + "staffNote": "" + } + ], + "holdingsStatementsForIndexes": [], + "holdingsStatementsForSupplements": [], + "discoverySuppress": false, + "itemRecords": [ + { + "id": "60ae1cf9-5b4c-5fac-9a38-2cb195cdb7b2", + "holdingsRecordId": "37fbc2dd-3b37-58b8-b447-b538ba7265b9", + "barcode": "31508021167700", + "effectiveLocationId": "7b4822a2-9aa7-442d-8ed5-8da3cc8c01a2", + "effectiveLocationDisplayName": "Special Collections Reading Room", + "effectiveCallNumberComponents": { + "callNumber": "NKA 1132" + }, + "status": { + "name": "Available", + "date": "2023-06-28T03:57:31.846+00:00" + }, + "pickupLocation": { + "id": "d1624450-96b9-48ac-9457-47d7d4924c66", + "name": "Special Collections Reading Room", + "code": "SCRR-SP", + "discoveryDisplayName": "Special Collections Reading Room", + "pickupLocation": true, + "holdShelfExpiryPeriod": { + "duration": 6, + "intervalId": "Days" + } + }, + "materialTypeId": "237cc266-f677-4b8e-aaae-846ed7e66361", + "permanentLoanTypeId": "ec500417-ea68-484f-858d-e8f6166e6237", + "yearCaption": [], + "itemCategory": "monograph", + "discoverySuppress": false, + "requestable": true, + "displayStatus": "Available" + } + ], + "notes": [ + { + "note": "(Nov.1975 - Dec.1975)", + "staffOnly": false, + "holdingsNoteTypeId": "b160f13a-ddba-4053-b9c4-60ec5ea45d56", + "holdingsNoteType": "Note" + } + ], + "checkedOutItems": [] + }, + { + "id": "fe525746-5142-5b54-8c89-c6ed7a9c6196", + "instanceId": "08aed703-3648-54d0-80ef-fddb3c635731", + "permanentLocationId": "11c74e44-110d-4e53-8418-cf4bad1e314a", + "effectiveLocationId": "11c74e44-110d-4e53-8418-cf4bad1e314a", + "callNumber": "S 910.5 NAT", + "holdingsStatements": [ + { + "statement": "v. 116, no. 6 - v. 218, no. 1 (1959:Dec. - 2010:July)", + "note": "", + "staffNote": "" + }, + { + "statement": "v. 218, no. 4 - v. 222, no. 2 (2010:Oct. - 2012:Aug.)", + "note": "", + "staffNote": "" + }, + { + "statement": "v. 222, no. 4 - v. 244, no. 5 (2012:Oct. - 2023:Nov.)", + "note": "", + "staffNote": "" + } + ], + "holdingsStatementsForIndexes": [ + { + "statement": "", + "note": "Cumulative index", + "staffNote": "" + }, + { + "statement": "", + "note": "1888-1988", + "staffNote": "" + }, + { + "statement": "", + "note": "1989-1998", + "staffNote": "" + }, + { + "statement": "2001", + "note": "", + "staffNote": "" + }, + { + "statement": "2003", + "note": "", + "staffNote": "" + }, + { + "statement": "2008", + "note": "", + "staffNote": "" + } + ], + "holdingsStatementsForSupplements": [ + { + "statement": "", + "note": "Maps of the United States and the world and cartography at the National Geographic Society, 1888-1988", + "staffNote": "" + }, + { + "statement": "", + "note": "Energy (Feb 1981)", + "staffNote": "" + }, + { + "statement": "", + "note": "Traveler's map of Germany (Sept 1991)", + "staffNote": "" + }, + { + "statement": "", + "note": "Native American heritage: a visitors guide (Oct 1991)", + "staffNote": "" + }, + { + "statement": "", + "note": "Water (v.184 ,no. 5A, 1993)", + "staffNote": "" + }, + { + "statement": "", + "note": "Millennium Supplement: Population (Oct 1998)", + "staffNote": "" + }, + { + "statement": "", + "note": "Supplement: Millennium in maps: cultures (Aug 1999)", + "staffNote": "" + }, + { + "statement": "", + "note": "Special millennium issue (Jan 2000)", + "staffNote": "" + } + ], + "discoverySuppress": false, + "itemRecords": [ + { + "id": "0f17532a-2fcf-5c72-a0c9-751fc459481f", + "holdingsRecordId": "fe525746-5142-5b54-8c89-c6ed7a9c6196", + "barcode": "31508002260425", + "effectiveLocationId": "11c74e44-110d-4e53-8418-cf4bad1e314a", + "effectiveLocationDisplayName": "Main Reading Room - Most pre 2005 held offsite", + "effectiveCallNumberComponents": { + "callNumber": "S 910.5 NAT" + }, + "status": { + "name": "Available", + "date": "2023-06-28T03:04:28.167+00:00" + }, + "pickupLocation": { + "id": "325aea8b-f5f2-4160-88ca-63d803856a70", + "name": "Main Reading Room", + "code": "MRR-SP", + "discoveryDisplayName": "Main Reading Room", + "pickupLocation": true, + "holdShelfExpiryPeriod": { + "duration": 6, + "intervalId": "Days" + } + }, + "materialTypeId": "237cc266-f677-4b8e-aaae-846ed7e66361", + "permanentLoanTypeId": "ec500417-ea68-484f-858d-e8f6166e6237", + "yearCaption": [], + "enumeration": "FOR REQUESTS, USE THE REQUEST OPTION BELOW", + "itemCategory": "journal", + "discoverySuppress": false, + "requestable": true, + "displayStatus": "Available" + } + ], + "notes": [], + "checkedOutItems": [] + } + ] +} \ No newline at end of file diff --git a/spec/services/catalogue_services_client_spec.rb b/spec/services/catalogue_services_client_spec.rb new file mode 100644 index 00000000..a205a7a3 --- /dev/null +++ b/spec/services/catalogue_services_client_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe CatalogueServicesClient do + subject(:service) { described_class.new } + + describe "#get_holdings" do + before do + cat_response = IO.read("spec/files/catalogue_services/08aed703-3648-54d0-80ef-fddb3c635731.json") + WebMock.stub_request(:get, /catservices.test\/catalogue-services\/folio\/instance\/08aed703-3648-54d0-80ef-fddb3c635731/) + .to_return(status: 200, body: cat_response, headers: {"Content-Type" => "application/json"}) + end + + it "returns holdings records" do + expect(service.get_holdings(instance_id: "08aed703-3648-54d0-80ef-fddb3c635731").size).to eq 5 + end + + context "when unable to request holdings" do + it "raises a HoldingsRequestError" do + WebMock.stub_request(:get, /catservices.test\/catalogue-services\/folio\/instance\/08aed703-3648-54d0-80ef-fddb3c635731/) + .to_return(status: 401, body: "", headers: {"Content-Type" => "application/json"}) + + expect { service.get_holdings(instance_id: "08aed703-3648-54d0-80ef-fddb3c635731") }.to raise_error(HoldingsRequestError) + end + end + end + + describe "#get_requestable" do + before do + cat_response = IO.read("spec/files/catalogue_services/08aed703-3648-54d0-80ef-fddb3c635731.json") + WebMock.stub_request(:get, /catservices.test\/catalogue-services\/folio\/instance\/08aed703-3648-54d0-80ef-fddb3c635731/) + .to_return(status: 200, body: cat_response, headers: {"Content-Type" => "application/json"}) + end + + it "returns the record's item id and holding id" do + expect(service.get_item_ids(instance_id: "08aed703-3648-54d0-80ef-fddb3c635731")).to eq %w[d6c97d9e-dfe6-5faa-9f0b-020b2bddbf8c 7460acfb-72b9-5dba-9089-603921fb47c7] + end + + it "returns the record's requestable status" do + expect(service.get_requestable(instance_id: "08aed703-3648-54d0-80ef-fddb3c635731", holdings_id: "d6c97d9e-dfe6-5faa-9f0b-020b2bddbf8c", item_id: "7460acfb-72b9-5dba-9089-603921fb47c7")).to eq "Available" + end + end +end