bulkrax 5.0.0 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 49a1f0ccc806cf73a7872a634c8c2819dac62f98f0a9dc163e7ada8931d9b1fe
4
- data.tar.gz: ecf5c8ad3e4864110665cfb5c6c5f6329e3b868414815bba163646db839400e7
3
+ metadata.gz: 490e0f170cc1128c00c467c3cc344db627d027a3b857d53dfa33b97805567d4b
4
+ data.tar.gz: 7290801bacea707b7398e674a17acf56e7a770cfb3bea20958169588a4404175
5
5
  SHA512:
6
- metadata.gz: df394e4fbbc6ca0a71eb595c075c5181d24098b1ab889ff185da10991ec01c2eaf60239658564838b67df4c0a421306de923d2eab4bbeffafb31321acb9ba1ff
7
- data.tar.gz: 35b691e96d0e59f83efadc35364283239f7f741ab4bf9d859756dacc8483e380090512cd10bf3b6ec4ce81936d00c88eb4d1ed96742c384e0f3082028ffdcf45
6
+ metadata.gz: a6f5486405e2d2eb7f6c0c49b17ed0926e55a701368e42c93db9b009a5f663682ec4141fe1dd58d0dde132fa747010ada7cd22187b81d60a7e8b6b23cbf2e24d
7
+ data.tar.gz: d56a8780ef074d412ac7406d3f3ddb39b34b17bbe43c61a64bebe06f39952a1d62cbf2dbd01a7012bcd776686def03c5a5e6c556cc1384284c2dea8a89f3eec2
@@ -5,10 +5,10 @@ require_dependency "oai"
5
5
 
6
6
  module Bulkrax
7
7
  class EntriesController < ApplicationController
8
- include Hyrax::ThemedLayoutController
8
+ include Hyrax::ThemedLayoutController if defined?(::Hyrax)
9
9
  before_action :authenticate_user!
10
10
  before_action :check_permissions
11
- with_themed_layout 'dashboard'
11
+ with_themed_layout 'dashboard' if defined?(::Hyrax)
12
12
 
13
13
  def show
14
14
  if params[:importer_id].present?
@@ -23,6 +23,7 @@ module Bulkrax
23
23
  @importer = Importer.find(params[:importer_id])
24
24
  @entry = Entry.find(params[:id])
25
25
 
26
+ return unless defined?(::Hyrax)
26
27
  add_breadcrumb t(:'hyrax.controls.home'), main_app.root_path
27
28
  add_breadcrumb t(:'hyrax.dashboard.breadcrumbs.admin'), hyrax.dashboard_path
28
29
  add_breadcrumb 'Importers', bulkrax.importers_path
@@ -35,6 +36,7 @@ module Bulkrax
35
36
  @exporter = Exporter.find(params[:exporter_id])
36
37
  @entry = Entry.find(params[:id])
37
38
 
39
+ return unless defined?(::Hyrax)
38
40
  add_breadcrumb t(:'hyrax.controls.home'), main_app.root_path
39
41
  add_breadcrumb t(:'hyrax.dashboard.breadcrumbs.admin'), hyrax.dashboard_path
40
42
  add_breadcrumb 'Exporters', bulkrax.exporters_path
@@ -4,24 +4,26 @@ require_dependency "bulkrax/application_controller"
4
4
 
5
5
  module Bulkrax
6
6
  class ExportersController < ApplicationController
7
- include Hyrax::ThemedLayoutController
7
+ include Hyrax::ThemedLayoutController if defined?(::Hyrax)
8
8
  include Bulkrax::DownloadBehavior
9
9
  before_action :authenticate_user!
10
10
  before_action :check_permissions
11
11
  before_action :set_exporter, only: [:show, :edit, :update, :destroy]
12
- with_themed_layout 'dashboard'
12
+ with_themed_layout 'dashboard' if defined?(::Hyrax)
13
13
 
14
14
  # GET /exporters
15
15
  def index
16
16
  @exporters = Exporter.all
17
17
 
18
- add_exporter_breadcrumbs
18
+ add_exporter_breadcrumbs if defined?(::Hyrax)
19
19
  end
20
20
 
21
21
  # GET /exporters/1
22
22
  def show
23
- add_exporter_breadcrumbs
24
- add_breadcrumb @exporter.name
23
+ if defined?(::Hyrax)
24
+ add_exporter_breadcrumbs
25
+ add_breadcrumb @exporter.name
26
+ end
25
27
 
26
28
  @work_entries = @exporter.entries.where(type: @exporter.parser.entry_class.to_s).page(params[:work_entries_page]).per(30)
27
29
  @collection_entries = @exporter.entries.where(type: @exporter.parser.collection_entry_class.to_s).page(params[:collections_entries_page]).per(30)
@@ -31,16 +33,18 @@ module Bulkrax
31
33
  # GET /exporters/new
32
34
  def new
33
35
  @exporter = Exporter.new
34
-
36
+ return unless defined?(::Hyrax)
35
37
  add_exporter_breadcrumbs
36
38
  add_breadcrumb 'New'
37
39
  end
38
40
 
39
41
  # GET /exporters/1/edit
40
42
  def edit
41
- add_exporter_breadcrumbs
42
- add_breadcrumb @exporter.name, bulkrax.exporter_path(@exporter.id)
43
- add_breadcrumb 'Edit'
43
+ if defined?(::Hyrax)
44
+ add_exporter_breadcrumbs
45
+ add_breadcrumb @exporter.name, bulkrax.exporter_path(@exporter.id)
46
+ add_breadcrumb 'Edit'
47
+ end
44
48
 
45
49
  # Correctly populate export_source_collection input
46
50
  @collection = Collection.find(@exporter.export_source) if @exporter.export_source.present? && @exporter.export_from == 'collection'
@@ -6,7 +6,7 @@ require_dependency 'oai'
6
6
  module Bulkrax
7
7
  # rubocop:disable Metrics/ClassLength
