bulkrax 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []