Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update depositors (proxies, transfers) synchronously #5537

Closed
wants to merge 7 commits into from
Closed
5 changes: 3 additions & 2 deletions app/actors/hyrax/actors/transfer_request_actor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ def create(env)
def create_proxy_deposit_request(env)
proxy = env.curation_concern.on_behalf_of
return true if proxy.blank?
ContentDepositorChangeEventJob.perform_later(env.curation_concern,
::User.find_by_user_key(proxy))
work = env.curation_concern
user = ::User.find_by_user_key(proxy)
Hyrax::ChangeContentDepositorService.call(work, user, false)
true
end
end
Expand Down
9 changes: 6 additions & 3 deletions app/controllers/concerns/hyrax/works_controller_behavior.rb
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,12 @@ def create_valkyrie_work

result =
transactions['change_set.create_work']
.with_step_args('work_resource.add_to_parent' => { parent_id: params[:parent_id], user: current_user },
'work_resource.add_file_sets' => { uploaded_files: uploaded_files, file_set_params: params[hash_key_for_curation_concern][:file_set] },
'change_set.set_user_as_depositor' => { user: current_user })
.with_step_args(
'work_resource.add_to_parent' => { parent_id: params[:parent_id], user: current_user },
'work_resource.add_file_sets' => { uploaded_files: uploaded_files, file_set_params: params[hash_key_for_curation_concern][:file_set] },
'change_set.set_user_as_depositor' => { user: current_user },
'work_resource.change_content_depositor' => { user: ::User.find_by_user_key(form.on_behalf_of) }
)
.call(form)
@curation_concern = result.value_or { return after_create_error(transaction_err_msg(result)) }
after_create_response
Expand Down
36 changes: 17 additions & 19 deletions app/jobs/content_depositor_change_event_job.rb
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
# frozen_string_literal: true
# Log work depositor change to activity streams
#
# @attr [Boolean] reset (false) should the access controls be reset. This means revoking edit access from the depositor
# This class simply logs the transfer, pulling data from the object that was
# just transferred. It does not perform the transfer.
class ContentDepositorChangeEventJob < ContentEventJob
include Rails.application.routes.url_helpers
include ActionDispatch::Routing::PolymorphicRoutes

attr_accessor :reset

# @param [ActiveFedora::Base] work the work to be transfered
# @param [User] user the user the work is being transfered to.
# @param [TrueClass,FalseClass] reset (false) if true, reset the access controls. This revokes edit access from the depositor
def perform(work, user, reset = false)
@reset = reset
super(work, user)
# @param [ActiveFedora::Base] work the work that's been transfered
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this parameter be a Hyrax::Work (i.e., Valkyrie::Resource)?

Suggested change
# @param [ActiveFedora::Base] work the work that's been transfered
# @param [ActiveFedora::Base,Hyrax::Work] work the work that's been transferred

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes thank you

def perform(work)
# these get set to repo_object and depositor
super(work, new_owner(work))
end

def action
"User #{link_to_profile work.proxy_depositor} has transferred #{link_to_work work.title.first} to user #{link_to_profile depositor}"
"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(work)
link_to text, polymorphic_path(repo_object)
end

# Log the event to the work's stream
Expand All @@ -30,18 +27,19 @@ def log_work_event(work)
end
alias log_file_set_event log_work_event

def work
@work ||= Hyrax::ChangeContentDepositorService.call(repo_object, depositor, reset)
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method call has been used for over 5 years. So it is making less sense to me now that this would be a problem. I originally thought this was moved here when the listener was created. Still not ruling out the various methods are chasing each other.


# 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)
# log the event to the proxy depositor's profile
proxy_depositor.log_profile_event(event)
previous_owner.log_profile_event(event)
depositor.log_event(event)
end

def proxy_depositor
@proxy_depositor ||= ::User.find_by_user_key(work.proxy_depositor)
private def previous_owner
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, I haven't seen including private on the line with the method def. Has this always worked or did this start in a specific Rails version? Typically, the private keyword is on a line by itself with all methods that follow being private. In Hyrax, that is the only pattern I see in use.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm not sure when. I've been thinking it would be nice to use it because it's more explicit than a private floating around somewhere in the file, especially if extra indentation isn't enforced. But it's not idiomatic; I'll change it.

::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
2 changes: 1 addition & 1 deletion app/models/proxy_deposit_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def send_request_transfer_message_as_part_of_update