8
8
  class ImportersController < ApplicationController
9
- include Hyrax::ThemedLayoutController
9
+ include Hyrax::ThemedLayoutController if defined?(::Hyrax)
10
10
  include Bulkrax::DownloadBehavior
11
11
  include Bulkrax::API
12
12
  include Bulkrax::ValidationHelper
@@ -16,14 +16,14 @@ module Bulkrax
16
16
  before_action :authenticate_user!, unless: -> { api_request? }
17
17
  before_action :check_permissions
18
18
  before_action :set_importer, only: [:show, :edit, :update, :destroy]
19
- with_themed_layout 'dashboard'
19
+ with_themed_layout 'dashboard' if defined?(::Hyrax)
20
20
 
21
21
  # GET /importers
22
22
  def index
23
23
  @importers = Importer.all
24
24
  if api_request?
25
25
  json_response('index')
26
- else
26
+ elsif defined?(::Hyrax)
27
27
  add_importer_breadcrumbs
28
28
  end
29
29
  end
@@ -32,7 +32,7 @@ module Bulkrax
32
32
  def show
33
33
  if api_request?
34
34
  json_response('show')
35
- else
35
+ elsif defined?(::Hyrax)
36
36
  add_importer_breadcrumbs
37
37
  add_breadcrumb @importer.name
38
38
 
@@ -47,7 +47,7 @@ module Bulkrax
47
47
  @importer = Importer.new
48
48
  if api_request?
49
49
  json_response('new')
50
- else
50
+ elsif defined?(::Hyrax)
51
51
  add_importer_breadcrumbs
52
52
  add_breadcrumb 'New'
53
53
  end
@@ -57,7 +57,7 @@ module Bulkrax
57
57
  def edit
58
58
  if api_request?
59
59
  json_response('edit')
60
- else
60
+ elsif defined?(::Hyrax)
61
61
  add_importer_breadcrumbs
62
62
  add_breadcrumb @importer.name, bulkrax.importer_path(@importer.id)
63
63
  add_breadcrumb 'Edit'
@@ -159,6 +159,7 @@ module Bulkrax
159
159
  # GET /importer/1/upload_corrected_entries
160
160
  def upload_corrected_entries
161
161
  @importer = Importer.find(params[:importer_id])
162
+ return unless defined?(::Hyrax)
162
163
  add_breadcrumb t(:'hyrax.controls.home'), main_app.root_path
163
164
  add_breadcrumb t(:'hyrax.dashboard.breadcrumbs.admin'), hyrax.dashboard_path
164
165
  add_breadcrumb 'Importers', bulkrax.importers_path
@@ -3,7 +3,7 @@ require 'coderay'
3
3
 
4
4
  module Bulkrax
5
5
  module ApplicationHelper
6
- include ::Hyrax::HyraxHelperBehavior
6
+ include ::Hyrax::HyraxHelperBehavior if defined?(::Hyrax)
7
7
 
8
8
  def coderay(value, opts)
9
9
  CodeRay
@@ -2,9 +2,9 @@
2
2
 
3
3
  module Bulkrax
4
4
  module ImportersHelper
5
- # borrowd from batch-importer https://github.com/samvera-labs/hyrax-batch_ingest/blob/main/app/controllers/hyrax/batch_ingest/batches_controller.rb
5
+ # borrowed from batch-importer https://github.com/samvera-labs/hyrax-batch_ingest/blob/main/app/controllers/hyrax/batch_ingest/batches_controller.rb
6
6
  def available_admin_sets
7
- # Restrict available_admin_sets to only those current user can desposit to.
7
+ # Restrict available_admin_sets to only those current user can deposit to.
8
8
  @available_admin_sets ||= Hyrax::Collections::PermissionsService.source_ids_for_deposit(ability: current_ability, source_type: 'admin_set').map do |admin_set_id|
9
9
  [AdminSet.find(admin_set_id).title.first, admin_set_id]
10
10
  end
@@ -17,12 +17,29 @@ module Bulkrax
17
17
  # NOTE: In the context of this job, "identifier" is used to generically refer
18
18
  # to either a record's ID or an Bulkrax::Entry's source_identifier.
19
19
  class CreateRelationshipsJob < ApplicationJob
20
+ ##
21
+ # @api public
22
+ # @since v5.0.1
23
+ #
24
+ # Once we've created the relationships, should we then index the works's file_sets to ensure
25
+ # that we have the proper indexed values. This can help set things like `is_page_of_ssim` for
26
+ # IIIF manifest and search results of file sets.
27
+ #
28
+ # @note As of v5.0.1 the default behavior is to not perform this. That preserves past
29
+ # implementations. However, we might determine that we want to change the default
30
+ # behavior. Which would likely mean a major version change.
31
+ #
32
+ # @example
33
+ # # In config/initializers/bulkrax.rb
34
+ # Bulkrax::CreateRelationshipsJob.update_child_records_works_file_sets = true
35
+ #
36
+ # @see https://github.com/scientist-softserv/louisville-hyku/commit/128a9ef
37
+ class_attribute :update_child_records_works_file_sets, default: false
38
+
20
39
  include DynamicRecordLookup
21
40
 
22
41
  queue_as :import
23
42
 
24
- attr_accessor :child_records, :child_entry, :parent_record, :parent_entry, :importer_run_id
25
-
26
43
  # @param parent_identifier [String] Work/Collection ID or Bulkrax::Entry source_identifiers
27
44
  # @param importer_run [Bulkrax::ImporterRun] current importer run (needed to properly update counters)
28
45
  #
@@ -31,82 +48,81 @@ module Bulkrax
31
48
  # Whether the @base_entry is the parent or the child in the relationship is determined by the presence of a
32
49
  # parent_identifier or child_identifier param. For example, if a parent_identifier is passed, we know @base_entry
33
50
  # is the child in the relationship, and vice versa if a child_identifier is passed.
