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

Fix sipity with migrations #2471

Merged
merged 5 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions app/models/sipity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# frozen_string_literal: true

# OVERRIDE Hyrax 5.0.x to handle lazy migration logic for solr documents
# This can be removed once code is ported back to Hyrax
# Due to loading sequence issues the entire module is included in the override.
# rubocop:disable Metrics/ModuleLength
module Sipity
##
# Cast a given input (e.g. a +::User+ or {Hyrax::Group} to a {Sipity::Agent}).
#
# @param input [Object]
#
# @return [Sipity::Agent]
def Agent(input, &block) # rubocop:disable Naming/MethodName
result = case input
when Sipity::Agent
input
end

handle_conversion(input, result, :to_sipity_agent, &block)
end
module_function :Agent

##
# Cast an object to an Entity
#
# @param input [Object]
#
# @return [Sipity::Entity]
# rubocop:disable Naming/MethodName, Metrics/CyclomaticComplexity, Metrics/MethodLength
def Entity(input, &block) # rubocop:disable Metrics/AbcSize
Hyrax.logger.debug("Trying to make an Entity for #{input.inspect}")

result = case input
when Sipity::Entity
input
when URI::GID, GlobalID
Hyrax.logger.debug("Entity() got a GID, searching by proxy")
Entity.find_by(proxy_for_global_id: input.to_s)
when SolrDocument
if Hyrax.config.valkyrie_transition? # we need the actual model, not the mapped "Resource" model
item = Hyrax.query_service.find_by(id: input.id)
# rubocop:disable Lint/RedundantStringCoercion
Hyrax.logger.debug("Entity() got a SolrDocument with valkyrie_transition, retrying on item #{item.id.to_s}")
# rubocop:enable Lint/RedundantStringCoercion
Entity(item)
else
Hyrax.logger.debug("Entity() got a SolrDocument, retrying on #{input.to_model}")
Entity(input.to_model)
end
when Draper::Decorator
Hyrax.logger.debug("Entity() got a Decorator, retrying on #{input.model}")
Entity(input.model)
when Sipity::Comment
Hyrax.logger.debug("Entity() got a Comment, retrying on #{input.entity}")
Entity(input.entity)
when Valkyrie::Resource
Hyrax.logger.debug("Entity() got a Resource, retrying on #{Hyrax::GlobalID(input)}")
Entity(Hyrax::GlobalID(input))
else
Hyrax.logger.debug("Entity() got something else, testing #to_global_id")
Entity(input.to_global_id) if input.respond_to?(:to_global_id)
end

Hyrax.logger.debug("Entity(): attempting conversion on #{result}")
handle_conversion(input, result, :to_sipity_entity, &block)
rescue URI::GID::MissingModelIdError
Entity(nil)
end # rubocop:enable Metrics/AbcSize
module_function :Entity
# rubocop:enable Naming/MethodName, Metrics/CyclomaticComplexity, Metrics/MethodLength

##
# Cast an object to an Role
def Role(input, &block) # rubocop:disable Naming/MethodName
result = case input
when Sipity::Role
input
when String, Symbol
Sipity::Role.find_or_create_by(name: input)
end

handle_conversion(input, result, :to_sipity_role, &block)
end
module_function :Role

##
# Cast an object to a Workflow id
# rubocop:disable Metrics/MethodLength
def WorkflowId(input, &block) # rubocop:disable Naming/MethodName
result = case input
when Sipity::Workflow
input.id
when Integer
input
when String
input.to_i
else
if input.respond_to?(workflow_id)
input.workflow_id
else
WorkflowId(Entity(input))
end
end
handle_conversion(input, result, :to_workflow_id, &block)
end
module_function :WorkflowId
# rubocop:enable Metrics/MethodLength

##
# Cast an object to a WorkflowAction in a given workflow
def WorkflowAction(input, workflow, &block) # rubocop:disable Naming/MethodName
workflow_id = WorkflowId(workflow)

result = case input
when WorkflowAction
input if input.workflow_id == workflow_id
when String, Symbol
WorkflowAction.find_by(workflow_id: workflow_id, name: input.to_s)
end