# @param [TrueClass,FalseClass] reset (false) if true, reset the access controls. This revokes edit access from the depositor
def transfer!(reset = false)
ContentDepositorChangeEventJob.perform_later(work, receiving_user, reset)
Hyrax::ChangeContentDepositorService.call(work, receiving_user, reset)
fulfill!(status: ACCEPTED)
end

Expand Down
25 changes: 18 additions & 7 deletions app/services/hyrax/change_content_depositor_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ class ChangeContentDepositorService
# 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
Expand All @@ -12,13 +16,21 @@ class ChangeContentDepositorService
# 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)
case work
when ActiveFedora::Base
call_af(work, user, reset)
when Valkyrie::Resource
call_valkyrie(work, user, reset)
end
# 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice to have exit checks and avoid unnecessary work


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)
Expand Down Expand Up @@ -49,7 +61,6 @@ def self.call_valkyrie(work, user, reset)
apply_valkyrie_changes_to_file_sets(work: work, user: user, reset: reset)

Hyrax.persister.save(resource: work)
work
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch

end
private_class_method :call_valkyrie

Expand Down
5 changes: 5 additions & 0 deletions lib/hyrax/transactions/container.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class Container # rubocop:disable Metrics/ClassLength
require 'hyrax/transactions/steps/add_to_collections'
require 'hyrax/transactions/steps/add_to_parent'
require 'hyrax/transactions/steps/apply_collection_type_permissions'
require 'hyrax/transactions/steps/change_content_depositor'
require 'hyrax/transactions/steps/check_for_empty_admin_set'
require 'hyrax/transactions/steps/delete_access_control'
require 'hyrax/transactions/steps/delete_resource'
Expand Down Expand Up @@ -204,6 +205,10 @@ class Container # rubocop:disable Metrics/ClassLength
Steps::AddToParent.new
end

ops.register 'change_content_depositor' do
Steps::ChangeContentDepositor.new
end

ops.register 'delete' do
Steps::DeleteResource.new
end
Expand Down
44 changes: 44 additions & 0 deletions lib/hyrax/transactions/steps/change_content_depositor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true
require 'dry/monads'

module Hyrax
module Transactions
module Steps
##
# Add a given `::User` as the `#depositor`
# Move the previous value of that property to `#proxy_depositor`
#
#
# If no user is given, simply passes as a `Success`.
#
# @since 3.4.0
class ChangeContentDepositor
include Dry::Monads[:result]

##
# @param [Hyrax::Work] obj
# @param user [User] the user that will "become" the depositor of
# the given work
#
# @return [Dry::Monads::Result]
def call(obj, user: NullUser.new, reset: false)
obj = Hyrax::ChangeContentDepositorService.call(obj, user, reset)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this bail if user is null?

Suggested change
obj = Hyrax::ChangeContentDepositorService.call(obj, user, reset)
return Success(obj) unless user&.user_key
obj = Hyrax::ChangeContentDepositorService.call(obj, user, reset)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the service has this guard, do you think we should do it both places?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We agreed to add it to make the code more explicit.


Success(obj)
rescue StandardError => err
Failure([err.message, obj])
end

##
# @api private
class NullUser
##
# @return [nil]
def user_key
nil
end
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/hyrax/transactions/work_create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class WorkCreate < Transaction
'change_set.set_user_as_depositor',
'change_set.apply',
'work_resource.save_acl',
'work_resource.change_content_depositor',
'work_resource.add_file_sets',
'work_resource.add_to_parent'].freeze

Expand Down
2 changes: 1 addition & 1 deletion spec/actors/hyrax/actors/transfer_request_actor_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
end

it "adds the template users to the work" do
expect(ContentDepositorChangeEventJob).to receive(:perform_later).with(work, User)
expect(ContentDepositorChangeEventJob).to receive(:perform_later).with(work)
expect(middleware.create(env)).to be true
end
end
Expand Down
11 changes: 11 additions & 0 deletions spec/controllers/concerns/hyrax/works_controller_behavior_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@
expect(Sipity::Entity(assigns[:curation_concern]).workflow_state).to have_attributes(name: "deposited")
end

context 'when depositing as a proxy for (on_behalf_of) another user' do
let(:create_params) { { title: 'comet in moominland', on_behalf_of: target_user.user_key } }
let(:target_user) { FactoryBot.create(:user) }
it 'transfers depositor status to proxy target' do
expect { post :create, params: { test_simple_work: create_params } }
.to have_enqueued_job(ContentDepositorChangeEventJob)
expect(assigns[:curation_concern]).to have_attributes(depositor: target_user.user_key)
expect(assigns[:curation_concern]).to have_attributes(proxy_depositor: user.user_key)
end
end