51
+ #
52
+ # rubocop:disable Metrics/MethodLength
34
53
  def perform(parent_identifier:, importer_run_id:) # rubocop:disable Metrics/AbcSize
35
- pending_relationships = Bulkrax::PendingRelationship.find_each.select do |rel|
36
- rel.importer_run_id == importer_run_id && rel.parent_id == parent_identifier
37
- end.sort_by(&:order)
38
-
39
- @importer_run_id = importer_run_id
40
- @parent_entry, @parent_record = find_record(parent_identifier, importer_run_id)
41
- @child_records = { works: [], collections: [] }
42
- pending_relationships.each do |rel|
43
- raise ::StandardError, %("#{rel}" needs either a child or a parent to create a relationship) if rel.child_id.nil? || rel.parent_id.nil?
44
- @child_entry, child_record = find_record(rel.child_id, importer_run_id)
45
- if child_record
46
- child_record.is_a?(::Collection) ? @child_records[:collections] << child_record : @child_records[:works] << child_record
54
+ importer_run = Bulkrax::ImporterRun.find(importer_run_id)
55
+ ability = Ability.new(importer_run.user)
56
+
57
+ parent_entry, parent_record = find_record(parent_identifier, importer_run_id)
58
+
59
+ number_of_successes = 0
60
+ number_of_failures = 0
61
+ errors = []
62
+
63
+ ActiveRecord::Base.uncached do
64
+ Bulkrax::PendingRelationship.where(parent_id: parent_identifier, importer_run_id: importer_run_id)
65
+ .ordered.find_each do |rel|
66
+ process(relationship: rel, importer_run_id: importer_run_id, parent_record: parent_record, ability: ability)
67
+ number_of_successes += 1
68
+ rescue => e
69
+ number_of_failures += 1
70
+ errors << e
47
71
  end
48
72
  end
49
73
 
50
- if (child_records[:collections].blank? && child_records[:works].blank?) || parent_record.nil?
74
+ # save record if members were added
75
+ parent_record.save! if @parent_record_members_added
76
+
77
+ # rubocop:disable Rails/SkipsModelValidations
78
+ if errors.present?
79
+ importer_run.increment!(:failed_relationships, number_of_failures)
80
+ parent_entry&.set_status_info(errors.last, importer_run)
81
+
82
+ # TODO: This can create an infinite job cycle, consider a time to live tracker.
51
83
  reschedule({ parent_identifier: parent_identifier, importer_run_id: importer_run_id })
52
84
  return false # stop current job from continuing to run after rescheduling
85
+ else
86
+ Bulkrax::ImporterRun.find(importer_run_id).increment!(:processed_relationships, number_of_successes)
53
87
  end
54
- @parent_entry ||= Bulkrax::Entry.where(identifier: parent_identifier,
55
- importerexporter_id: ImporterRun.find(importer_run_id).importer_id,
56
- importerexporter_type: "Bulkrax::Importer").first
57
- create_relationships
58
- pending_relationships.each(&:destroy)
59
- rescue ::StandardError => e
60
- parent_entry ? parent_entry.status_info(e) : child_entry.status_info(e)
61
- Bulkrax::ImporterRun.find(importer_run_id).increment!(:failed_relationships) # rubocop:disable Rails/SkipsModelValidations
88
+ # rubocop:enable Rails/SkipsModelValidations
62
89
  end
90
+ # rubocop:enable Metrics/MethodLength
63
91
 
64
92
  private
65
93
 
66
- def create_relationships
67
- if parent_record.is_a?(::Collection)
68
- collection_parent_work_child unless child_records[:works].empty?
69
- collection_parent_collection_child unless child_records[:collections].empty?
70
- else
71
- work_parent_work_child unless child_records[:works].empty?
72
- raise ::StandardError, 'a Collection may not be assigned as a child of a Work' if child_records[:collections].present?
73
- end
74
- end
94
+ def process(relationship:, importer_run_id:, parent_record:, ability:)
95
+ raise "#{relationship} needs a child to create relationship" if relationship.child_id.nil?
96
+ raise "#{relationship} needs a parent to create relationship" if relationship.parent_id.nil?
75
97
 
76
- def user
77
- @user ||= Bulkrax::ImporterRun.find(importer_run_id).importer.user
78
- end
98
+ _child_entry, child_record = find_record(relationship.child_id, importer_run_id)
99
+ raise "#{relationship} could not find child record" unless child_record
79
100
 
80
- # Work-Collection membership is added to the child as member_of_collection_ids
81
- # This is adding the reverse relationship, from the child to the parent
82
- def collection_parent_work_child
83
- child_work_ids = child_records[:works].map(&:id)
84
- parent_record.try(:reindex_extent=, Hyrax::Adapters::NestingIndexAdapter::LIMITED_REINDEX)
101
+ raise "Cannot add child collection (ID=#{relationship.child_id}) to parent work (ID=#{relationship.parent_id})" if child_record.collection? && parent_record.work?
85
102
 
86
- parent_record.add_member_objects(child_work_ids)
87
- ImporterRun.find(importer_run_id).increment!(:processed_relationships, child_work_ids.count) # rubocop:disable Rails/SkipsModelValidations
103
+ ability.authorize!(:edit, child_record)
104
+
105
+ # We could do this outside of the loop, but that could lead to odd counter failures.
106
+ ability.authorize!(:edit, parent_record)
107
+
108
+ parent_record.is_a?(Collection) ? add_to_collection(child_record, parent_record) : add_to_work(child_record, parent_record)
109
+
110
+ child_record.file_sets.each(&:update_index) if update_child_records_works_file_sets? && child_record.respond_to?(:file_sets)
111
+ relationship.destroy
88
112
  end
89
113
 
