diff --git a/.rubocop_fixme.yml b/.rubocop_fixme.yml index 4ef63b9fb4..8ea6f36193 100644 --- a/.rubocop_fixme.yml +++ b/.rubocop_fixme.yml @@ -63,6 +63,7 @@ RSpec/ExampleLength: - 'spec/jobs/content_restored_version_event_job_spec.rb' - 'spec/jobs/content_new_version_event_job_spec.rb' - 'spec/jobs/content_depositor_change_event_job_spec.rb' + - 'spec/jobs/change_depositor_event_job_spec.rb' - 'spec/jobs/content_deposit_event_job_spec.rb' - 'spec/jobs/content_delete_event_job_spec.rb' - 'spec/jobs/ingest_file_job_spec.rb' @@ -100,7 +101,6 @@ RSpec/SubjectStub: - 'spec/models/hyrax/operation_spec.rb' - 'spec/controllers/hyrax/accepts_batches_controller_spec.rb' - 'spec/indexers/hyrax/repository_reindexer_spec.rb' - - 'spec/jobs/content_depositor_change_event_job_spec.rb' - 'spec/lib/hyrax/analytics_spec.rb' - 'spec/models/job_io_wrapper_spec.rb' - 'spec/search_builders/hyrax/abstract_type_relation_spec.rb' diff --git a/app/jobs/change_depositor_event_job.rb b/app/jobs/change_depositor_event_job.rb new file mode 100644 index 0000000000..9ecff19a58 --- /dev/null +++ b/app/jobs/change_depositor_event_job.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true +# Log work depositor change to activity streams +# +# This class simply logs the transfer, pulling data from the object that was +# just transferred. It does not perform the transfer. +class ChangeDepositorEventJob < ContentEventJob + include Rails.application.routes.url_helpers + include ActionDispatch::Routing::PolymorphicRoutes + + # @param [ActiveFedora::Base] work the work that's been transfered + def perform(work) + # these get set to repo_object and depositor + super(work, new_owner(work)) + end + + def action + "User #{link_to_profile repo_object.proxy_depositor} has transferred #{link_to_work repo_object.title.first} to user #{link_to_profile depositor}" + end + + def link_to_work(text) + link_to text, polymorphic_path(repo_object) + end + + # Log the event to the work's stream + def log_work_event(work) + work.log_event(event) + end + alias log_file_set_event log_work_event + + # overriding default to log the event to the depositor instead of their profile + # and to log the event for both users + def log_user_event(depositor) + previous_owner.log_profile_event(event) + depositor.log_event(event) + end + + private def previous_owner + ::User.find_by_user_key(repo_object.proxy_depositor) + end + + # used for @depositor + private def new_owner(work) + ::User.find_by_user_key(work.depositor) + end +end diff --git a/app/services/hyrax/change_depositor_service.rb b/app/services/hyrax/change_depositor_service.rb new file mode 100644 index 0000000000..d630aaa69e --- /dev/null +++ b/app/services/hyrax/change_depositor_service.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true +module Hyrax + class ChangeDepositorService + # Set the given `user` as the depositor of the given `work`; If + # `reset` is true, first remove all previous permissions. + # + # Used to transfer a an existing work, and to set + # depositor / proxy_depositor on a work newly deposited + # on_behalf_of another user + # + # @param work [ActiveFedora::Base, Valkyrie::Resource] the work + # that is receiving a change of depositor + # @param user [User] the user that will "become" the depositor of + # the given work + # @param reset [TrueClass, FalseClass] when true, first clear + # permissions for the given work and contained file + # sets; regardless of true/false make the given user + # the depositor of the given work + # @return work, updated if necessary + def self.call(work, user, reset) + # user_key is nil when there was no `on_behalf_of` in the form + return work unless user&.user_key + # Don't transfer to self + return work if user.user_key == work.depositor + + work = case work + when ActiveFedora::Base + call_af(work, user, reset) + when Valkyrie::Resource + call_valkyrie(work, user, reset) + end + ContentDepositorChangeEventJob.perform_later(work) + work + end + + def self.call_af(work, user, reset) + work.proxy_depositor = work.depositor + work.permissions = [] if reset + work.apply_depositor_metadata(user) + work.file_sets.each do |f| + f.permissions = [] if reset + f.apply_depositor_metadata(user) + f.save! + end + work.save! + work + end + private_class_method :call_af + + # @todo Should this include some dependency injection regarding + # the Hyrax.persister and Hyrax.custom_queries? + def self.call_valkyrie(work, user, reset) + if reset + work.permission_manager.acl.permissions = [] + work.permission_manager.acl.save + end + + work.proxy_depositor = work.depositor + apply_depositor_metadata(work, user) + + apply_valkyrie_changes_to_file_sets(work: work, user: user, reset: reset) + + Hyrax.persister.save(resource: work) + end + private_class_method :call_valkyrie + + def self.apply_depositor_metadata(resource, depositor) + depositor_id = depositor.respond_to?(:user_key) ? depositor.user_key : depositor + resource.depositor = depositor_id if resource.respond_to? :depositor= + Hyrax::AccessControlList.new(resource: resource).grant(:edit).to(::User.find_by_user_key(depositor_id)).save + end + private_class_method :apply_depositor_metadata + + def self.apply_valkyrie_changes_to_file_sets(work:, user:, reset:) + Hyrax.custom_queries.find_child_file_sets(resource: work).each do |f| + if reset + f.permission_manager.acl.permissions = [] + f.permission_manager.acl.save + end + apply_depositor_metadata(f, user) + Hyrax.persister.save(resource: f) + end + end + private_class_method :apply_valkyrie_changes_to_file_sets + end +end diff --git a/spec/jobs/change_depositor_event_job_spec.rb b/spec/jobs/change_depositor_event_job_spec.rb new file mode 100644 index 0000000000..b4a2350c95 --- /dev/null +++ b/spec/jobs/change_depositor_event_job_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true +RSpec.describe ChangeDepositorEventJob do + let(:previous_user) { create(:user) } + let(:new_user) { create(:user) } + let(:mock_time) { Time.zone.at(1) } + let(:event) do + { action: + "User #{previous_user.user_key} " \ + "has transferred BethsMac " \ + "to user #{new_user.user_key}", + timestamp: '1' } + end + + before do + allow(Time).to receive(:now).at_least(:once).and_return(mock_time) + end + + context "when passing an ActiveFedora work" do + let(:generic_work) { create(:generic_work, title: ['BethsMac'], user: new_user, proxy_depositor: previous_user.user_key) } + + it "logs the event to the proxy depositor's profile, the depositor's dashboard, and the FileSet" do + expect { described_class.perform_now(generic_work) } + .to change { previous_user.profile_events.length } + .by(1) + .and change { new_user.events.length } + .by(1) + .and change { generic_work.events.length } + .by(1) + expect(previous_user.profile_events.first).to eq(event) + expect(new_user.events.first).to eq(event) + expect(generic_work.events.first).to eq(event) + end + end + + context "when passing a valkyrie work" do + let(:monograph) { valkyrie_create(:monograph, title: ['BethsMac'], depositor: new_user.user_key, proxy_depositor: previous_user.user_key) } + + let(:event) do + { action: "User #{previous_user.user_key} " \ + "has transferred BethsMac " \ + "to user #{new_user.user_key}", + timestamp: '1' } + end + + it "logs the event to the proxy depositor's profile, the depositor's dashboard, and the FileSet" do + expect { subject.perform(monograph) } + .to change { previous_user.profile_events.length } + .by(1) + .and change { new_user.events.length } + .by(1) + .and change { monograph.events.length } + .by(1) + expect(previous_user.profile_events.first).to eq(event) + expect(new_user.events.first).to eq(event) + expect(monograph.events.first).to eq(event) + end + end +end diff --git a/spec/services/hyrax/change_depositor_service_spec.rb b/spec/services/hyrax/change_depositor_service_spec.rb new file mode 100644 index 0000000000..08de3447e9 --- /dev/null +++ b/spec/services/hyrax/change_depositor_service_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true +RSpec.describe Hyrax::ChangeDepositorService do + let!(:depositor) { create(:user) } + let!(:receiver) { create(:user) } + + context "for Active Fedora objects" do + let!(:file) do + create(:file_set, user: depositor) + end + let!(:work) do + create(:work, title: ['Test work'], user: depositor) + end + + before do + work.members << file + described_class.call(work, receiver, reset) + end + + context "by default, when permissions are not reset" do + let(:reset) { false } + + it "changes the depositor and records an original depositor" do + work.reload + expect(work.depositor).to eq receiver.user_key + expect(work.proxy_depositor).to eq depositor.user_key + expect(work.edit_users).to include(receiver.user_key, depositor.user_key) + end + + it "changes the depositor of the child file sets" do + file.reload + expect(file.depositor).to eq receiver.user_key + expect(file.edit_users).to include(receiver.user_key, depositor.user_key) + end + end + + context "when permissions are reset" do + let(:reset) { true } + + it "excludes the depositor from the edit users" do + work.reload + expect(work.depositor).to eq receiver.user_key + expect(work.proxy_depositor).to eq depositor.user_key + expect(work.edit_users).to contain_exactly(receiver.user_key) + end + + it "changes the depositor of the child file sets" do + file.reload + expect(file.depositor).to eq receiver.user_key + expect(file.edit_users).to contain_exactly(receiver.user_key) + end + end + end + + context "for Valkyrie objects" do + let!(:base_work) { valkyrie_create(:hyrax_work, :with_member_file_sets, title: ['SoonToBeSomeoneElses'], depositor: depositor.user_key, edit_users: [depositor]) } + before do + work_acl = Hyrax::AccessControlList.new(resource: base_work) + Hyrax.custom_queries.find_child_file_sets(resource: base_work).each do |file_set| + Hyrax::AccessControlList.copy_permissions(source: work_acl, target: file_set) + end + end + + context "by default, when permissions are not reset" do + it "changes the depositor and records an original depositor" do + expect(ContentDepositorChangeEventJob).to receive(:perform_later) + described_class.call(base_work, receiver, false) + work = Hyrax.query_service.find_by_alternate_identifier(alternate_identifier: base_work.id, use_valkyrie: true) + expect(work.depositor).to eq receiver.user_key + expect(work.proxy_depositor).to eq depositor.user_key + expect(work.edit_users.to_a).to include(receiver.user_key, depositor.user_key) + end + + it "changes the depositor of the child file sets" do + described_class.call(base_work, receiver, false) + file_sets = Hyrax.custom_queries.find_child_file_sets(resource: base_work) + expect(file_sets.size).not_to eq 0 # A quick check to make sure our each block works + + file_sets.each do |file_set| + expect(file_set.depositor).to eq receiver.user_key + expect(file_set.edit_users.to_a).to include(receiver.user_key, depositor.user_key) + end + end + end + + context "when permissions are reset" do + it "changes the depositor and records an original depositor" do + expect(ContentDepositorChangeEventJob).to receive(:perform_later) + described_class.call(base_work, receiver, true) + work = Hyrax.query_service.find_by_alternate_identifier(alternate_identifier: base_work.id, use_valkyrie: true) + expect(work.depositor).to eq receiver.user_key + expect(work.proxy_depositor).to eq depositor.user_key + expect(work.edit_users.to_a).to contain_exactly(receiver.user_key) + end + + it "changes the depositor of the child file sets" do + described_class.call(base_work, receiver, true) + file_sets = Hyrax.custom_queries.find_child_file_sets(resource: base_work) + expect(file_sets.size).not_to eq 0 # A quick check to make sure our each block works + + file_sets.each do |file_set| + expect(file_set.depositor).to eq receiver.user_key + expect(file_set.edit_users.to_a).to contain_exactly(receiver.user_key) + end + end + end + + context "when no user is provided" do + it "does not update the work" do + expect(ContentDepositorChangeEventJob).not_to receive(:perform_later) + persister = double("Valkyrie Persister") + allow(Hyrax).to receive(:persister).and_return(persister) + allow(persister).to receive(:save) + + described_class.call(base_work, nil, false) + expect(persister).not_to have_received(:save) + end + end + + context "when transfer is requested to the existing owner" do + let!(:base_work) { valkyrie_create(:hyrax_work, :with_member_file_sets, title: ['AlreadyMine'], depositor: depositor.user_key, edit_users: [depositor]) } + + it "does not update the work" do + expect(ContentDepositorChangeEventJob).not_to receive(:perform_later) + persister = double("Valkyrie Persister") + allow(Hyrax).to receive(:persister).and_return(persister) + allow(persister).to receive(:save) + + described_class.call(base_work, depositor, false) + expect(persister).not_to have_received(:save) + end + end + end +end