diff --git a/Gemfile b/Gemfile
index dbe12da9..19af7e16 100644
--- a/Gemfile
+++ b/Gemfile
@@ -58,8 +58,8 @@ gem 'net-ldap'
# temporarily hold back bulkrax version to 0.1.0
gem 'bulkrax', git: 'https://github.com/samvera-labs/bulkrax.git', ref: '5299b81' # branch: 'main'
-# EZID client from Duke
-gem 'ezid-client'
+# custom datacite client
+gem 'datacite', git: 'https://github.com/IUBLibTech/datacite-ruby', branch: 'datacore'
# Use jquery as the JavaScript library
gem 'jquery-rails'
diff --git a/Gemfile.lock b/Gemfile.lock
index d43540d1..d7a994ce 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,3 +1,13 @@
+GIT
+ remote: https://github.com/IUBLibTech/datacite-ruby
+ revision: 95c7061f1cd95e8d8134b6e03738f871037e9b60
+ branch: datacore
+ specs:
+ datacite (0.6.0)
+ dry-monads (~> 1.3)
+ faraday (~> 0.17.6)
+ zeitwerk (~> 2.4)
+
GIT
remote: https://github.com/notch8/willow_sword.git
revision: 0a669d78617c6003e4aa1a46a10447be92be27d5
@@ -340,9 +350,6 @@ GEM
ethon (0.16.0)
ffi (>= 1.15.0)
execjs (2.10.0)
- ezid-client (1.11.0)
- hashie (~> 3.4, >= 3.4.3)
- nokogiri
factory_bot (6.4.5)
activesupport (>= 5.0.0)
faraday (0.17.6)
@@ -1094,12 +1101,12 @@ DEPENDENCIES
config
coveralls_reborn
database_cleaner
+ datacite!
devise
devise-guests (~> 0.6)
dotenv-rails
down (~> 4.4)
edtf
- ezid-client
factory_bot
fcrepo_wrapper
flipflop (= 2.6.0)
diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb
index 6c4bf472..53d3bb31 100644
--- a/app/controllers/catalog_controller.rb
+++ b/app/controllers/catalog_controller.rb
@@ -223,6 +223,7 @@ def self.modified_field
}
end
+ # FIXME: review DOI searching
config.add_search_field('doi') do |field|
field.label = "Doi"
solr_name = solr_name("doi_label", :stored_searchable)
diff --git a/app/controllers/concerns/datacore/doi_controller_behavior.rb b/app/controllers/concerns/datacore/doi_controller_behavior.rb
new file mode 100644
index 00000000..877833b9
--- /dev/null
+++ b/app/controllers/concerns/datacore/doi_controller_behavior.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Datacore
+ module DoiControllerBehavior
+
+ def doi_minting_enabled?
+ ::Datacore::DoiMintingService.enabled?
+ end
+
+ def doi
+ doi_mint!
+
+ respond_to do |wants|
+ wants.html { redirect_to [main_app, curation_concern] }
+ wants.json do
+ render :show,
+ status: :ok,
+ location: polymorphic_path([main_app, curation_concern])
+ end
+ end
+ end
+
+ private
+
+ def doi_mint!
+ if !doi_minting_enabled?
+ flash[:alert] = MsgHelper.t('data_set.doi_minting_disabled')
+ elsif curation_concern.doi_pending?
+ flash[:notice] = MsgHelper.t('data_set.doi_is_being_minted')
+ elsif curation_concern.doi_minted?
+ flash[:alert] = MsgHelper.t('data_set.doi_already_exists')
+ elsif !curation_concern.doi_minimum_files?
+ flash[:alert] = MsgHelper.t('data_set.doi_requires_work_with_files')
+ elsif !curation_concern.valid?
+ flash[:alert] = MsgHelper.t('data_set.doi_requires_valid_work')
+ elsif (curation_concern.depositor != current_user.email) && !current_ability.admin?
+ flash[:alert] = MsgHelper.t('data_set.doi_user_without_access')
+ elsif curation_concern.doi_mint(current_user: current_user, event_note: 'DataSetsController')
+ flash[:notice] = MsgHelper.t('data_set.doi_minting_started')
+ else
+ flash[:error] = MsgHelper.t('data_set.doi_minting_error')
+ end
+ end
+ end
+end
diff --git a/app/controllers/concerns/deepblue/works_controller_behavior.rb b/app/controllers/concerns/deepblue/works_controller_behavior.rb
index f9f7ad83..d4b0bcbf 100644
--- a/app/controllers/concerns/deepblue/works_controller_behavior.rb
+++ b/app/controllers/concerns/deepblue/works_controller_behavior.rb
@@ -4,8 +4,7 @@ module Deepblue
module WorksControllerBehavior
extend ActiveSupport::Concern
- #in umrdr
- #include Hyrax::Controller
+
include Hyrax::WorksControllerBehavior
include Deepblue::ControllerWorkflowEventBehavior
diff --git a/app/controllers/hyrax/data_sets_controller.rb b/app/controllers/hyrax/data_sets_controller.rb
index 6639e787..7522aeb2 100644
--- a/app/controllers/hyrax/data_sets_controller.rb
+++ b/app/controllers/hyrax/data_sets_controller.rb
@@ -7,9 +7,11 @@ class DataSetsController < DeepblueController
PARAMS_KEY = 'data_set'
include Deepblue::WorksControllerBehavior
+ include Datacore::DoiControllerBehavior
self.curation_concern_type = ::DataSet
self.show_presenter = Hyrax::DataSetPresenter
+ delegate :show_presenter, to: :class
before_action :assign_date_coverage, only: %i[create update]
before_action :assign_admin_set, only: %i[create update]
@@ -94,47 +96,6 @@ def assign_admin_set
# end date_coverage
- ## DOI
-
- def doi
- doi_mint
- respond_to do |wants|
- wants.html { redirect_to [main_app, curation_concern] }
- wants.json do
- render :show,
- status: :ok,
- location: polymorphic_path([main_app, curation_concern])
- end
- end
- end
-
- def doi_minting_enabled?
- ::Deepblue::DoiBehavior::DOI_MINTING_ENABLED
- end
-
- def doi_mint
- # Do not mint doi if
- # one already exists
- # work file_set count is 0.
- if curation_concern.doi_pending?
- flash[:notice] = MsgHelper.t( 'data_set.doi_is_being_minted' )
- elsif curation_concern.doi_minted?
- flash[:notice] = MsgHelper.t( 'data_set.doi_already_exists' )
- elsif curation_concern.file_sets.count < 1
- flash[:notice] = MsgHelper.t( 'data_set.doi_requires_work_with_files' )
- elsif ( curation_concern.depositor != current_user.email ) && !current_ability.admin?
- flash[:notice] = MsgHelper.t( 'data_set.doi_user_without_access' )
- elsif curation_concern.doi_mint( current_user: current_user, event_note: 'DataSetsController' )
- flash[:notice] = MsgHelper.t( 'data_set.doi_minting_started' )
- end
- end
-
- # def mint_doi_enabled?
- # true
- # end
-
- ## end DOI
-
## Globus
def globus_add_email
diff --git a/app/controllers/hyrax/deepblue_controller.rb b/app/controllers/hyrax/deepblue_controller.rb
index 9899652a..38cb6450 100644
--- a/app/controllers/hyrax/deepblue_controller.rb
+++ b/app/controllers/hyrax/deepblue_controller.rb
@@ -17,17 +17,13 @@ def display_provenance_log_enabled?
end
def doi_minting_enabled?
- false
+ ::Datacore::DoiMintingService.enabled?
end
def globus_download_enabled?
false
end
- # def mint_doi_enabled?
- # false
- # end
-
def tombstone_enabled?
false
end
diff --git a/app/controllers/hyrax/file_sets_controller.rb b/app/controllers/hyrax/file_sets_controller.rb
index eb432d0c..b3fcea49 100644
--- a/app/controllers/hyrax/file_sets_controller.rb
+++ b/app/controllers/hyrax/file_sets_controller.rb
@@ -9,6 +9,7 @@ class FileSetsController < ApplicationController
PARAMS_KEY = 'file_set'
self.show_presenter = Hyrax::DsFileSetPresenter
+ delegate :show_presenter, to: :class
alias_method :monkey_attempt_update, :attempt_update
# alias_method :monkey_update_metadata, :update_metadata
@@ -103,10 +104,6 @@ def presenter
end
end
- def show_presenter
- Hyrax::DsFileSetPresenter
- end
-
def search_result_document( search_params )
_, document_list = search_results( search_params )
return document_list.first unless document_list.empty?
diff --git a/app/indexers/data_set_indexer.rb b/app/indexers/data_set_indexer.rb
index 1e2425e1..7d8503f5 100644
--- a/app/indexers/data_set_indexer.rb
+++ b/app/indexers/data_set_indexer.rb
@@ -28,7 +28,7 @@ def generate_solr_document
# solr_doc['member_of_collection_ids_ssim'] = object.member_of_collections.map(&:id)
solr_doc[Solrizer.solr_name('creator_ordered', :stored_searchable)] = object.creator_ordered
- solr_doc[Solrizer.solr_name('doi', :symbol)] = object.doi
+ solr_doc['doi_ssi'] = object.doi # FIXME: fix tesim version getting created
value = Array( object.referenced_by ).join( " " )
solr_doc[Solrizer.solr_name('referenced_by', :stored_searchable)] = value
diff --git a/app/jobs/doi_minting_job.rb b/app/jobs/doi_minting_job.rb
index 69271ce0..9fd230b8 100644
--- a/app/jobs/doi_minting_job.rb
+++ b/app/jobs/doi_minting_job.rb
@@ -4,53 +4,30 @@ class DoiMintingJob < ::Hyrax::ApplicationJob
queue_as :doi_minting
- def perform( id, current_user: nil, job_delay: 0 )
- Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here,
- Deepblue::LoggingHelper.called_from,
- "work.id=#{id}",
- "current_user=#{current_user}",
- "job_delay=#{job_delay}"]
- if 0 < job_delay
- return unless ActiveFedora::Base.find( id ).doi_pending?
- Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here,
- Deepblue::LoggingHelper.called_from,
- "work.id=#{id}",
- "current_user=#{current_user}",
- "sleeping #{job_delay} seconds"]
- sleep job_delay
+ def perform(id, current_user: nil, job_delay: 0)
+ sleep(job_delay) if job_delay > 0
+ work = ActiveFedora::Base.find(id)
+ unless work.doi_pending? && work.valid?
+ Rails.logger.error "DoiMintingJob called on #{'invalid ' unless work.valid?}work (#{work.id})#{' in invalid doi state (' + work.doi.to_s + ')' unless work.doi_pending?}, aborting"
+ return
end
- work = ActiveFedora::Base.find( id )
- Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here,
- Deepblue::LoggingHelper.called_from,
- "work.id=#{id}",
- Deepblue::LoggingHelper.obj_class( "work", work ),
- "work.doi=#{work.doi}",
- "work.doi_pending?=#{work.doi_pending?}"]
- return unless work.doi_pending?
current_user = work.depositor if current_user.blank?
- user = User.find_by_user_key( current_user )
- Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here,
- Deepblue::LoggingHelper.called_from,
- "work.id=#{id}",
- "user.email=#{user.email}",
- "Starting..." ]
- # Rails.logger.debug "DoiMintingJob work id #{id} #{user.email} starting..."
- if Deepblue::DoiMintingService.mint_doi_for( work: work, current_user: current_user )
- Rails.logger.debug "DoiMintingJob work id #{id} #{user.email} succeeded."
+ if Datacore::DoiMintingService.mint_doi_for(work: work, current_user: current_user)
+ Rails.logger.debug "DoiMintingJob work id #{id} #{current_user} succeeded."
# do success callback
- if Hyrax.config.callback.set?( :after_doi_success )
- Hyrax.config.callback.run( :after_doi_success, work, user, log.created_at )
+ if Hyrax.config.callback.set?(:after_doi_success)
+ Hyrax.config.callback.run(:after_doi_success, work, user, log.created_at)
end
else
- Rails.logger.debug "DoiMintingJob work id #{id} #{user.email} failed."
+ Rails.logger.debug "DoiMintingJob work id #{id} #{current_user} failed."
# do failure callback
- if Hyrax.config.callback.set?( :after_doi_failure )
- Hyrax.config.callback.run( :after_doi_failure, work, user, log.created_at )
+ if Hyrax.config.callback.set?(:after_doi_failure)
+ Hyrax.config.callback.run(:after_doi_failure, work, user, log.created_at)
end
end
+ true
rescue Exception => e # rubocop:disable Lint/RescueException
Rails.logger.error "DoiMintingJob.perform(#{id},#{job_delay}) #{e.class}: #{e.message} at #{e.backtrace[0]}"
- raise
+ false
end
-
end
diff --git a/app/models/concerns/deepblue/doi_behavior.rb b/app/models/concerns/deepblue/doi_behavior.rb
index c5e2e948..6763a754 100644
--- a/app/models/concerns/deepblue/doi_behavior.rb
+++ b/app/models/concerns/deepblue/doi_behavior.rb
@@ -1,65 +1,53 @@
# frozen_string_literal: true
module Deepblue
-
- class DoiError < RuntimeError
- end
-
module DoiBehavior
- DOI_MINTING_ENABLED = true
DOI_PENDING = 'doi_pending'
DOI_MINIMUM_FILE_COUNT = 1
def doi_minted?
- !doi.nil?
- rescue
- nil
+ doi.present? && !doi_pending?
end
def doi_minting_enabled?
- ::Deepblue::DoiBehavior::DOI_MINTING_ENABLED
+ ::Datacore::DoiMintingService.enabled?
end
def doi_pending?
doi == DOI_PENDING
end
- def doi_mint( current_user: nil, event_note: '', enforce_minimum_file_count: true, job_delay: 0 )
- Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here,
- Deepblue::LoggingHelper.called_from,
- "work.id=#{id}",
- "doi=#{doi}",
- "current_user=#{current_user}",
- "event_note=#{event_note}",
- "enforce_minimum_file_count=#{enforce_minimum_file_count}",
- "job_delay=#{job_delay}" ]
- return false if doi_pending?
- # Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here,
- # Deepblue::LoggingHelper.called_from,
- # "work.id=#{id}",
- # "past doi_pending?" ]
- return false if doi_minted?
- # Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here,
- # Deepblue::LoggingHelper.called_from,
- # "work.id=#{id}",
- # "past doi_minted?" ]
- return false if enforce_minimum_file_count && file_sets.count < DOI_MINIMUM_FILE_COUNT
- self.doi = DOI_PENDING
- self.save
- self.reload
- current_user = current_user.email if current_user.respond_to? :email
- Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here,
- Deepblue::LoggingHelper.called_from,
- "work.id=#{id}",
- "doi=#{doi}",
- "about to call DoiMintingJob" ]
- ::DoiMintingJob.perform_later( id, current_user: current_user, job_delay: job_delay )
- return true
+ def doi_minimum_files?
+ file_sets.count >= DOI_MINIMUM_FILE_COUNT
+ end
+
+ def doi_mint(current_user: nil, event_note: '', enforce_minimum_file_count: true, job_delay: 0 )
+ if !doi_minting_enabled?
+ Rails.logger.warn "DoiBehavior.doi_mint called for curation_concern.id #{id} with minting disabled"
+ return false
+ elsif invalid?
+ Rails.logger.warn "DoiBehavior.doi_mint called for curation_concern.id #{id} with invalid metadata: #{self.errors.full_messages}"
+ return false
+ elsif doi_pending?
+ Rails.logger.warn "DoiBehavior.doi_mint called for curation_concern.id #{id} with pending doi"
+ return false
+ elsif doi_minted?
+ Rails.logger.warn "DoiBehavior.doi_mint called for curation_concern.id #{id} with minted doi (#{doi})"
+ return false
+ elsif enforce_minimum_file_count && !doi_minimum_files?
+ Rails.logger.warn "DoiBehavior.doi_mint called for curation_concern.id #{id} with insufficient FileSet count (#{file_sets.count})"
+ return false
+ else
+ self.doi = DOI_PENDING
+ self.save
+ self.reload
+ ::DoiMintingJob.perform_later(id, current_user: current_user&.try(:email), job_delay: job_delay)
+ Rails.logger.info "DoiBehavior.doi_mint called DoiMintingJob for curation_concern.id #{id}"
+ return true
+ end
rescue Exception => e # rubocop:disable Lint/RescueException
Rails.logger.error "DoiBehavior.doi_mint for curation_concern.id #{id} -- #{e.class}: #{e.message} at #{e.backtrace[0]}"
end
-
end
-
end
diff --git a/app/models/concerns/umrdr/solr_document_behavior.rb b/app/models/concerns/umrdr/solr_document_behavior.rb
index c7f8c0f1..641420b8 100644
--- a/app/models/concerns/umrdr/solr_document_behavior.rb
+++ b/app/models/concerns/umrdr/solr_document_behavior.rb
@@ -36,43 +36,15 @@ def date_published2
## begin DOI methods
def doi
- rv = doi_the_correct_one
- # rv = Array( self[ Solrizer.solr_name( 'doi', :symbol ) ] ).first
- # rv = self[ Solrizer.solr_name( 'doi', :symbol ) ]
- # ::Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here,
- # Deepblue::LoggingHelper.called_from,
- # Deepblue::LoggingHelper.obj_class( 'class', self ),
- # "doi = #{doi}",
- # "" ]
- return rv
- end
-
- def doi_the_correct_one
- # rv = Array( self[Solrizer.solr_name('doi')] ).first
- rv = self[ Solrizer.solr_name( 'doi', :symbol ) ]
- # ::Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here,
- # Deepblue::LoggingHelper.called_from,
- # Deepblue::LoggingHelper.obj_class( 'class', self ),
- # "doi = #{doi}",
- # "" ]
- return rv
+ fetch('doi_ssi', nil)
end
def doi_minted?
- # the first time this is called, doi will not be in solr.
- # @solr_document[ Solrizer.solr_name( 'doi', :symbol ) ].first
- doi_the_correct_one.present?
- rescue
- nil
- end
-
- def doi_minting_enabled?
- ::Deepblue::DoiBehavior::DOI_MINTING_ENABLED
+ doi.present? && !doi_pending?
end
def doi_pending?
- #@solr_document[ Solrizer.solr_name( 'doi', :symbol ) ].first == ::Deepblue::DoiBehavior::DOI_PENDING
- doi_the_correct_one == ::Deepblue::DoiBehavior::DOI_PENDING
+ doi == ::Deepblue::DoiBehavior::DOI_PENDING
end
## end DOI methods
diff --git a/app/models/solr_document.rb b/app/models/solr_document.rb
index 5e57e1a9..1010ffa5 100644
--- a/app/models/solr_document.rb
+++ b/app/models/solr_document.rb
@@ -71,6 +71,7 @@ def degree_grantors_label
self['degree_grantors_label_ssim']
end
+ # FIXME: drop?
def doi_label
self['doi_label_ssim']
end
@@ -201,7 +202,7 @@ def license_other
'description_thesisdegreegrantor',
'description_thesisdegreename',
'digitization_spec',
- 'doi',
+ # 'doi',
'dspace_collection',
'dspace_community',
'duration',
diff --git a/app/presenters/hyrax/data_set_presenter.rb b/app/presenters/hyrax/data_set_presenter.rb
index 70b90736..7602c9a4 100644
--- a/app/presenters/hyrax/data_set_presenter.rb
+++ b/app/presenters/hyrax/data_set_presenter.rb
@@ -9,9 +9,8 @@ class DataSetPresenter < DeepbluePresenter
:curation_notes_user,
:date_coverage,
:date_published, :date_published2, # FIXME; investigate
- :doi, :doi_the_correct_one, # FIXME: investigate
+ :doi,
:doi_minted?,
- :doi_minting_enabled?,
:doi_pending?,
:fundedby,
:fundedby_other,
@@ -47,28 +46,6 @@ class DataSetPresenter < DeepbluePresenter
:type_none,
to: :solr_document
- # def initialize( solr_document, current_ability, request = nil )
- # ::Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here,
- # Deepblue::LoggingHelper.called_from,
- # Deepblue::LoggingHelper.obj_class( 'class', self ),
- # "solr_document = #{solr_document}",
- # "solr_document.class.name = #{solr_document.class.name}",
- # "current_ability = #{current_ability}",
- # "request = #{request}",
- # "" ]
- # super( solr_document, current_ability, request )
- # ::Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here,
- # Deepblue::LoggingHelper.called_from,
- # Deepblue::LoggingHelper.obj_class( 'class', self ),
- # "@solr_document.class.name = #{@solr_document.class.name}",
- # "@solr_document.doi = #{@solr_document.doi}",
- # "@solr_document.doi_the_correct_one = #{@solr_document.doi_the_correct_one}",
- # "@solr_document.doi_minted? = #{@solr_document.doi_minted?}",
- # "@solr_document.doi_minting_enabled? = #{@solr_document.doi_minting_enabled?}",
- # "@solr_document.doi_pending? = #{@solr_document.doi_pending?}",
- # "" ]
- # end
-
# begin box
def box_enabled?
@@ -113,30 +90,6 @@ def provenance_log_entries?
# end display_provenance_log
- # begin doi
- #
- # def doi
- # solr_value = @solr_document[Solrizer.solr_name('doi', :symbol)]
- # return nil if solr_value.blank?
- # solr_value.first
- # end
- #
- # def doi_minted?
- # !doi.nil?
- # rescue
- # nil
- # end
- #
- # def doi_pending?
- # doi == ::Deepblue::DoiBehavior::DOI_PENDING
- # end
- #
- # def mint_doi_enabled?
- # true
- # end
- #
- # end doi
-
# begin globus
def globus_download_enabled?
diff --git a/app/presenters/hyrax/deepblue_presenter.rb b/app/presenters/hyrax/deepblue_presenter.rb
index efe7899f..1224c187 100644
--- a/app/presenters/hyrax/deepblue_presenter.rb
+++ b/app/presenters/hyrax/deepblue_presenter.rb
@@ -13,7 +13,7 @@ def display_provenance_log_enabled?
end
def doi_minting_enabled?
- false
+ ::Datacore::DoiMintingService.enabled?
end
def globus_download_enabled?
@@ -24,10 +24,6 @@ def human_readable_type
"Work"
end
- # def mint_doi_enabled?
- # false
- # end
-
# def tombstone_enabled?
# false
# end
@@ -35,7 +31,5 @@ def human_readable_type
def zip_download_enabled?
false
end
-
end
-
end
diff --git a/app/presenters/hyrax/ds_file_set_presenter.rb b/app/presenters/hyrax/ds_file_set_presenter.rb
index 39693def..573edce1 100644
--- a/app/presenters/hyrax/ds_file_set_presenter.rb
+++ b/app/presenters/hyrax/ds_file_set_presenter.rb
@@ -4,11 +4,7 @@ module Hyrax
class DsFileSetPresenter < Hyrax::FileSetPresenter
include ::Datacore::PresentsArchiveFile
- delegate :doi, :doi_the_correct_one,
- :doi_minted?,
- :doi_minting_enabled?,
- :doi_pending?,
- :file_size,
+ delegate :file_size,
:file_size_human_readable,
:original_checksum,
:mime_type,
@@ -17,28 +13,12 @@ class DsFileSetPresenter < Hyrax::FileSetPresenter
:virus_scan_status,
:virus_scan_status_date, to: :solr_document
- # def doi_minted?
- # # the first time this is called, doi will not be in solr.
- # @solr_document[ Solrizer.solr_name( 'doi', :symbol ) ].first
- # rescue
- # nil
- # end
- #
- # def doi_pending?
- # @solr_document[ Solrizer.solr_name( 'doi', :symbol ) ].first == ::Deepblue::DoiBehavior::DOI_PENDING
- # end
-
def relative_url_root
rv = ::DeepBlueDocs::Application.config.relative_url_root
return rv if rv
''
end
- def parent_doi_minted?
- g = DataSet.find parent.id
- g.doi_minted?
- end
-
# begin display_provenance_log
def display_provenance_log_enabled?
diff --git a/app/presenters/hyrax/work_show_presenter.rb b/app/presenters/hyrax/work_show_presenter.rb
index 3face10c..953ca0e0 100644
--- a/app/presenters/hyrax/work_show_presenter.rb
+++ b/app/presenters/hyrax/work_show_presenter.rb
@@ -1,11 +1,16 @@
# frozen_string_literal: true
+# FIXME: confirm line below needed
require File.join(Gem::Specification.find_by_name("hyrax").full_gem_path, "app/presenters/hyrax/work_show_presenter.rb")
# monkey patch Hyrax::WorkShowPresenter
module Hyrax
class WorkShowPresenter
+ # FIXME: check whether fileset presenter should be using this class for parent?
+ delegate :doi,
+ :doi_minted?,
+ :doi_pending?, to: :solr_document
def relative_url_root
rv = ::DeepBlueDocs::Application.config.relative_url_root
diff --git a/app/renderers/hyrax/renderers/doi_attribute_renderer.rb b/app/renderers/hyrax/renderers/doi_attribute_renderer.rb
new file mode 100644
index 00000000..5d89cbc3
--- /dev/null
+++ b/app/renderers/hyrax/renderers/doi_attribute_renderer.rb
@@ -0,0 +1,28 @@
+module Hyrax
+ module Renderers
+
+ # This is used by PresentsAttributes to show DOIs
+ # e.g.: presenter.attribute_to_html(:doi, render_as: :doi)
+ class DoiAttributeRenderer < ExternalLinkAttributeRenderer
+ private
+
+ ##
+ # Special treatment for DOI values. A DOI could be stored as:
+ # 'doi:10.5967/56ck-gp62'
+ # '10.5967/56ck-gp62'
+ # but should be rendered as:
+ # 'https://doi.org/10.5967/56ck-gp62'
+ def attribute_value_to_html(value)
+ case value
+ when /^doi:/
+ value.sub!('doi:', 'https://doi.org/')
+ when /^10\./
+ value = "https://doi.org/#{value}"
+ when ::Deepblue::DoiBehavior::DOI_PENDING
+ # FIXME: display differently?
+ end
+ super
+ end
+ end
+ end
+end
diff --git a/app/services/datacore/doi_minting_service.rb b/app/services/datacore/doi_minting_service.rb
new file mode 100644
index 00000000..410610a6
--- /dev/null
+++ b/app/services/datacore/doi_minting_service.rb
@@ -0,0 +1,180 @@
+# frozen_string_literal: true
+
+module Datacore
+
+ class DoiMintingService
+
+ PUBLISHER = "Indiana University".freeze
+ RESOURCE_TYPE = "Dataset".freeze
+
+ attr_reader :current_user, :work, :metadata, :prefix, :doi
+
+ # @return Boolean
+ def self.enabled?
+ Settings.datacite.enabled
+ end
+ delegate :enabled?, to: :class
+
+ # @param work [DataSet] Dataset getting the DOI minted
+ # @param current_user [String] email for user requesting minting
+ # @return [String, nil] returns DOI on successful minting, nil on failure or error
+ def self.mint_doi_for(work:, current_user:)
+ Datacore::DoiMintingService.new(work: work, current_user: current_user).run if enabled?
+ end
+
+ def initialize(work:, current_user:)
+ @work = work
+ @current_user = current_user
+ @metadata = local_metadata
+ @prefix = Settings.datacite.prefix&.to_s # cast inadvertent Float to String
+ @doi = work.doi
+ end
+
+ # @return [String] id portion of doi without "doi:" namespace or url portion; used by API calls
+ def id
+ doi.to_s.sub(/^doi:/, '').sub('https://doi.org/', '')
+ end
+
+ # @return [String, nil] DOI on successful minting, nil on failure or error
+ def run
+ return unless enabled?
+ if work.valid? && work.doi_pending?
+ doi = mint_doi!
+ if doi
+ update_work_with_doi!(doi, update_provenance: true)
+ else
+ failed_minting!
+ end
+ else
+ Rails.logger.error "DoiMintingService called for work (#{work.id}) with invalid state (#{!work.valid?}) or doi value (#{work.doi})"
+ false
+ end
+ end
+
+ private
+ # @return [nil]
+ # logs failure, updates work DOI nil
+ def failed_minting!
+ Rails.logger.error "DoiMintingService failure for: work: #{work.id}, user: #{current_user}, nullifying doi value"
+ update_work_with_doi!(nil)
+ end
+
+ # @return [String, nil] work's DOI
+ # updates work DOI
+ # conditionally logs minting to provenance
+ def update_work_with_doi!(doi, update_provenance: false)
+ work.reload # consider locking work
+ if work.doi == doi
+ @doi = doi
+ else
+ work.doi = doi
+ work.save
+ work.reload
+ work.provenance_mint_doi(current_user: current_user, event_note: 'DoiMintingService') if update_provenance
+ @doi = work.doi
+ end
+ @doi
+ end
+
+ # @return [Hash] work minimal metadata for remote record creation or update
+ def local_metadata
+ { creators: work.creator.map { |c| { name: c } },
+ titles: work.title.map { |t| { title: t } },
+ publisher: PUBLISHER,
+ publicationYear: Date.today.year.to_s,
+ types: { resourceTypeGeneral: RESOURCE_TYPE },
+ url: Rails.application.routes.url_helpers.hyrax_data_set_url(id: work.id)
+ }
+ end
+
+ # @return [DataCite::Client] client instance for Datacite interactions
+ def client(host: Settings.datacite.host, username: Settings.datacite.username, password: Settings.datacite.password)
+ @client ||= Datacite::Client.new(host: host, username: username, password: password)
+ end
+
+ # client interaction methods below
+
+ # @return [String, nil] DOI value on successful minting, nil on failure or error
+ def mint_doi!(suffix: nil)
+ begin
+ if suffix.present?
+ result = client.register_doi(prefix: prefix, suffix: suffix, metadata: metadata)
+ else
+ result = client.autogenerate_doi(prefix: prefix, metadata: metadata)
+ end
+ if result.success?
+ "doi:#{result.value!.doi}"
+ else
+ Rails.logger.error("API failure in DoiMintingService#mint_doi!: #{result.inspect}")
+ nil
+ end
+ rescue => e
+ Rails.logger.error("Error in DoiMintingService#mint_doi!: #{e.inspect}")
+ nil
+ end
+ end
+
+ # developer client methods below, not currently called
+
+ # @return [Boolean, nil] true if DOI found remotely, false if not, nil on failure or error
+ def doi_exists?
+ return unless work.doi_minted?
+ begin
+ result = client.exists?(id: id)
+ if result.success?
+ result.value!
+ else
+ Rails.logger.error("API failure in DoiMintingService#doi_exists?: #{result.inspect}")
+ nil
+ end
+ rescue => e
+ Rails.logger.error("Error in DoiMintingService#doi_exists?: #{e.inspect}")
+ nil
+ end
+ end
+
+ # @return [Hash, nil] remote metadata Hash if found for DOI, nil on failure or error
+ def doi_metadata
+ return nil unless doi_exists?
+ begin
+ result = client.metadata(id: id)
+ if result.success?
+ result.value!
+ else
+ Rails.logger.error("API failure in DoiMintingService#doi_metadata: #{result.inspect}")
+ nil
+ end
+ rescue => e
+ Rails.logger.error("Error in DoiMintingService#doi_metadata: #{e.inspect}")
+ nil
+ end
+ end
+
+ # @return [String] remote metadata XML if found for DOI, blank string for failure or error
+ def doi_xml
+ begin
+ Base64.decode64(doi_metadata&.dig('data', 'attributes', 'xml')&.to_s)
+ rescue => e
+ Rails.logger.error("Error in DoiMintingService#doi_xml: #{e.inspect}")
+ nil
+ end
+ end
+
+ # @return [String, nil] returns DOI on successful remote metadata update, nil on failure or error
+ def update_metadata!
+ return nil unless doi_exists?
+ begin
+ result = client.update(id: id, attributes: metadata)
+ if result.success?
+ "doi:#{result.value!.doi}"
+ else
+ Rails.logger.error("API failure in DoiMintingService#update_metadata!: #{result.inspect}")
+ nil
+ end
+ rescue => e
+ Rails.logger.error("Error in DoiMintingService#update_metadata!: #{e.inspect}")
+ nil
+ end
+ end
+ end
+end
diff --git a/app/services/deepblue/doi_minting_service.rb b/app/services/deepblue/doi_minting_service.rb
deleted file mode 100644
index ccf759e2..00000000
--- a/app/services/deepblue/doi_minting_service.rb
+++ /dev/null
@@ -1,119 +0,0 @@
-# frozen_string_literal: true
-
-module Deepblue
-
- class DoiMintingService
-
- PUBLISHER = "Indiana University".freeze
- RESOURCE_TYPE = "Dataset".freeze
-
- attr :current_user, :work, :metadata
-
- def self.mint_doi_for( work:, current_user: )
- Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here,
- Deepblue::LoggingHelper.called_from,
- "work.id=#{work.id}" ]
- service = Deepblue::DoiMintingService.new( work: work, current_user: current_user )
- Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here,
- Deepblue::LoggingHelper.called_from,
- "work.id=#{work.id}",
- "about to call service.run" ]
- service.run
- rescue Exception => e # rubocop:disable Lint/RescueException
- Rails.logger.debug "DoiMintingService.mint_doi_for( work id = #{work.id}, current_user = #{current_user} ) rescue exception -- Exception: #{e.class}: #{e.message} at #{e.backtrace[0]}"
- unless work.nil?
- work.reload # consider locking work
- work.doi = nil
- work.save
- work.reload
- work.doi
- end
- raise
- end
-
- def initialize( work:, current_user: )
- Rails.logger.debug "DoiMintingService.initalize( work id = #{work.id} )"
- @work = work
- @current_user = current_user
- @metadata = generate_metadata
- end
-
- def run
- Rails.logger.debug "DoiMintingService.run( work id = #{work.id} )"
- rv = doi_server_reachable?
- Rails.logger.debug "DoiMintingService.run doi_server_reachable?=#{rv}"
- return mint_doi_failed unless rv
- work.reload # consider locking work
- work.doi = mint_doi
- work.save
- work.reload
- work.provenance_mint_doi( current_user: current_user, event_note: 'DoiMintingService' )
- work.doi
- end
-
- def self.print_ezid_config
- config = Ezid::Client.config
- puts "Ezid::Client.config.host = #{config.host}"
- puts "Ezid::Client.config.port = #{config.port}"
- puts "Ezid::Client.config.user = #{config.user}"
- puts "Ezid::Client.config.password = #{config.password}"
- puts "Ezid::Client.config.default_shoulder = #{config.default_shoulder}"
- end
-
- def ezid_config
- config = Ezid::Client.config
- return [ "Ezid::Client.config.host = #{config.host}",
- "Ezid::Client.config.port = #{config.port}",
- "Ezid::Client.config.user = #{config.user}",
- # "Ezid::Client.config.password = #{config.password}",
- "Ezid::Client.config.default_shoulder = #{config.default_shoulder}" ]
- end
-
- private
-
- # Any error raised during connection is considered false
- def doi_server_reachable?
- Ezid::Client.new.server_status.up? rescue false
- end
-
- def generate_metadata
- Ezid::Metadata.new.tap do |md|
- md.datacite_title = work.title.first
- md.datacite_publisher = PUBLISHER
- md.datacite_publicationyear = Date.today.year.to_s
- md.datacite_resourcetype= RESOURCE_TYPE
- md.datacite_creator=work.creator.join(';')
- md.target = Rails.application.routes.url_helpers.hyrax_data_set_url(id: work.id)
- end
- end
-
- def mint_doi
- # identifier = Ezid::Identifier.create(@metadata)
- Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here,
- Deepblue::LoggingHelper.called_from,
- "work.id=#{work.id}",
- "metadata=#{metadata}" ]
-
- # Rails.logger.debug "DoiMintingService.mint_doi( #{metadata} )"
- # msg = ezid_config.join("\n")
- # Rails.logger.debug msg
- Deepblue::LoggingHelper.bold_debug [ Deepblue::LoggingHelper.here,
- Deepblue::LoggingHelper.called_from,
- "work.id=#{work.id}" ] + ezid_config
- shoulder = Ezid::Client.config.default_shoulder
- identifier = Ezid::Identifier.mint( shoulder, @metadata )
- identifier.id
- end
-
- def mint_doi_failed
- Rails.logger.error "DoiMintingService.mint_doi_failed work id = #{work.id}"
- work.reload # consider locking work
- work.doi = nil
- work.save
- work.reload
- work.doi
- end
-
- end
-
-end
diff --git a/app/views/hyrax/base/_attribute_rows.html.erb b/app/views/hyrax/base/_attribute_rows.html.erb
index a248b674..7da76b3d 100644
--- a/app/views/hyrax/base/_attribute_rows.html.erb
+++ b/app/views/hyrax/base/_attribute_rows.html.erb
@@ -98,12 +98,7 @@
<%= presenter.attribute_to_html(:geo_location_box, label: t('show.label.geo_location_box')) %>
<%= presenter.attribute_to_html(:related_url, render_as: :external_link) %>
<%= presenter.attribute_to_html(:source) %>
-
- <% unless presenter.doi_the_correct_one.nil? %>
- <% presenter.doi_the_correct_one[0].sub! 'doi:', 'https://doi.org/' %>
- <% end %>
- <%= presenter.attribute_to_html(:doi_the_correct_one, label: t('show.label.doi'), work_type: "DataSet") %>
-
+<%= presenter.attribute_to_html(:doi, render_as: :doi) %>
<%= presenter.rights_license[0] %>
diff --git a/app/views/hyrax/base/_edit_panel.html.erb b/app/views/hyrax/base/_edit_panel.html.erb
index 6371a0ca..5310044f 100644
--- a/app/views/hyrax/base/_edit_panel.html.erb
+++ b/app/views/hyrax/base/_edit_panel.html.erb
@@ -16,11 +16,16 @@
<% end %>
<% if @presenter.doi_minting_enabled? && !@presenter.doi_minted? %>
-
-
+ <% if @presenter.doi_pending? %>
+
+
DOI minting is currently processing.
+ <% else %> + + <% end %><%= raw (t('simple_form.actions.data_set.mint_help', contact_path: hyrax.contact_path)) %>
<% end %> diff --git a/app/views/hyrax/file_sets/_show_actions.html.erb b/app/views/hyrax/file_sets/_show_actions.html.erb index 016d3fc3..2cd0f4e8 100644 --- a/app/views/hyrax/file_sets/_show_actions.html.erb +++ b/app/views/hyrax/file_sets/_show_actions.html.erb @@ -8,11 +8,18 @@ <% end %> <% if @presenter.parent %> - <% if ( ( @presenter.editor? && @presenter.parent.workflow.state != "deposited" ) || current_ability.admin? ) %> + <% if ((@presenter.editor? && @presenter.parent.workflow.state != "deposited" ) || current_ability.admin?) %> <%= link_to "Edit This #{@presenter.human_readable_type}", edit_polymorphic_path([main_app, @presenter]), class: 'btn btn-default' %> - <% if ( @presenter.parent_doi_minted? && !current_ability.admin? ) %> + + <% if (@presenter.parent.doi_minted? && !current_ability.admin?) %> +Parent work has a DOI (<%= @presenter.parent.doi %>) -- FileSet deletion is restricted to Admins
+ <%= link_to "Delete This #{@presenter.human_readable_type}", "#", + class: 'btn btn-danger', disabled: true %> <% else %> + <% if @presenter.parent.doi_minted? %> +Deletion warning: parent work has a DOI (<%= @presenter.parent.doi %>)
+ <% end %> <%= link_to "Delete This #{@presenter.human_readable_type}", [main_app, @presenter], class: 'btn btn-danger', data: { confirm: "Delete this #{@presenter.human_readable_type}?" }, method: :delete %> diff --git a/config/environments/development.rb b/config/environments/development.rb index 31f04043..5c5c16d9 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -62,6 +62,6 @@ # Use a real queuing backend for Active Job (needed for testing resque-scheduler) # this will force the jobs to be asynchronous (and does not work in dev due to a bug) - # config.active_job.queue_adapter = :resque + config.active_job.queue_adapter = :inline end diff --git a/config/initializers/ezid.rb b/config/initializers/ezid.rb deleted file mode 100644 index dbc71fea..00000000 --- a/config/initializers/ezid.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -Ezid::Client.configure do |config| - config.host = Settings.ezid.host - config.port = Settings.ezid.port - config.user = Settings.ezid.user - config.password = Settings.ezid.password - config.timeout = Settings.ezid.timeout - config.default_shoulder = Settings.ezid.shoulder -end diff --git a/config/locales/data_set.en.yml b/config/locales/data_set.en.yml index 9c872081..ccdfffce 100644 --- a/config/locales/data_set.en.yml +++ b/config/locales/data_set.en.yml @@ -4,10 +4,14 @@ en: "A DOI already exists for this work." doi_is_being_minted: "A DOI is currently being minted." - doi_job_started: - "DOI process kicked off for work id: %{id}" + doi_minting_disabled: + "DOI minting is disabled." + doi_minting_error: + "An error occurred requesting DOI minting." doi_minting_started: "DOI minting has started." + doi_requires_valid_work: + "DOI cannot be minted for a work with incomplete metadata." doi_requires_work_with_files: "DOI cannot be minted for a work without files." doi_user_without_access: diff --git a/config/locales/deepblue.en.yml b/config/locales/deepblue.en.yml index 0628b700..1f2f63b3 100644 --- a/config/locales/deepblue.en.yml +++ b/config/locales/deepblue.en.yml @@ -48,12 +48,6 @@ en: deepblue: "deepblue@umich.edu" generic_work: - doi_already_exists: - "A DOI already exists or is being minted." - doi_job_started: - "DOI process kicked off for work id: %{id}" - doi_requires_work_with_files: - "DOI cannot be minted for a work without files." globus_clean: "Files are being delete from %{dirs}" globus_clean_join_html: diff --git a/config/routes.rb b/config/routes.rb index 8b14d8ef..6694cfa1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -93,7 +93,6 @@ member do # post 'confirm' get 'display_provenance_log' - get 'doi' post 'doi' post 'globus_download' post 'globus_add_email' diff --git a/config/settings.yml b/config/settings.yml index 9c898167..74a7f40c 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -63,16 +63,13 @@ redis: port: 6379 thread_safe: true -# EZID Client configuration; see initializer/ezid.rb -# The doi:10.5072/FK2 shoulder is a defined temporary/testing namespace. +# Datacite client configuration # Set real values in settings.local.yml or local environment file. -ezid: - host: ez.test.datacite.org - user: eziduser.invalid - password: ezidpassword.invalid - shoulder: doi:10.5072/FK2 - port: 443 - timeout: 300 +datacite: + host: api.test.datacite.org + username: datacite.test.user + password: datacite.test.password + prefix: '10.5072' # force string interpretation jira: username: jirausername.invalid diff --git a/config/settings/production.yml b/config/settings/production.yml index d05a7ef7..d9c410c6 100644 --- a/config/settings/production.yml +++ b/config/settings/production.yml @@ -19,11 +19,13 @@ solr: redis: url: redis://redis/ -# The shoulder is made invalid here to avoid unintentionally creating -# identifiers in a test namespace and appearing to work if left unconfigured. +# Datacite client configuration +# Set real values in settings.production.local.yml or local environment file. ezid: - host: ez.datacite.org - shoulder: invalid:invalid + host: api.datacite.org + username: datacite.production.user + password: datacite.production.password + prefix: invalid jira: username: jirausername.invalid diff --git a/config/settings/test.yml b/config/settings/test.yml index 9206cd4e..afccdacb 100644 --- a/config/settings/test.yml +++ b/config/settings/test.yml @@ -1,4 +1,4 @@ -hostname: 'test.datacaore.iu.edu' +hostname: 'test.datacore.iu.edu' hyrax: redis_namespace: 'datacore-test' @@ -30,3 +30,6 @@ rails: database: db/test.sqlite3 timeout: 10000 secret_key_base: a8e4aa45a4953ee0263a0df4a33bd051b4db4503d96f93db838730ff93ed413d62dbb6f2feb82ec902d135799e7faf44b815726dc491f5cc3131db394fd2259d + +rspec: + aggressive_cleaning: false # set to true for local development as needed diff --git a/spec/controllers/hyrax/data_sets_controller_spec.rb b/spec/controllers/hyrax/data_sets_controller_spec.rb index 5dbeb728..f24bad85 100644 --- a/spec/controllers/hyrax/data_sets_controller_spec.rb +++ b/spec/controllers/hyrax/data_sets_controller_spec.rb @@ -1,38 +1,100 @@ require 'rails_helper' -RSpec.describe Hyrax::DataSetsController do # rubocop:disable RSpec/EmptyExampleGroup - - # before(:all ) do - # puts "DataSet ids before=#{DataSet.all.map { |ds| ds.id }}" - # #puts "FileSet ids before=#{FileSet.all.map { |fs| fs.id }}" - # end - # - # after(:all ) do - # #puts "FileSet ids after=#{FileSet.all.map { |fs| fs.id }}" - # puts "DataSet ids after=#{DataSet.all.map { |ds| ds.id }}" - # # clean up created DataSet - # DataSet.all.each { |ds| ds.delete } - # #FileSet.all.each { |fs| fs.delete } - # end - - include Devise::Test::ControllerHelpers - routes { Rails.application.routes } +RSpec.describe Hyrax::DataSetsController do + render_views let(:main_app) { Rails.application.routes.url_helpers } let(:hyrax) { Hyrax::Engine.routes.url_helpers } - let(:user) { create(:user) } + let(:user) { FactoryBot.create(:admin) } # FIXME: enable access for depositor? + let(:pending_doi) { Deepblue::DoiBehavior::DOI_PENDING } + let(:minted_doi) { 'doi:10.82028/18sn-h641' } + let(:data_set) { FactoryBot.create(:data_set, user: user, doi: nil) } + let(:data_set_with_one_file) { FactoryBot.create(:data_set_with_one_file, user: user, doi: nil) } before do sign_in user end - context 'someone elses private work' do # rubocop:disable RSpec/EmptyExampleGroup - # let(:work) { create(:private_data_set) } - # - # it 'shows unauthorized message' do - # get :show, params: { id: work } - # expect(response.code).to eq '401' - # expect(response).to render_template(:unauthorized) - # end + describe "#show" do + it "renders show page" do + get :show, params: { id: data_set.id } + expect(response).to render_template(:show) + end end + shared_examples "doi_mint! behavior" do |flash_type, flash_match| + it "redirects to the work" do + expect(response).to redirect_to(main_app.hyrax_data_set_path(data_set, locale: :en)) + end + it "flashes :#{flash_type} message matching #{flash_match}" do + expect(flash[flash_type]).to be_a String + expect(flash[flash_type]).to match flash_match + end + end + + describe "#doi" do + before(:each) do + allow(Datacore::DoiMintingService).to receive(:enabled?).and_return(true) + end + context "when minting is disabled" do + before(:each) do + allow(Datacore::DoiMintingService).to receive(:enabled?).and_return(false) + post :doi, params: { id: data_set.id } + end + include_examples "doi_mint! behavior", :alert, /disabled/ + end + context "when minting is in progress" do + before(:each) do + data_set.doi = pending_doi; data_set.save! + post :doi, params: { id: data_set.id } + end + include_examples "doi_mint! behavior", :notice, /currently/ + end + context "when minting is already done" do + before(:each) do + data_set.doi = minted_doi; data_set.save! + post :doi, params: { id: data_set.id } + end + include_examples "doi_mint! behavior", :alert, /already/ + end + context "when missing files" do + before(:each) do + expect(data_set.file_sets.count).to eq 0 + post :doi, params: { id: data_set.id } + end + include_examples "doi_mint! behavior", :alert, /files/ + end + context "when invalid metadata" do + let(:data_set) { data_set_with_one_file } + before(:each) do + data_set.title = nil; data_set.save(validate: false) + post :doi, params: { id: data_set.id } + end + include_examples "doi_mint! behavior", :alert, /metadata/ + end + context "when user lacks rights", skip: 'authorization check prevents condition from arising' do + let(:data_set) { data_set_with_one_file } + before(:each) do + allow_any_instance_of(Ability).to receive(:admin?).and_return(false) + post :doi, params: { id: data_set.id } + end + include_examples "doi_mint! behavior", :alert, /access/ + end + context "when user has rights" do + let(:data_set) { data_set_with_one_file } + context "and minting succeeds" do + before(:each) do + allow_any_instance_of(data_set.class).to receive(:doi_mint).and_return(true) + post :doi, params: { id: data_set.id } + end + include_examples "doi_mint! behavior", :notice, /started/ + end + context "and minting fails" do + before(:each) do + allow_any_instance_of(data_set.class).to receive(:doi_mint).and_return(false) + post :doi, params: { id: data_set.id } + end + include_examples "doi_mint! behavior", :error, /error/ + end + end + end end diff --git a/spec/controllers/hyrax/deepblue_controller_spec.rb b/spec/controllers/hyrax/deepblue_controller_spec.rb index 962eee25..91daeeb3 100644 --- a/spec/controllers/hyrax/deepblue_controller_spec.rb +++ b/spec/controllers/hyrax/deepblue_controller_spec.rb @@ -15,8 +15,8 @@ end describe "#doi_minting_enabled?" do - it "returns false" do - expect(subject.doi_minting_enabled?).to eq false + it "returns service value (#{Datacore::DoiMintingService.enabled?}" do + expect(subject.doi_minting_enabled?).to eq Datacore::DoiMintingService.enabled? end end diff --git a/spec/factories/data_sets.rb b/spec/factories/data_sets.rb index 609dce85..ce1a3ada 100644 --- a/spec/factories/data_sets.rb +++ b/spec/factories/data_sets.rb @@ -2,6 +2,14 @@ FactoryBot.define do factory :data_set, aliases: [:data_set_work], class: ::DataSet do + title { ["Test title"] } + visibility { Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_PRIVATE } + authoremail { "test@iu.edu" } + description { ["This is the description."] } + methodology { "The Methodology" } + creator { ['creator1'] } + rights_license { "http://creativecommons.org/publicdomain/zero/1.0/" } + rights_statement { ["http://rightsstatements.org/vocab/NKC/1.0/"] } transient do user { create(:user) } @@ -25,13 +33,6 @@ work.save! if work.member_of_collections.present? end - title { ["Test title"] } - visibility { Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_PRIVATE } - - authoremail { "test@umich.edu" } - description { ["This is the description."] } - methodology { "The Methodology" } - after(:build) do |work, evaluator| work.apply_depositor_metadata(evaluator.user.user_key) end diff --git a/spec/jobs/doi_minting_job_spec.rb b/spec/jobs/doi_minting_job_spec.rb new file mode 100644 index 00000000..ca64c313 --- /dev/null +++ b/spec/jobs/doi_minting_job_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +describe DoiMintingJob do + let(:current_user) { 'user@example.com' } + let(:service) { Datacore::DoiMintingService.new(current_user: current_user, work: work) } + let(:work) { FactoryBot.create(:data_set, creator: ['creator'], rights_license: 'rights_license', doi: doi) } + let(:doi) { 'doi:10.82028/18sn-h641' } + let(:pending_doi) { Deepblue::DoiBehavior::DOI_PENDING } + let(:job_run) { DoiMintingJob.perform_now(work.id, current_user: current_user) } + + before do + allow(Datacore::DoiMintingService).to receive(:mint_doi_for).and_return('return_value') + end + + describe "#perform_now", :clean do + context "when work is invalid" do + before do + work.title = nil + work.save(validate: false) + expect(work).to be_invalid + end + it "returns nil" do + expect(job_run).to be_nil + end + end + context "when doi blank" do + let(:doi) { nil } + it "returns nil" do + expect(job_run).to be_nil + end + end + context "when doi minted" do + it "returns nil" do + expect(job_run).to be_nil + end + end + context "when doi pending" do + let(:doi) { pending_doi } + it "returns true" do + expect(job_run).to eq true + end + end + end +end diff --git a/spec/models/data_set_spec.rb b/spec/models/data_set_spec.rb index 11d3b6e6..af343836 100644 --- a/spec/models/data_set_spec.rb +++ b/spec/models/data_set_spec.rb @@ -3,7 +3,6 @@ require 'rails_helper' RSpec.describe DataSet do - let( :authoremail ) { 'authoremail@umich.edu' } let( :creator ) { 'Creator, A' } let( :current_user ) { 'user@umich.edu' } @@ -662,4 +661,5 @@ def validate_prov_logger_received( prov_logger_received:, expect( rv_key_values.size ).to eq size end + include_examples "DeepBlue::DoiBehavior", :data_set end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index df9e85f0..c30443a1 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -5,6 +5,7 @@ # Prevent database truncation if the environment is production abort("The Rails environment is running in production mode!") if Rails.env.production? require 'rspec/rails' +require 'active_fedora/cleaner' # Add additional requires below this line. Rails is not loaded until this point! # Requires supporting ruby files with custom matchers and macros, etc, in @@ -79,4 +80,7 @@ DatabaseCleaner.clean end + config.after(:each) do + ActiveFedora::Cleaner.clean! if Settings.rspec.aggressive_cleaning + end end diff --git a/spec/services/datacore/doi_minting_service_spec.rb b/spec/services/datacore/doi_minting_service_spec.rb new file mode 100644 index 00000000..df147a0b --- /dev/null +++ b/spec/services/datacore/doi_minting_service_spec.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Datacore::DoiMintingService do + let(:current_user) { 'user@example.com' } + # define doi per context + let(:work) { FactoryBot.create(:data_set, doi: doi) } + let(:service) { Datacore::DoiMintingService.new(current_user: current_user, work: work) } + let(:namespaced_doi) { 'doi:10.82028/18sn-h641' } + let(:url_doi) { 'https://doi.org/10.82028/18sn-h641' } + let(:raw_doi) { '10.82028/18sn-h641' } + let(:pending_doi) { Deepblue::DoiBehavior::DOI_PENDING } + before do + allow(Datacore::DoiMintingService).to receive(:enabled?).and_return(true) + allow(service).to receive(:mint_doi!).and_raise(StandardError) # prevent client interactions + end + + describe "#id" do + context "with a nil DOI" do + let(:doi) { nil } + it "returns a blank string" do + expect(service.id).to eq '' + end + end + context "with a namespaced DOI" do + let(:doi) { namespaced_doi } + it "returns the raw DOI value" do + expect(service.id).to eq raw_doi + end + end + context "with a URL DOI" do + let(:doi) { url_doi } + it "returns the raw DOI value" do + expect(service.id).to eq raw_doi + end + end + context "with a raw DOI" do + let(:doi) { raw_doi } + it "returns the same value" do + expect(service.id).to eq doi + end + end + end + + describe "#run" do + context "on a work with a minted DOI" do + let(:doi) { namespaced_doi } + it "returns false" do + expect(service).not_to receive(:mint_doi!) + expect(service).not_to receive(:update_work_with_doi!) + expect(service.run).to eq false + end + end + context "on a work with a nil DOI" do + let(:doi) { nil } + it "returns false" do + expect(service).not_to receive(:mint_doi!) + expect(service).not_to receive(:update_work_with_doi!) + expect(service.run).to eq false + end + end + context "on a work with a pending DOI" do + let(:doi) { pending_doi } + context "but invalid metadata" do + before do + work.creator = [] + work.save(validate: false) + allow(service).to receive(:mint_doi!).and_return(nil) # prevent client interactions + end + it "returns false" do + expect(service).not_to receive(:mint_doi!) + expect(service).not_to receive(:update_work_with_doi!) + expect(service.run).to eq false + end + end + context "with successful minting" do + before do + allow(service).to receive(:mint_doi!).and_return(namespaced_doi) # stub client interaction + end + it "successfully calls mint_doi!" do + expect(service).to receive(:mint_doi!) + expect(service.run).to eq namespaced_doi + end + it "updates the work doi value" do + expect(work.doi).to eq Deepblue::DoiBehavior::DOI_PENDING + service.run + expect(work.doi).to eq namespaced_doi + end + end + context "with failed minting" do + before do + allow(service).to receive(:mint_doi!).and_return(nil) # stub client interaction + end + it "returns nil!" do + expect(service).to receive(:mint_doi!) + expect(service).to receive(:update_work_with_doi!) + expect(service.run).to be_nil + end + it "resets the work doi value to nil" do + expect(work.doi).to eq pending_doi + service.run + expect(work.doi).to be_nil + end + end + end + end +end diff --git a/spec/services/deepblue/doi_minting_service_spec.rb b/spec/services/deepblue/doi_minting_service_spec.rb deleted file mode 100644 index 1e44b643..00000000 --- a/spec/services/deepblue/doi_minting_service_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Deepblue::DoiMintingService do - - context "when minting a new doi" do - subject { described_class.new( work: work, current_user: "test_doi_minting_service@umich.edu" ) } - let(:work) { mock_model(GenericWork, id: '123', title: ['demotitle'], - creator: ['Smith, John', 'Smith, Jane', 'O\'Rielly, Kelly'])} - let(:work_url) { "umrdr-testing.hydra.lib.umich.edu/concern/work/#{work.id}" } - let(:dummy_doi) { "doi:10.5072/FK2DEAD455BEEF" } - let(:identifier) { instance_double(Ezid::Identifier, id: dummy_doi) } - - before do - allow(Rails).to receive_message_chain("application.routes.url_helpers.hyrax_data_set_url").and_return(work_url) - allow(work).to receive(:save) - allow(work).to receive(:reload) - allow(work).to receive(:doi).and_return(identifier.id) - allow(work).to receive(:doi=) - allow(work).to receive(:provenance_mint_doi) - allow(subject).to receive(:doi_server_reachable?).and_return(true) - allow(Ezid::Identifier).to receive(:mint).and_return(identifier) - end - - it "has expected metadata" do - expect(subject.metadata.datacite_title).to eq(work.title.first) - expect(subject.metadata.datacite_publisher).to eq(described_class::PUBLISHER) - expect(subject.metadata.datacite_publicationyear).to eq(Date.today.year.to_s) - expect(subject.metadata.datacite_resourcetype).to eq(described_class::RESOURCE_TYPE) - expect(subject.metadata.datacite_creator).to eq(work.creator.join(';')) - expect(subject.metadata.target).not_to be_empty - end - - it "calls out to EZID to mint a doi" do - expect(Ezid::Identifier).to receive(:mint) - subject.run - end - - it "returns the id value of the identifier" do - expect(subject.run).to eq(identifier.id) - end - - it "assigns the doi value and saves the work" do - expect(work).to receive(:doi=).with(identifier.id) - expect(work).to receive(:save) - subject.run - end - - context "EZID service is unreachable" do - before do - allow(subject).to receive(:doi_server_reachable?).and_return(false) - end - it "does not attempt to mint a doi" do - expect(subject).not_to receive(:mint_doi) - expect(subject.run).to eq "doi:10.5072/FK2DEAD455BEEF" - end - end - end - - context "when actually calling out to service" do - let(:work) { GenericWork.new(id: '123', title: ['demotitle'], - creator: ['Smith, John', 'Smith, Jane', 'O\'Rielly, Kelly'])} - let( :current_user ) { "test_doi_minting_service@umich.edu" } - it "mints a doi" do - skip unless ENV['INTEGRATION'] - expect(described_class.mint_doi_for( work: work, current_user: current_user ) ).to start_with 'doi:10.5072/FK2' - end - end - -end diff --git a/spec/support/shared_examples/doi_behavior.rb b/spec/support/shared_examples/doi_behavior.rb new file mode 100644 index 00000000..f89b08bc --- /dev/null +++ b/spec/support/shared_examples/doi_behavior.rb @@ -0,0 +1,145 @@ +RSpec.shared_examples "DeepBlue::DoiBehavior" do |object_factory| + let(:minted_doi) { 'doi:10.82028/18sn-h641' } + let(:pending_doi) { Deepblue::DoiBehavior::DOI_PENDING } + let(:minted_work) { FactoryBot.create(object_factory, doi: minted_doi) } + let(:pending_work) { FactoryBot.create(object_factory, doi: pending_doi) } + let(:unminted_work) { FactoryBot.create(object_factory, doi: nil) } + let(:file_set) { FactoryBot.create(:file_set) } + + describe "#doi_minted?" do + let(:work) { unminted_work } + context "with a nil doi" do + it "returns false" do + expect(work.doi).to be_nil + expect(work.doi_minted?).to eq false + end + end + context "with a pending doi" do + let(:work) { pending_work } + it "returns false" do + expect(work.doi).to eq pending_doi + expect(work.doi_minted?).to eq false + end + end + context "with a doi" do + let(:work) { minted_work } + it "returns true" do + expect(work.doi).to eq minted_doi + expect(work.doi_minted?).to eq true + end + end + end + + describe "#doi_minting_enabled?" do + let(:work) { unminted_work } + it "returns server setting" do + expect(work.doi_minting_enabled?).to eq Datacore::DoiMintingService.enabled? + end + end + + describe "#doi_pending?" do + context "with a nil doi" do + let(:work) { unminted_work } + it "returns false" do + expect(work.doi).to be_nil + expect(work.doi_pending?).to eq false + end + end + context "with a pending doi" do + let(:work) { pending_work } + it "returns true" do + expect(work.doi).to eq pending_doi + expect(work.doi_pending?).to eq true + end + end + context "with a doi" do + let(:work) { minted_work } + it "returns false" do + expect(work.doi).to eq minted_doi + expect(work.doi_pending?).to eq false + end + end + end + + describe "#doi_minimum_files?" do + let(:work) { minted_work } + context "without minimum files" do + it "returns false" do + expect(work.file_sets.count).to eq 0 + expect(work.doi_minimum_files?).to eq false + end + end + context "with minimum files" do + before(:each) do + work.ordered_members << file_set + work.save + end + it "returns true" do + expect(work.file_sets.count).to eq 1 + expect(work.doi_minimum_files?).to eq true + end + end + end + + describe "#doi_mint" do + before(:each) do + allow(work).to receive(:doi_minting_enabled?).and_return(true) + allow(Rails.logger).to receive(:warn) + allow(Rails.logger).to receive(:info) + allow(DoiMintingJob).to receive(:perform_later).and_return(true) + end + context "when minting is disabled" do + let(:work) { unminted_work } + before(:each) do + allow(work).to receive(:doi_minting_enabled?).and_return(false) + end + it "logs a warning" do expect(Rails.logger).to receive(:warn); work.doi_mint end + it "returns false" do expect(work.doi_mint).to eq false end + end + context "when metadata is invalid" do + let(:work) { unminted_work } + before(:each) do + work.title = nil + work.save(validate: false) + expect(work).to be_invalid + end + it "logs a warning" do expect(Rails.logger).to receive(:warn); work.doi_mint end + it "returns false" do expect(work.doi_mint).to eq false end + end + context "when minting is in progress" do + let(:work) { pending_work } + it "logs a warning" do expect(Rails.logger).to receive(:warn); work.doi_mint end + it "returns false" do expect(work.doi_mint).to eq false end + end + context "when already minted" do + let(:work) { minted_work } + it "logs a warning" do expect(Rails.logger).to receive(:warn); work.doi_mint end + it "returns false" do expect(work.doi_mint).to eq false end + end + context "when insufficient files" do + let(:work) { unminted_work } + before(:each) do + allow(work).to receive(:doi_minimum_files?).and_return(false) + end + it "logs a warning" do expect(Rails.logger).to receive(:warn); work.doi_mint end + it "returns false" do expect(work.doi_mint).to eq false end + end + context "when no doi yet" do + let(:work) { unminted_work } + before(:each) do + allow(work).to receive(:doi_minimum_files?).and_return(true) + end + it "updates DOI to pending" do + expect(work.doi).to be_nil + work.doi_mint + expect(work.doi).to eq pending_doi + end + it "calls DoiMintingJob" do + expect(DoiMintingJob).to receive(:perform_later).and_return(true) + work.doi_mint + end + it "logs success" do expect(Rails.logger).to receive(:info); work.doi_mint end + it "returns true" do expect(work.doi_mint).to eq true end + end + end +end