context 'when setting visibility' do
let(:create_params) { { title: 'comet in moominland', visibility: 'open' } }

Expand Down
30 changes: 30 additions & 0 deletions spec/hyrax/transactions/steps/change_content_depositor_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true
require 'spec_helper'
require 'hyrax/transactions'

RSpec.describe Hyrax::Transactions::Steps::ChangeContentDepositor, valkyrie_adapter: :test_adapter do
subject(:step) { described_class.new }
let(:work) { FactoryBot.valkyrie_create(:hyrax_work) }

it 'gives Success(obj) in basic case' do
expect(step.call(work).value!).to eql(work)
end

context "when the depositor update is successful" do
it "calls the service" do
allow(Hyrax::ChangeContentDepositorService).to receive(:call)
step.call(work)

expect(Hyrax::ChangeContentDepositorService).to have_received(:call)
end
end

context "when there's an error" do
it 'returns a Failure' do
allow(Hyrax::ChangeContentDepositorService).to receive(:call).and_raise
result = step.call(work)

expect(result).to be_failure
end
end
end
39 changes: 20 additions & 19 deletions spec/jobs/content_depositor_change_event_job_spec.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# frozen_string_literal: true
RSpec.describe ContentDepositorChangeEventJob do
let(:user) { create(:user) }
let(:another_user) { create(:user) }
let(:previous_user) { create(:user) }
let(:new_user) { create(:user) }
let(:mock_time) { Time.zone.at(1) }
let(:event) do
{ action: "User <a href=\"/users/#{user.to_param}\">#{user.user_key}</a> " \
"has transferred <a href=\"/concern/generic_works/#{generic_work.id}\">BethsMac</a> " \
"to user <a href=\"/users/#{another_user.to_param}\">#{another_user.user_key}</a>",
{ action:
"User <a href=\"/users/#{previous_user.to_param}\">#{previous_user.user_key}</a> " \
"has transferred <a href=\"/concern/generic_works/#{generic_work.id}\">BethsMac</a> " \
"to user <a href=\"/users/#{new_user.to_param}\">#{new_user.user_key}</a>",
timestamp: '1' }
end

Expand All @@ -15,42 +16,42 @@
end

context "when passing an ActiveFedora work" do
let(:generic_work) { create(:generic_work, title: ['BethsMac'], user: user) }
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, another_user) }
.to change { user.profile_events.length }
expect { described_class.perform_now(generic_work) }
.to change { previous_user.profile_events.length }
.by(1)
.and change { another_user.events.length }
.and change { new_user.events.length }
.by(1)
.and change { generic_work.events.length }
.by(1)
expect(user.profile_events.first).to eq(event)
expect(another_user.events.first).to eq(event)
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: user.user_key) }
let(:monograph) { valkyrie_create(:monograph, title: ['BethsMac'], depositor: new_user.user_key, proxy_depositor: previous_user.user_key) }

let(:event) do
{ action: "User <a href=\"/users/#{user.to_param}\">#{user.user_key}</a> " \
{ action: "User <a href=\"/users/#{previous_user.to_param}\">#{previous_user.user_key}</a> " \
"has transferred <a href=\"/concern/monographs/#{monograph.id}\">BethsMac</a> " \
"to user <a href=\"/users/#{another_user.to_param}\">#{another_user.user_key}</a>",
"to user <a href=\"/users/#{new_user.to_param}\">#{new_user.user_key}</a>",
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, another_user) }
.to change { user.profile_events.length }
expect { subject.perform(monograph) }
.to change { previous_user.profile_events.length }
.by(1)
.and change { another_user.events.length }
.and change { new_user.events.length }
.by(1)
.and change { monograph.events.length }
.by(1)
expect(user.profile_events.first).to eq(event)
expect(another_user.events.first).to eq(event)
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
Expand Down
3 changes: 2 additions & 1 deletion spec/models/proxy_deposit_request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,12 @@

describe '#transfer!' do
it 'will change the status, fulfillment_date, and perform later the ContentDepositorChangeEventJob' do
allow(ContentDepositorChangeEventJob).to receive(:perform_later)
allow(Hyrax::ChangeContentDepositorService).to receive(:call)
subject.transfer!
expect(subject.status).to eq(described_class::ACCEPTED)
expect(subject.fulfillment_date).to be_a(Time)
expect(subject).to be_accepted
expect(Hyrax::ChangeContentDepositorService).to have_received(:call)
end
end

Expand Down
Loading