90
- # Collection-Collection membership is added to the as member_ids
91
- def collection_parent_collection_child
92
- child_records[:collections].each do |child_record|
93
- ::Hyrax::Collections::NestedCollectionPersistenceService.persist_nested_collection_for(parent: parent_record, child: child_record)
94
- ImporterRun.find(importer_run_id).increment!(:processed_relationships) # rubocop:disable Rails/SkipsModelValidations
95
- end
114
+ def add_to_collection(child_record, parent_record)
115
+ child_record.member_of_collections << parent_record
116
+ child_record.save!
96
117
  end
97
118
 
98
- # Work-Work membership is added to the parent as member_ids
99
- def work_parent_work_child
100
- records_hash = {}
101
- child_records[:works].each_with_index do |child_record, i|
102
- records_hash[i] = { id: child_record.id }
103
- end
104
- attrs = { work_members_attributes: records_hash }
105
- parent_record.try(:reindex_extent=, Hyrax::Adapters::NestingIndexAdapter::LIMITED_REINDEX)
106
- env = Hyrax::Actors::Environment.new(parent_record, Ability.new(user), attrs)
119
+ def add_to_work(child_record, parent_record)
120
+ return true if parent_record.ordered_members.to_a.include?(child_record)
107
121
 
108
- Hyrax::CurationConcern.actor.update(env)
109
- ImporterRun.find(importer_run_id).increment!(:processed_relationships, child_records[:works].count) # rubocop:disable Rails/SkipsModelValidations
122
+ parent_record.ordered_members << child_record
123
+ @parent_record_members_added = true
124
+ # TODO: Do we need to save the child record?
125
+ child_record.save!
110
126
  end
111
127
 
112
128
  def reschedule(parent_identifier:, importer_run_id:)
@@ -13,7 +13,7 @@ module Bulkrax
13
13
  entry.save!
14
14
  entry.importer.current_run = ImporterRun.find(importer_run.id)
15
15
  entry.importer.record_status
16
- entry.status_info("Deleted", ImporterRun.find(importer_run.id))
16
+ entry.set_status_info("Deleted", ImporterRun.find(importer_run.id))
17
17
  end
18
18
  # rubocop:enable Rails/SkipsModelValidations
19
19
  end
@@ -29,9 +29,9 @@ module Bulkrax
29
29
  return entry if exporter_run.enqueued_records.positive?
30
30
 
31
31
  if exporter_run.failed_records.positive?
32
- exporter_run.exporter.status_info('Complete (with failures)')
32
+ exporter_run.exporter.set_status_info('Complete (with failures)')
33
33
  else
34
- exporter_run.exporter.status_info('Complete')
34
+ exporter_run.exporter.set_status_info('Complete')
35
35
  end
36
36
 
37
37
  return entry
@@ -41,7 +41,7 @@ module Bulkrax
41
41
  ImportFileSetJob.set(wait: (entry.import_attempts + 1).minutes).perform_later(entry_id, importer_run_id)
42
42
  else
43
43
  ImporterRun.find(importer_run_id).decrement!(:enqueued_records) # rubocop:disable Rails/SkipsModelValidations
44
- entry.status_info(e)
44
+ entry.set_status_info(e)
45
45
  end
46
46
  end
47
47
 
@@ -13,7 +13,7 @@ module Bulkrax
13
13
  update_current_run_counters(importer)
14
14
  schedule(importer) if importer.schedulable?
15
15
  rescue CSV::MalformedCSVError => e
16
- importer.status_info(e)
16
+ importer.set_status_info(e)
17
17
  end
18
18
 
19
19
  def import(importer, only_updates_since_last_import)
@@ -69,7 +69,7 @@ module Bulkrax
69
69
  end
70
70
 
71
71
  def workflow_status_list
72
- Sipity::WorkflowState.all.map { |s| [s.name&.titleize, s.name] }.uniq
72
+ Sipity::WorkflowState.all.map { |s| [s.name&.titleize, s.name] }.uniq if defined?(::Hyrax)
73
73
  end
74
74
 
75
75
  # If field_mapping is empty, setup a default based on the export_properties
@@ -84,12 +84,20 @@ module Bulkrax
84
84
  end
85
85
 
86
86
  def export_from_list
87
- [
88
- [I18n.t('bulkrax.exporter.labels.importer'), 'importer'],
89
- [I18n.t('bulkrax.exporter.labels.collection'), 'collection'],
90
- [I18n.t('bulkrax.exporter.labels.worktype'), 'worktype'],
91
- [I18n.t('bulkrax.exporter.labels.all'), 'all']
92
- ]
87
+ if defined?(::Hyrax)
88
+ [
89
+ [I18n.t('bulkrax.exporter.labels.importer'), 'importer'],
90
+ [I18n.t('bulkrax.exporter.labels.collection'), 'collection'],
91
+ [I18n.t('bulkrax.exporter.labels.worktype'), 'worktype'],
92
+ [I18n.t('bulkrax.exporter.labels.all'), 'all']
93
+ ]
94
+ else
95
+ [
96
+ [I18n.t('bulkrax.exporter.labels.importer'), 'importer'],
97
+ [I18n.t('bulkrax.exporter.labels.collection'), 'collection'],
98
+ [I18n.t('bulkrax.exporter.labels.all'), 'all']
99
+ ]
100
+ end
93
101
  end
94
102
 
95
103
  def export_type_list
@@ -15,7 +15,7 @@ module Bulkrax
15
15
  has_many :entries, as: :importerexporter, dependent: :destroy
16
16
 
17
17
  validates :name, presence: true
18
- validates :admin_set_id, presence: true
18
+ validates :admin_set_id, presence: true if defined?(::Hyrax)
19
19
  validates :parser_klass, presence: true
20
20
 
21
21
  delegate :valid_import?, :write_errored_entries_file, :visibility, to: :parser
@@ -47,12 +47,12 @@ module Bulkrax
47
47
  if importer_run.failed_records.positive?
48
48
  if importer_run.invalid_records.present?
49
49
  e = Bulkrax::ImportFailed.new('Failed with Invalid Records', importer_run.invalid_records.split("\n"))