handle_conversion(input, result, :to_sipity_action, &block)
end
module_function :WorkflowAction

##
# Cast an object to a WorkflowState in a given workflow
def WorkflowState(input, workflow, &block) # rubocop:disable Naming/MethodName
result = case input
when Sipity::WorkflowState
input
when Symbol, String
WorkflowState.find_by(workflow_id: workflow.id, name: input)
end

handle_conversion(input, result, :to_sipity_workflow_state, &block)
end
module_function :WorkflowState

##
# A parent error class for all workflow errors caused by bad state
class StateError < RuntimeError; end

class ConversionError < RuntimeError
def initialize(value)
super("Unable to convert #{value.inspect}")
end
end

##
# Provides compatibility with the old `PowerConverter` conventions
def handle_conversion(input, result, method_name)
result ||= input.try(method_name)
return result unless result.nil?
return yield if block_given?

raise ConversionError.new(input) # rubocop:disable Style/RaiseArgs
end
module_function :handle_conversion
end
# rubocop:enable Metrics/ModuleLength
24 changes: 24 additions & 0 deletions app/services/migrate_resource_service_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

# OVERRIDE Hyrax 5.0.x to also migrate the sipity entity
# This can be removed once code is ported back to Hyrax
module MigrateResourceServiceDecorator
def call
original_entity = find_original_entity
result = super
migrate_entity(original_entity) if result.success? && original_entity
result
end

def find_original_entity
resource.work? ? Sipity::Entity(resource) : nil
end

def migrate_entity(entity)
migrated_resource = Hyrax.query_service.find_by(id: resource.id)
gid = Hyrax::GlobalID(migrated_resource).to_s
entity.update(proxy_for_global_id: gid) if entity.proxy_for_global_id != gid
end
end

MigrateResourceService.prepend(MigrateResourceServiceDecorator)
8 changes: 6 additions & 2 deletions spec/features/work_show_institution_visibility_spec.rb
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
# frozen_string_literal: true

RSpec.describe "Users trying to access an Institution Work's show page", type: :feature, clean: true, js: true do # rubocop:disable Layout/LineLength
let(:id) { SecureRandom.uuid }
let(:visibility) { Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_AUTHENTICATED }
let(:work) { double(GenericWork, id: id, visibility: visibility) }
let(:fake_solr_document) do
{
'has_model_ssim': ['GenericWork'],
id: SecureRandom.uuid,
id: id,
'title_tesim': ['Institution GenericWork'],
'admin_set_tesim': ['Default Admin Set'],
'suppressed_bsi': false,
'read_access_group_ssim': ['registered'],
'edit_access_group_ssim': ['admin'],
'edit_access_person_ssim': ['fake@example.com'],
'visibility_ssi': 'authenticated'
'visibility_ssi': visibility
}
end

before do
solr = Blacklight.default_index.connection
solr.add(fake_solr_document)
solr.commit
allow(Hyrax.query_service).to receive(:find_by).with(id: id).and_return(work)
end

context 'an unauthenticated user' do
Expand Down
8 changes: 6 additions & 2 deletions spec/features/work_show_open_visibility_spec.rb
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
# frozen_string_literal: true

RSpec.describe "Users trying to access a Public Work's show page", type: :feature, clean: true, js: true do # rubocop:disable Layout/LineLength
let(:id) { SecureRandom.uuid }
let(:visibility) { Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_PUBLIC }
let(:work) { double(GenericWork, id: id, visibility: visibility) }
let(:fake_solr_document) do
{
'has_model_ssim': ['GenericWork'],
id: SecureRandom.uuid,
id: id,
'title_tesim': ['Public GenericWork'],
'admin_set_tesim': ['Default Admin Set'],
'suppressed_bsi': false,
'read_access_group_ssim': ['public'],
'edit_access_group_ssim': ['admin'],
'edit_access_person_ssim': ['fake@example.com'],
'visibility_ssi': 'open'
'visibility_ssi': visibility
}
end

before do
solr = Blacklight.default_index.connection
solr.add(fake_solr_document)
solr.commit
allow(Hyrax.query_service).to receive(:find_by).with(id: id).and_return(work)
end

