diff --git a/app/controllers/administrative_tags_controller.rb b/app/controllers/administrative_tags_controller.rb index 2a3582841..1ff9db1cc 100644 --- a/app/controllers/administrative_tags_controller.rb +++ b/app/controllers/administrative_tags_controller.rb @@ -31,8 +31,7 @@ def create }) render status: :conflict, plain: e.message else - # Broadcast this update action to a topic so that it can be indexed - Notifications::ObjectUpdated.publish(model: @cocina_object) + Indexer.reindex_later(cocina_object: @cocina_object) head :created end @@ -51,8 +50,7 @@ def update }) render status: :conflict, plain: e.message else - # Broadcast this update action to a topic so that it can be indexed - Notifications::ObjectUpdated.publish(model: @cocina_object) + Indexer.reindex_later(cocina_object: @cocina_object) head :no_content end @@ -61,7 +59,6 @@ def destroy rescue ActiveRecord::RecordNotFound => e render status: :not_found, plain: e.message else - # Broadcast this update action to a topic so that it can be indexed - Notifications::ObjectUpdated.publish(model: @cocina_object) + Indexer.reindex_later(cocina_object: @cocina_object) end end diff --git a/app/jobs/publish_items_modified_job.rb b/app/jobs/publish_items_modified_job.rb index 2b708d600..158b3276b 100644 --- a/app/jobs/publish_items_modified_job.rb +++ b/app/jobs/publish_items_modified_job.rb @@ -8,7 +8,7 @@ class PublishItemsModifiedJob < ApplicationJob def perform(collection_identifier) MemberService.for(collection_identifier).each do |druid| cocina_object_with_metadata = CocinaObjectStore.find(druid) - Notifications::ObjectUpdated.publish(model: cocina_object_with_metadata) + Indexer.reindex_later(cocina_object: cocina_object_with_metadata) end end end diff --git a/app/jobs/reindex_job.rb b/app/jobs/reindex_job.rb new file mode 100644 index 000000000..69e406d43 --- /dev/null +++ b/app/jobs/reindex_job.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# Reindexes an object. +class ReindexJob < ApplicationJob + queue_as :default + + # @param [Hash] model the cocina object attributes (without metadata) + # @param [DateTime] created the time the object was created + # @param [DateTime] modified the time the object was last modified + def perform(model:, created:, modified:) + cocina_object = Cocina::Models.build(model) + cocina_object_with_metadata = Cocina::Models.with_metadata(cocina_object, 'void', created:, modified:) + Indexer.reindex(cocina_object: cocina_object_with_metadata) + rescue DorIndexing::RepositoryError => e + Rails.logger.error("Error reindexing #{model[:externalIdentifier]}: #{e.message}") + Honeybadger.notify(e, context: { druid: model[:externalIdentifier] }) + end +end diff --git a/app/services/indexer.rb b/app/services/indexer.rb index 3a980574d..88a1231db 100644 --- a/app/services/indexer.rb +++ b/app/services/indexer.rb @@ -21,6 +21,15 @@ def self.delete(druid:) solr.commit end + # @param [Cocina::Models::DROWithMetadata|CollectionWithMetadata|AdminPolicyWithMetadata] + def self.reindex_later(cocina_object:) + ReindexJob.perform_later( + model: cocina_object.to_h, + created: cocina_object.created, + modified: cocina_object.modified + ) + end + # Repository implementations backed by ActiveRecord def self.administrative_tags_finder lambda do |druid| diff --git a/app/services/notifications/object_updated.rb b/app/services/notifications/object_updated.rb deleted file mode 100644 index f90f61cea..000000000 --- a/app/services/notifications/object_updated.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -module Notifications - # Send a message to a RabbitMQ exchange that an item has been updated. - # The primary use case here is that an index may need to be updated (dor-indexing-app) - class ObjectUpdated - def self.publish(model:) - return unless Settings.rabbitmq.enabled - - Rails.logger.debug "Publishing Rabbitmq Message for updating #{model.externalIdentifier}" - new(model:, channel: RabbitChannel.instance).publish - Rails.logger.debug "Published Rabbitmq Message for updating #{model.externalIdentifier}" - end - - def initialize(model:, channel:) - @model = model - @channel = channel - end - - def publish - message = { - model: Cocina::Models.without_metadata(model).to_h, - created_at: model.created.to_datetime.httpdate, - modified_at: model.modified.to_datetime.httpdate - } - exchange.publish(message.to_json, routing_key:) - end - - private - - attr_reader :model, :channel - - def exchange - channel.topic('sdr.objects.updated') - end - - # Using the project as a routing key because listeners may only care about their projects. - def routing_key - model.is_a?(Cocina::Models::AdminPolicyWithMetadata) ? 'SDR' : AdministrativeTags.project(identifier: model.externalIdentifier).first - end - end -end diff --git a/app/services/update_object_service.rb b/app/services/update_object_service.rb index d80788132..c8afeac63 100644 --- a/app/services/update_object_service.rb +++ b/app/services/update_object_service.rb @@ -52,8 +52,7 @@ def update event_factory.create(druid:, event_type: 'update', data: { success: true, request: cocina_object_without_metadata.to_h }) - # Broadcast this update action to a topic - Notifications::ObjectUpdated.publish(model: cocina_object_with_metadata) + Indexer.reindex_later(cocina_object: cocina_object_with_metadata) # Update all items in the collection if necessary PublishItemsModifiedJob.perform_later(druid) if update_items diff --git a/spec/jobs/publish_items_modified_job_spec.rb b/spec/jobs/publish_items_modified_job_spec.rb index f37adbf06..2a4844de2 100644 --- a/spec/jobs/publish_items_modified_job_spec.rb +++ b/spec/jobs/publish_items_modified_job_spec.rb @@ -12,11 +12,11 @@ before do allow(MemberService).to receive(:for).and_return([{ 'id' => '123' }, { 'id' => '456' }]) allow(CocinaObjectStore).to receive(:find).and_return(instance_double(Cocina::Models::DRO), instance_double(Cocina::Models::DRO)) - allow(Notifications::ObjectUpdated).to receive(:publish) + allow(Indexer).to receive(:reindex_later) perform end it 'sends object updated notifications for each member' do - expect(Notifications::ObjectUpdated).to have_received(:publish).twice + expect(Indexer).to have_received(:reindex_later).twice end end diff --git a/spec/jobs/reindex_job_spec.rb b/spec/jobs/reindex_job_spec.rb new file mode 100644 index 000000000..56b68379f --- /dev/null +++ b/spec/jobs/reindex_job_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ReindexJob do + subject(:perform) do + described_class.perform_now(model: dro.to_h, created: Time.zone.now, modified: Time.zone.now) + end + + let(:dro) { build(:dro) } + + context 'when no errors' do + before do + allow(Indexer).to receive(:reindex) + end + + it 'invokes the Indexer' do + perform + expect(Indexer).to have_received(:reindex).with(cocina_object: an_instance_of(Cocina::Models::DROWithMetadata)) + end + end + + context 'when an error' do + before do + allow(Indexer).to receive(:reindex).and_raise(DorIndexing::RepositoryError) + allow(Honeybadger).to receive(:notify) + end + + it 'Honeybadger alerts' do + perform + expect(Indexer).to have_received(:reindex).with(cocina_object: an_instance_of(Cocina::Models::DROWithMetadata)) + expect(Honeybadger).to have_received(:notify) + end + end +end diff --git a/spec/requests/administrative_tags_spec.rb b/spec/requests/administrative_tags_spec.rb index 6f2cfd85b..c03a42ae8 100644 --- a/spec/requests/administrative_tags_spec.rb +++ b/spec/requests/administrative_tags_spec.rb @@ -35,7 +35,7 @@ describe '#create' do before do allow(AdministrativeTags).to receive(:create) - allow(Notifications::ObjectUpdated).to receive(:publish) + allow(Indexer).to receive(:reindex_later) end context 'when happy path (without replacement)' do @@ -45,7 +45,7 @@ headers: { 'Authorization' => "Bearer #{jwt}", 'Content-Type' => 'application/json' } expect(AdministrativeTags).to have_received(:create) .with(identifier: druid, tags:, replace: nil) - expect(Notifications::ObjectUpdated).to have_received(:publish) + expect(Indexer).to have_received(:reindex_later) expect(response).to have_http_status(:created) end end @@ -57,7 +57,7 @@ headers: { 'Authorization' => "Bearer #{jwt}", 'Content-Type' => 'application/json' } expect(AdministrativeTags).to have_received(:create) .with(identifier: druid, tags:, replace: true) - expect(Notifications::ObjectUpdated).to have_received(:publish) + expect(Indexer).to have_received(:reindex_later) expect(response).to have_http_status(:created) end end @@ -74,7 +74,7 @@ headers: { 'Authorization' => "Bearer #{jwt}", 'Content-Type' => 'application/json' } expect(response).to have_http_status(:not_found) expect(response.body).to eq('Unable to find \'druid:mx123qw2323\' in repository. See logger for details.') - expect(Notifications::ObjectUpdated).not_to have_received(:publish) + expect(Indexer).not_to have_received(:reindex_later) end end @@ -119,7 +119,7 @@ before do allow(AdministrativeTags).to receive(:update) - allow(Notifications::ObjectUpdated).to receive(:publish) + allow(Indexer).to receive(:reindex_later) end context 'when happy path' do @@ -129,7 +129,7 @@ headers: { 'Authorization' => "Bearer #{jwt}", 'Content-Type' => 'application/json' } expect(AdministrativeTags).to have_received(:update) .with(identifier: druid, current: current_tag, new: new_tag) - expect(Notifications::ObjectUpdated).to have_received(:publish) + expect(Indexer).to have_received(:reindex_later) expect(response).to have_http_status(:no_content) end end @@ -253,7 +253,7 @@ def full_messages before do allow(AdministrativeTags).to receive(:destroy) - allow(Notifications::ObjectUpdated).to receive(:publish) + allow(Indexer).to receive(:reindex_later) end context 'when happy path' do @@ -262,7 +262,7 @@ def full_messages headers: { 'Authorization' => "Bearer #{jwt}" } expect(AdministrativeTags).to have_received(:destroy) .with(identifier: druid, tag:) - expect(Notifications::ObjectUpdated).to have_received(:publish) + expect(Indexer).to have_received(:reindex_later) expect(response).to have_http_status(:no_content) end end diff --git a/spec/services/indexer_spec.rb b/spec/services/indexer_spec.rb index 850de2dd9..7df200a64 100644 --- a/spec/services/indexer_spec.rb +++ b/spec/services/indexer_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe Indexer do - let(:cocina_object) { instance_double(Cocina::Models::DRO, externalIdentifier: druid) } + let(:cocina_object) { build(:dro_with_metadata, id: druid) } let(:druid) { 'druid:bc123df4567' } let(:solr_doc) { instance_double(Hash) } @@ -40,6 +40,21 @@ end end + describe '#reindex_later' do + before do + allow(ReindexJob).to receive(:perform_later) + end + + it 'reindexes the object later' do + described_class.reindex_later(cocina_object:) + expect(ReindexJob).to have_received(:perform_later).with( + model: cocina_object.to_h, + created: cocina_object.created, + modified: cocina_object.modified + ) + end + end + describe '#cocina_finder' do before do allow(CocinaObjectStore).to receive(:find).and_return(cocina_object) diff --git a/spec/services/notifications/object_updated_spec.rb b/spec/services/notifications/object_updated_spec.rb deleted file mode 100644 index 17f640a7b..000000000 --- a/spec/services/notifications/object_updated_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Notifications::ObjectUpdated do - subject(:publish) { described_class.publish(model: model.to_cocina_with_metadata) } - - let(:created_at) { '04 Feb 2022' } - let(:updated_at) { '04 Feb 2022' } - let(:channel) { instance_double(Notifications::RabbitChannel, topic:) } - let(:topic) { instance_double(Bunny::Exchange, publish: true) } - let(:message) { "{\"model\":#{model_json},\"created_at\":\"Fri, 04 Feb 2022 00:00:00 GMT\",\"modified_at\":\"Fri, 04 Feb 2022 00:00:00 GMT\"}" } - let(:model_json) { model.to_cocina.to_json } - - context 'when RabbitMQ is enabled' do - before do - allow(Notifications::RabbitChannel).to receive(:instance).and_return(channel) - allow(Settings.rabbitmq).to receive(:enabled).and_return(true) - end - - context 'when called with a DRO' do - let(:model) { build(:ar_dro, created_at:, updated_at:) } - - before do - allow(AdministrativeTags).to receive(:project).and_return(['h2']) - end - - it 'is successful' do - publish - expect(topic).to have_received(:publish).with(message, routing_key: 'h2') - expect(AdministrativeTags).to have_received(:project).with(identifier: model.external_identifier) - end - end - - context 'when called with an AdminPolicy' do - let(:model) { build(:ar_admin_policy, created_at:, updated_at:) } - - it 'is successful' do - publish - expect(topic).to have_received(:publish).with(message, routing_key: 'SDR') - end - end - end - - context 'when RabbitMQ is disabled' do - before do - allow(Settings.rabbitmq).to receive(:enabled).and_return(false) - end - - context 'when called with a DRO' do - let(:model) { build(:ar_dro, created_at:, updated_at:) } - - it 'does not receive a message' do - publish - expect(topic).not_to have_received(:publish) - end - end - end -end diff --git a/spec/services/update_object_service_spec.rb b/spec/services/update_object_service_spec.rb index 07b9e4ac3..b920d9a17 100644 --- a/spec/services/update_object_service_spec.rb +++ b/spec/services/update_object_service_spec.rb @@ -11,6 +11,7 @@ before do allow(Cocina::ObjectValidator).to receive(:validate) allow(VersionService).to receive(:open?).and_return(open) + allow(Indexer).to receive(:reindex_later) end context 'when object is a DRO' do