50
- importer_run.importer.status_info(e)
50
+ importer_run.importer.set_status_info(e)
51
51
  else
52
- importer_run.importer.status_info('Complete (with failures)')
52
+ importer_run.importer.set_status_info('Complete (with failures)')
53
53
  end
54
54
  else
55
- importer_run.importer.status_info('Complete')
55
+ importer_run.importer.set_status_info('Complete')
56
56
  end
57
57
  end
58
58
 
@@ -9,5 +9,11 @@ module Bulkrax
9
9
  def parents
10
10
  pending_relationships.pluck(:parent_id).uniq
11
11
  end
12
+
13
+ def user
14
+ # An importer might not have a user, the CLI ingest need not assign a user. As such, we
15
+ # fallback to the configured user.
16
+ importer.user || Bulkrax.fallback_user_for_importer_exporter_processing
17
+ end
12
18
  end
13
19
  end
@@ -9,6 +9,15 @@ module Bulkrax
9
9
 
10
10
  delegate :record, to: :raw_record
11
11
 
12
+ # @api private
13
+ #
14
+ # Included to assist in testing; namely so that you can copy down an OAI entry, store it locally,
15
+ # and then manually construct an {OAI::GetRecordResponse}.
16
+ #
17
+ # @see Bulkrax::EntrySpecHelper.oai_entry_for
18
+ attr_writer :raw_record
19
+
20
+ # @return [OAI::GetRecordResponse]
12
21
  def raw_record
13
22
  @raw_record ||= client.get_record(identifier: identifier, metadata_prefix: parser.parser_fields['metadata_prefix'])
14
23
  end
@@ -28,7 +37,7 @@ module Bulkrax
28
37
  def build_metadata
29
38
  self.parsed_metadata = {}
30
39
  self.parsed_metadata[work_identifier] = [record.header.identifier]
31
- self.raw_metadata = { xml: record.metadata.to_s }
40
+ self.raw_metadata = { record: record.metadata.to_s, header: record.header.to_s }
32
41
 
33
42
  # We need to establish the #factory_class before we proceed with the metadata. See
34
43
  # https://github.com/samvera-labs/bulkrax/issues/702 for further details.
@@ -91,7 +100,8 @@ module Bulkrax
91
100
  # If OAI-PMH doesn't return setSpec in the headers for GetRecord, use parser.collection_name
92
101
  # in this case, if 'All' is selected, records will not be added to a collection.
93
102
  def find_collection_ids
94
- return self.collection_ids if collections_created?
103
+ return self.collection_ids if defined?(@called_find_collection_ids)
104
+
95
105
  if sets.blank? || parser.collection_name != 'all'
96
106
  collection = find_collection(importerexporter.unique_collection_identifier(parser.collection_name))
97
107
  self.collection_ids << collection.id if collection.present? && !self.collection_ids.include?(collection.id)
@@ -101,6 +111,8 @@ module Bulkrax
101
111
  self.collection_ids << c.id if c.present? && !self.collection_ids.include?(c.id)
102
112
  end
103
113
  end
114
+
115
+ @called_find_collection_ids = true
104
116
  return self.collection_ids
105
117
  end
106
118
  end
@@ -3,5 +3,9 @@
3
3
  module Bulkrax
4
4
  class PendingRelationship < ApplicationRecord
5
5
  belongs_to :importer_run
6
+
7
+ # Ideally we wouldn't have a column named "order", as it is a reserved SQL term. However, if we
8
+ # quote the column, all is well...for the application.
9
+ scope :ordered, -> { order("#{quoted_table_name}.#{connection.quote_column_name('order')}") }
6
10
  end
7
11
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bulkrax
4
+ # TODO: Extract methods to class methods; there's no reason for these methods to be a mixin.
5
+ # TODO: Add specs to test in isolation
4
6
  module DynamicRecordLookup
5
7
  # Search entries, collections, and every available work type for a record that
6
8
  # has the provided identifier.
@@ -33,16 +33,21 @@ module Bulkrax
33
33
  current_status&.created_at
34
34
  end
35
35
 
36
- def status_info(e = nil, current_run = nil)
36
+ def set_status_info(e = nil, current_run = nil)
37
+ runnable = current_run || last_run
37
38
  if e.nil?
38
- self.statuses.create!(status_message: 'Complete', runnable: current_run || last_run)
39
+ self.statuses.create!(status_message: 'Complete', runnable: runnable)
39
40
  elsif e.is_a?(String)
40
- self.statuses.create!(status_message: e, runnable: current_run || last_run)
41
+ self.statuses.create!(status_message: e, runnable: runnable)
41
42
  else
42
- self.statuses.create!(status_message: 'Failed', runnable: current_run || last_run, error_class: e.class.to_s, error_message: e.message, error_backtrace: e.backtrace)
43
+ self.statuses.create!(status_message: 'Failed', runnable: runnable, error_class: e.class.to_s, error_message: e.message, error_backtrace: e.backtrace)
43
44
  end
44
45
  end
45
46
 
47
+ alias status_info set_status_info
48
+
49
+ deprecation_deprecate status_info: "Favor Bulkrax::StatusInfo.set_status_info. We will be removing .status_info in Bulkrax v6.0.0"
50
+
46
51
  # api compatible with previous error structure
47
52
  def last_error
48
53
  return unless current_status && current_status.error_class.present?
@@ -143,7 +143,7 @@ module Bulkrax
143
143
  begin
144
144
  bag.add_file(file_name, file.path) if bag.bag_files.select { |b| b.include?(file_name) }.blank?
145
145
  rescue => e
146
- entry.status_info(e)
146
+ entry.set_status_info(e)
147
147
  status_info(e)
148
148
  end
149
149
  end
@@ -11,7 +11,7 @@
11
11
  </div>
12
12
  <% end %>
13
13
 
14
- <%= form.input :name, label: t('bulkrax.exporter.labels.name') %>
14
+ <%= form.input :name, label: t('bulkrax.exporter.labels.name'), input_html: { class: 'form-control' } %>
15
15
 
16
16
  <%= form.hidden_field :user_id, value: current_user.id %>
17
17
 
@@ -19,20 +19,22 @@
19
19
  collection: form.object.export_type_list,
20
20
  label: t('bulkrax.exporter.labels.export_type'),
21
21
  required: true,
22
- prompt: 'Please select an export type' %>
22
+ prompt: 'Please select an export type',
23
+ input_html: { class: 'form-control' } %>
23
24
 
24
25
  <%= form.input :export_from,
25
26
  collection: form.object.export_from_list,
26
27
  label: t('bulkrax.exporter.labels.export_from'),
27
28
  required: true,
28
- prompt: 'Please select an export source' %>
29
+ prompt: 'Please select an export source',
30
+ input_html: { class: 'form-control' } %>
29
31
 
30
32
  <%= form.input :export_source_importer,
31
33
  label: t('bulkrax.exporter.labels.importer'),
32
34
  required: true,
33
35
  prompt: 'Select from the list',
34
36
  label_html: { class: 'importer export-source-option hidden' },
35
- input_html: { class: 'importer export-source-option hidden' },
37
+ input_html: { class: 'importer export-source-option hidden form-control' },
36
38
  collection: form.object.importers_list.sort %>
37
39
 
38
40
  <%= form.input :export_source_collection,
@@ -42,7 +44,7 @@
42
44
  placeholder: @collection&.title&.first,
43
45
  label_html: { class: 'collection export-source-option hidden' },