context 'an unauthenticated user' do
Expand Down
10 changes: 7 additions & 3 deletions spec/features/work_show_private_visibility_spec.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
# frozen_string_literal: true

RSpec.describe "Users trying to access a Private Work's show page", type: :feature, clean: true, js: true do # rubocop:disable Layout/LineLength
let(:id) { SecureRandom.uuid }
let(:visibility) { Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_PRIVATE }
let(:work) { double(GenericWork, id: id, visibility: visibility) }
let(:fake_solr_document) do
{
'has_model_ssim': ['GenericWork'],
id: SecureRandom.uuid,
'title_tesim': ['Private GenericWork'],
id: id,
'title_tesim': ['Private GenericWork'],
'admin_set_tesim': ['Default Admin Set'],
'suppressed_bsi': false,
'edit_access_group_ssim': ['admin'],
'edit_access_person_ssim': ['fake@example.com'],
'visibility_ssi': 'restricted'
'visibility_ssi': visibility
}
end

before do
solr = Blacklight.default_index.connection
solr.add(fake_solr_document)
solr.commit
allow(Hyrax.query_service).to receive(:find_by).with(id: id).and_return(work)
end

context 'an unauthenticated user' do
Expand Down
82 changes: 82 additions & 0 deletions spec/models/hyrax/sipity_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# frozen_string_literal: true

# OVERRIDE: Hyrax 5.0.x to add an additional spec to test the Sipity.Entity conversions with migration
# This can be removed once code is ported back to Hyrax
RSpec.describe Sipity do
describe '.Entity' do
let(:subject) { described_class.Entity(solr_document) }

let(:user) { create(:user) }
let(:workflow_state) { FactoryBot.create(:workflow_state) }
let(:solr_document) { SolrDocument.new(id: work.id, has_model_ssim: ["GenericWork"]) }
# rubocop:disable Lint/RedundantStringCoercion
let(:proxy_string) { Hyrax::GlobalID(work).to_s }
# rubocop:enable Lint/RedundantStringCoercion
let(:saved_entity) do
Sipity::Entity.create(proxy_for_global_id: proxy_string,
workflow_state: workflow_state,
workflow: workflow_state.workflow)
end
let(:work_resource) { Hyrax.query_service.find_by(id: work.id) }
let(:migrated_entity) { Sipity::Entity.find_by(id: saved_entity.id) }

before do
saved_entity
end

context 'on a generic work with lazy migration: true' do
let(:work) { create(:generic_work, user: user) }

before do
allow(Hyrax.config).to receive(:valkyrie_transition?).and_return(true)
end

it 'will find the entity' do
expect(subject).to eq(saved_entity)
end
end

context 'on a generic work with lazy migration: false' do
let(:work) { create(:generic_work, user: user) }

before do
allow(Hyrax.config).to receive(:valkyrie_transition?).and_return(false)
end

it 'will find the entity' do
expect(subject).to eq(saved_entity)
end
end

# NOTE: ideally this would create the entity with the original work and then migrate it.
# However, the resulting entity ended up being the same (due to the adapters used in specs?)
# so we had to fake the original entity.
context 'with a migrated work' do
let(:subject) { Sipity::Entity(migrated_work) }
let(:work) { create(:generic_work, user: user) }
# rubocop:disable Lint/RedundantStringCoercion
let(:proxy_string) { "gid://hyku/this_will_be_wrong/#{work.id.to_s}" }
# rubocop:enable Lint/RedundantStringCoercion
let(:migrated_work) { Hyrax.query_service.find_by(id: work.id) }

before do
# we don't care if the work actually updated... just pretend it did so we can migrate the entity to what it should be
allow_any_instance_of(Dry::Monads::Result::Failure).to receive(:success?).and_return(true)
allow_any_instance_of(MigrateResourceServiceDecorator).to receive(:find_original_entity).and_return(saved_entity)
MigrateResourceService.new(resource: work).call
end

it 'will find the migrated entity' do
expect(subject).to eq(migrated_entity)
end
end

context 'with a native Valkyrie work' do
let(:work) { FactoryBot.valkyrie_create(:generic_work_resource) }

it 'will find the entity' do
expect(subject).to eq(saved_entity)
end
end
end
end
Loading