44
46
  input_html: {
45
- class: 'collection export-source-option hidden',
47
+ class: 'collection export-source-option hidden form-control',
46
48
  data: {
47
49
  'autocomplete-url' => '/authorities/search/collections',
48
50
  'autocomplete' => 'collection'
@@ -50,18 +52,22 @@
50
52
  }
51
53
  %>
52
54
 
53
- <%= form.input :export_source_worktype,
55
+ <% if defined?(::Hyrax) %>
56
+ <%= form.input :export_source_worktype,
54
57
  label: t('bulkrax.exporter.labels.worktype'),
55
58
  required: true,
56
59
  prompt: 'Select from the list',
57
60
  label_html: { class: 'worktype export-source-option hidden' },
58
- input_html: { class: 'worktype export-source-option hidden' },
61
+ input_html: { class: 'worktype export-source-option hidden form-control' },
59
62
  collection: Hyrax.config.curation_concerns.map {|cc| [cc.to_s, cc.to_s] } %>
63
+ <% end %>
64
+
60
65
 
61
66
  <%= form.input :limit,
62
67
  as: :integer,
63
68
  hint: 'leave blank or 0 for all records',
64
- label: t('bulkrax.exporter.labels.limit') %>
69
+ label: t('bulkrax.exporter.labels.limit'),
70
+ input_html: { class: 'form-control' } %>
65
71
 
66
72
  <%= form.input :generated_metadata?,
67
73
  as: :boolean,
@@ -76,26 +82,36 @@
76
82
  <%= form.input :date_filter,
77
83
  as: :boolean,
78
84
  label: t('bulkrax.exporter.labels.filter_by_date') %>
85
+
79
86
  <div id="date_filter_picker" class="hidden">
80
87
  <%= form.input :start_date,
81
88
  as: :date,
82
- label: t('bulkrax.exporter.labels.start_date') %>
89
+ label: t('bulkrax.exporter.labels.start_date'),
90
+ input_html: { class: 'form-control' } %>
83
91
 
84
92
  <%= form.input :finish_date,
85
93
  as: :date,
86
- label: t('bulkrax.exporter.labels.finish_date') %>
94
+ label: t('bulkrax.exporter.labels.finish_date'),
95
+ input_html: { class: 'form-control' } %>
87
96
  </div>
88
- <%= form.input :work_visibility,
89
- collection: form.object.work_visibility_list,
90
- label: t('bulkrax.exporter.labels.visibility') %>
97
+ <% if defined?(::Hyrax) %>
98
+ <%= form.input :work_visibility,
99
+ collection: form.object.work_visibility_list,
100
+ label: t('bulkrax.exporter.labels.visibility'),
101
+ input_html: { class: 'form-control' } %>
102
+ <% end %>
91
103
 
92
- <%= form.input :workflow_status,
93
- collection: form.object.workflow_status_list,
94
- label: t('bulkrax.exporter.labels.status') %>
104
+ <% if defined?(::Hyrax) %>
105
+ <%= form.input :workflow_status,
106
+ collection: form.object.workflow_status_list,
107
+ label: t('bulkrax.exporter.labels.status'),
108
+ input_html: { class: 'form-control' } %>
109
+ <% end %>
95
110
 
96
111
  <%= form.input :parser_klass,
97
112
  collection: Bulkrax.parsers.map {|p| [p[:name], p[:class_name], {'data-partial' => p[:partial]}] if p[:class_name].constantize.export_supported? }.compact,
98
- label: t('bulkrax.exporter.labels.export_format') %>
113
+ label: t('bulkrax.exporter.labels.export_format'),
114
+ input_html: { class: 'form-control' } %>
99
115
  </div>
100
116
 
101
117
  <%# Find definitions for the functions called in this script in
@@ -4,7 +4,7 @@
4
4
 
5
5
  <div class="row">
6
6
  <div class="col-md-12">
7
- <div class="panel panel-default tabs">
7
+ <div class="panel panel-default tabs exporter-form">
8
8
  <%= simple_form_for @exporter do |form| %>
9
9
  <%= render 'form', exporter: @exporter, form: form %>
10
10
  <div class="panel-footer">
@@ -4,7 +4,7 @@
4
4
 
5
5
  <div class="row">
6
6
  <div class="col-md-12">
7
- <div class="panel panel-default tabs">
7
+ <div class="panel panel-default tabs exporter-form">
8
8
  <%= simple_form_for @exporter do |form| %>
9
9
  <%= render 'form', exporter: @exporter, form: form %>
10
10
  <div class="panel-footer">
@@ -11,17 +11,17 @@
11
11
  </div>
12
12
  <% end %>
13
13
 
14
- <%= form.input :name %>
14
+ <%= form.input :name, input_html: { class: 'form-control' } %>
15
15
 
16
- <%= form.input :admin_set_id, collection: available_admin_sets %>
16
+ <%= form.input :admin_set_id, collection: available_admin_sets if defined?(::Hyrax) %>
17
17
 
18
18
  <%= form.hidden_field :user_id, value: current_user.id %>
19
19
 
20
- <%= form.input :frequency, collection: form.object.class.frequency_enums %>
20
+ <%= form.input :frequency, collection: form.object.class.frequency_enums, input_html: { class: 'form-control' } %>
21
21
 
22
- <%= form.input :limit, as: :integer, hint: 'leave blank or 0 for all records' %>
22
+ <%= form.input :limit, as: :integer, hint: 'leave blank or 0 for all records', input_html: { class: 'form-control'} %>
23
23
 
24
- <%= form.input :parser_klass, collection: Bulkrax.parsers.map {|p| [p[:name], p[:class_name], {'data-partial' => p[:partial]}]}, label: "Parser" %>
24
+ <%= form.input :parser_klass, collection: Bulkrax.parsers.map {|p| [p[:name], p[:class_name], {'data-partial' => p[:partial]}]}, label: "Parser", input_html: { class: 'form-control' } %>
25
25
 
26
26
  <%= form.fields_for :parser_fields do |fi| %>
27
27
  <div class='parser_fields'>
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oai'
4
+ require 'xml/libxml'
5
+
6
+ module Bulkrax
7
+ ##
8
+ # The purpose of this module is to provide some testing facilities for those that include the
9
+ # Bulkrax gem in their application.
10
+ #
11
+ # This module came about through a desire to expose a quick means of vetting the accuracy of the
12
+ # different parsers.
13
+ module EntrySpecHelper
14
+ ##
15
+ # @api public
16
+ # @since v5.0.1
17
+ #
18
+ # The purpose of this method is encapsulate the logic of creating the appropriate Bulkrax::Entry
19
+ # object based on the given data, identifier, and parser_class_name.
20
+ #
21
+ # From that entry, you should be able to test how {Bulkrax::Entry#build_metadata} populates the
22
+ # {Bulkrax::Entry#parsed_metadata} variable. Other uses may emerge.
23
+ #
24
+ # @param data [Object] the data that we use to populate the raw metadata. Due to implementation
25
+ # details of each entry, the data will be of different formats.
26
+ #
27
+ # @param identifier [String, Integer] The identifier of the entry. This might also be found in
28
+ # the metadata of the entry, but for instantiation purposes we need this value.
29
+ # @param parser_class_name [String] The name of the parser class you're wanting to test.
30
+ # @param type [Sybmol] The type of entry (e.g. :entry, :collection, :file_set) for testing.
31
+ # @param options [Hash<Symbol,Object>] these are to be passed along into the instantiation of
32
+ # the various classes. See implementation details.
33
+ #
34
+ # @return [Bulkrax::Entry]
35
+ def self.entry_for(data:, identifier:, parser_class_name:, type: :entry, **options)
36
+ importer = importer_for(parser_class_name: parser_class_name, **options)
37
+ entry_type_method_name = ENTRY_TYPE_TO_METHOD_NAME_MAP.fetch(type)
38
+ entry_class = importer.parser.public_send(entry_type_method_name)
39
+
40
+ # Using an instance of the entry_class to dispatch to different
41
+ entry_for_dispatch = entry_class.new
42
+
43
+ # Using the {is_a?} test we get the benefit of inspecting an object's inheritance path
44
+ # (e.g. ancestry). The logic, as implemented, also provides a mechanism for folks in their
45
+ # applications to add a {class_name_entry_for}; something that I suspect isn't likely
46
+ # but given the wide variety of underlying needs I could see happening and I want to encourage
47
+ # patterned thinking to fold that different build method into this structure.
48
+ key = entry_class_to_symbol_map.keys.detect { |class_name| entry_for_dispatch.is_a?(class_name.constantize) }
49
+
50
+ # Yes, we'll raise an error if we didn't find a corresponding key. And that's okay.
51
+ symbol = entry_class_to_symbol_map.fetch(key)
52
+
53
+ send("build_#{symbol}_entry_for",
54
+ importer: importer,
55
+ identifier: identifier,
56
+ entry_class: entry_class,
57
+ data: data,
58
+ **options)
59
+ end
60
+
61
+ ENTRY_TYPE_TO_METHOD_NAME_MAP = {
62
+ entry: :entry_class,
63
+ collection: :collection_entry_class,
64
+ file_set: :file_set_entry_class
65
+ }.freeze
66
+
67
+ DEFAULT_ENTRY_CLASS_TO_SYMBOL_MAP = {
68
+ 'Bulkrax::OaiEntry' => :oai,
69
+ 'Bulkrax::XmlEntry' => :xml,
70
+ 'Bulkrax::CsvEntry' => :csv
71
+ }.freeze
72
+
73
+ # Present implementations of entry classes tend to inherit from the below listed class names.
74
+ # We're not looking to register all descendents of the {Bulkrax::Entry} class, but instead find
75
+ # the ancestor where there is significant deviation.
76
+ def self.entry_class_to_symbol_map
77
+ @entry_class_to_symbol_map || DEFAULT_ENTRY_CLASS_TO_SYMBOL_MAP
78
+ end
79
+
80
+ def self.entry_class_to_symbol_map=(value)
81
+ @entry_class_to_symbol_map = value
82
+ end
83
+
84
+ def self.importer_for(parser_class_name:, parser_fields: {}, **options)
85
+ # Ideally, we could pass in the field_mapping. However, there is logic that ignores the
86
+ # parser's field_mapping and directly asks for Bulkrax's field_mapping (e.g. model_mapping
87
+ # method).
88
+ Rails.logger.warn("You passed :importer_field_mapping as an option. This may not fully work as desired.") if options.key?(:importer_field_mapping)
89
+ Bulkrax::Importer.new(
90
+ name: options.fetch(:importer_name, "Test importer for identifier"),
91
+ admin_set_id: options.fetch(:importer_admin_set_id, "admin_set/default"),
92
+ user: options.fetch(:importer_user, User.new(email: "hello@world.com")),
93
+ limit: options.fetch(:importer_limits, 1),
94
+ parser_klass: parser_class_name,
95
+ field_mapping: options.fetch(:importer_field_mappings) { Bulkrax.field_mappings.fetch(parser_class_name) },
96
+ parser_fields: parser_fields
97
+ ).tap do |importer|
98
+ # Why are we saving the importer and a run? We might want to delve deeper into the call
99
+ # stack. See https://github.com/scientist-softserv/adventist-dl/pull/266
100
+ importer.save!
101
+ # Later on, we might to want a current run
102
+ importer.importer_runs.create!
103
+ end
104
+ end
105
+ private_class_method :importer_for
106
+
107
+ ##
108
+ # @api private
109
+ #
110
+ # @param data [Hash<Symbol,String>] we're expecting a hash with keys that are symbols and then
111
+ # values that are strings.
112
+ #
113
+ # @return [Bulkrax::CsvEntry]
114
+ #
115
+ # @note As a foible of this implementation, you'll need to include along a CSV to establish the
116
+ # columns that you'll parse (e.g. the first row
117
+ def self.build_csv_entry_for(importer:, data:, identifier:, entry_class:, **_options)
118
+ entry_class.new(
119
+ importerexporter: importer,
120
+ identifier: identifier,
121
+ raw_metadata: data
122
+ )
123
+ end
124
+
125
+ ##
126
+ # @api private
127
+ #
128
+ # @param data [String] we're expecting a string that is well-formed XML for OAI parsing.
129
+ #
130
+ # @return [Bulkrax::OaiEntry]
131
+ def self.build_oai_entry_for(importer:, data:, identifier:, entry_class:, **options)
132
+ # The raw record assumes we take the XML data, parse it and then send that to the
133
+ # OAI::GetRecordResponse object.
134
+ doc = XML::Parser.string(data)
135
+ raw_record = OAI::GetRecordResponse.new(doc.parse)
136
+
137
+ raw_metadata = {
138
+ importer.parser.source_identifier.to_s => identifier,
139
+ "data" => data,
140
+ "collections" => options.fetch(:raw_metadata_collections, []),
141
+ "children" => options.fetch(:raw_metadata_children, [])
142
+ }
143
+
144
+ entry_class.new(
145
+ raw_record: raw_record,
146
+ importerexporter: importer,
147
+ identifier: identifier,
148
+ raw_metadata: raw_metadata
149
+ )
150
+ end
151
+
152
+ ##
153
+ # @api private
154
+ #
155
+ # @param data [String] we're expecting a string that is well-formed XML.
156
+ #
157
+ # @return [Bulkrax::XmlEntry]
158
+ def self.build_xml_entry_for(importer:, data:, identifier:, entry_class:, **options)
159
+ raw_metadata = {
160
+ importer.parser.source_identifier.to_s => identifier,
161
+ "data" => data,
162
+ "collections" => options.fetch(:raw_metadata_collections, []),
163
+ "children" => options.fetch(:raw_metadata_children, [])
164
+ }
165
+
166
+ entry_class.new(
167
+ importerexporter: importer,
168
+ identifier: identifier,
169
+ raw_metadata: raw_metadata
170
+ )
171
+ end
172
+ end
173
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bulkrax
4
- VERSION = '5.0.0'
4
+ VERSION = '5.1.0'
5
5
  end
data/lib/bulkrax.rb CHANGED
@@ -185,6 +185,12 @@ module Bulkrax
185
185
  value.to_s.delete("\xEF\xBB\xBF")
186
186
  end
187
187
 
188
+ def self.fallback_user_for_importer_exporter_processing
189
+ return User.batch_user if defined?(Hyrax) && User.respond_to?(:batch_user)
190
+
191
+ raise "We have no fallback user available for Bulkrax.fallback_user_for_importer_exporter_processing"
192
+ end
193
+
188
194
  # This class confirms to the Active::Support.serialze interface. It's job is to ensure that we
189
195
  # don't have keys with the tricksy Byte Order Mark character.
190
196
  #
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bulkrax
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0
4
+ version: 5.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Kaufman
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-24 00:00:00.000000000 Z
11
+ date: 2023-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -396,6 +396,7 @@ files:
396
396
  - db/migrate/20220609001128_rename_bulkrax_importer_run_to_importer_run.rb
397
397
  - lib/bulkrax.rb
398
398
  - lib/bulkrax/engine.rb
399
+ - lib/bulkrax/entry_spec_helper.rb
399
400
  - lib/bulkrax/version.rb
400
401
  - lib/generators/bulkrax/install_generator.rb
401
402
  - lib/generators/bulkrax/templates/README
@@ -410,7 +411,7 @@ homepage: https://github.com/samvera-labs/bulkrax
410
411
  licenses:
411
412
  - Apache-2.0
412
413
  metadata: {}
413
- post_install_message:
414
+ post_install_message:
414
415
  rdoc_options: []
415
416
  require_paths:
416
417
  - lib
@@ -425,8 +426,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
425
426
  - !ruby/object:Gem::Version
426
427
  version: '0'
427
428
  requirements: []
428
- rubygems_version: 3.0.3
429
- signing_key:
429
+ rubygems_version: 3.0.3.1
430
+ signing_key:
430
431
  specification_version: 4
431
432
  summary: Import and export tool for Hyrax and Hyku
432
433
  test_files: []