hyrax 5.0.3 → 5.0.5

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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/app/controllers/concerns/hyrax/valkyrie_downloads_controller_behavior.rb +1 -0
  4. data/app/controllers/hyrax/admin/analytics/work_reports_controller.rb +4 -4
  5. data/app/jobs/batch_create_job.rb +6 -5
  6. data/app/jobs/concerns/hyrax/queued_job_behavior.rb +22 -0
  7. data/app/jobs/create_work_job.rb +4 -3
  8. data/app/jobs/hyrax/queued_delete_job.rb +11 -0
  9. data/app/jobs/hyrax/queued_indexing_job.rb +11 -0
  10. data/app/jobs/migrate_files_to_valkyrie_job.rb +33 -21
  11. data/app/jobs/migrate_sipity_entity_job.rb +21 -0
  12. data/app/models/concerns/hyrax/solr_document_behavior.rb +5 -2
  13. data/app/models/hyrax/file_metadata.rb +22 -7
  14. data/app/services/hyrax/analytics/ga4/base.rb +1 -1
  15. data/app/services/hyrax/analytics/ga4.rb +5 -1
  16. data/app/services/hyrax/user_stat_importer.rb +1 -1
  17. data/app/services/migrate_resource_service.rb +1 -1
  18. data/app/views/hyrax/admin/analytics/collection_reports/_top_collections.html.erb +3 -7
  19. data/app/views/hyrax/admin/analytics/work_reports/_top_file_set_downloads.html.erb +3 -6
  20. data/app/views/hyrax/admin/analytics/work_reports/_top_works.html.erb +2 -3
  21. data/app/views/hyrax/admin/analytics/work_reports/_work_files.html.erb +1 -6
  22. data/chart/hyrax/Chart.yaml +1 -1
  23. data/config/initializers/indexing_adapter_initializer.rb +4 -0
  24. data/config/metadata/core_metadata.yaml +1 -0
  25. data/docker-compose-dassie.yml +4 -4
  26. data/documentation/developing-your-hyrax-based-app.md +2 -2
  27. data/lib/freyja/persister.rb +11 -4
  28. data/lib/hyrax/configuration.rb +22 -7
  29. data/lib/hyrax/transactions/steps/add_file_sets.rb +2 -1
  30. data/lib/hyrax/version.rb +1 -1
  31. data/lib/hyrax.rb +1 -0
  32. data/lib/valkyrie/indexing/redis_queue/indexing_adapter.rb +142 -0
  33. data/template.rb +1 -1
  34. metadata +8 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e1143acd065d9ea879f9d47024e4b14603dce26ce528082ba1d358a3aec9629
4
- data.tar.gz: 4b75c209e9caace3457521ec1af21fc59d8250bf6a4536e74af63061d07d69bf
3
+ metadata.gz: 97ecad85144a7592892b75201b53b2d021541fb2a02188783149f51c07f89e31
4
+ data.tar.gz: '0943ac40e085f7beb166f24cbfc32fd90cdee77bf504f4457864ee145bb4e24a'
5
5
  SHA512:
6
- metadata.gz: 248c5cdb08d506306f7e7664f17647b7af8c0afea32a583d1fe1698cf2346b57caa3d276f4c684627326ff174340a27ea5a74cf8c79d83b79d23285feef8c06e
7
- data.tar.gz: dbe9e52dea97e0162fa943120f6f3507cb25ad0fd945a7baf693ded264ee5b7308d20894f8220af43471f0cb08fb1c3b7512a5e116261bba22c425369baece15
6
+ metadata.gz: 4380562339d0c5506e0fe73ec1a5970bf440e2f28398424d722b21a1d42632979a314adea6be892056d476d5fe94fb5da79e731decbcb01a03c794e425d54da2
7
+ data.tar.gz: ed3bfd922ff65c64b9d0c8411f5806f9ed80a9f2dcc03e1afcbc140e96e7e5bdf13fe4066ed6e3e76839a4edb61438c5c0e60b97707e592712f4617478ae7c33
data/Gemfile CHANGED
@@ -21,4 +21,5 @@ group :development, :test do
21
21
  gem 'ruby-prof', require: false
22
22
  gem 'semaphore_test_boosters'
23
23
  gem "simplecov", require: false
24
+ gem 'timecop'
24
25
  end
@@ -102,6 +102,7 @@ module Hyrax
102
102
  def find_file_metadata(file_set:, use: :original_file, mime_type: nil)
103
103
  if mime_type.nil?
104
104
  use = :thumbnail_file if use == :thumbnail
105
+ use = :original_file unless Hyrax::FileMetadata::Use.keys.include?(use)
105
106
  use = Hyrax::FileMetadata::Use.uri_for(use: use)
106
107
  results = Hyrax.custom_queries.find_many_file_metadata_by_use(resource: file_set, use: use)
107
108
  else
@@ -14,9 +14,9 @@ module Hyrax
14
14
  @works_count = @accessible_works.count
15
15
  @top_works = paginate(top_works_list, rows: 10)
16
16
  @top_file_set_downloads = paginate(top_files_list, rows: 10)
17
-
18
- @pageviews = Hyrax::Analytics.daily_events('work-view'), @downloads = Hyrax::Analytics.daily_events('file-set-download') if current_user.ability.admin?
19
-
17
+ # rubocop:disable Style/ParallelAssignment
18
+ @pageviews, @downloads = Hyrax::Analytics.daily_events('work-view'), Hyrax::Analytics.daily_events('file-set-download') if current_user.ability.admin?
19
+ # rubocop:enable Style/ParallelAssignment
20
20
  respond_to do |format|
21
21
  format.html
22
22
  format.csv { export_data }
@@ -34,7 +34,7 @@ module Hyrax
34
34
  end
35
35
  end
36
36
 
37
- private
37
+ private
38
38
 
39
39
  def accessible_works
40
40
  models = Hyrax::ModelRegistry.work_rdf_representations.map { |m| "\"#{m}\"" }
@@ -25,18 +25,19 @@ class BatchCreateJob < Hyrax::ApplicationJob
25
25
  private
26
26
 
27
27
  def create(user, titles, resource_types, uploaded_files, attributes, operation)
28
- model = attributes.delete(:model) || attributes.delete('model')
28
+ job_attributes = attributes
29
+ model = job_attributes.delete(:model) || job_attributes.delete('model')
29
30
  raise ArgumentError, 'attributes must include "model" => ClassName.to_s' unless model
30
31
  uploaded_files.each do |upload_id|
31
32
  title = [titles[upload_id]] if titles[upload_id]
32
33
  resource_type = Array.wrap(resource_types[upload_id]) if resource_types[upload_id]
33
- attributes = attributes.merge(uploaded_files: [upload_id],
34
- title: title,
35
- resource_type: resource_type)
34
+ job_attributes = job_attributes.merge(uploaded_files: [upload_id],
35
+ title: title,
36
+ resource_type: resource_type)
36
37
  child_operation = Hyrax::Operation.create!(user: user,
37
38
  operation_type: "Create Work",
38
39
  parent: operation)
39
- CreateWorkJob.perform_later(user, model, attributes, child_operation)
40
+ CreateWorkJob.perform_later(user, model, job_attributes, child_operation)
40
41
  end
41
42
  end
42
43
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ # Grants the user's edit access on the provided FileSet
4
+ module QueuedJobBehavior
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ queue_as Hyrax.config.ingest_queue_name
9
+ cattr_accessor :requeue_frequency
10
+ end
11
+
12
+ private
13
+
14
+ def redis_queue
15
+ Valkyrie::IndexingAdapter.find(:redis_queue)
16
+ end
17
+
18
+ def requeue(**args)
19
+ self.class.set(wait_until: (self.class.requeue_frequency || 5.minutes).from_now).perform_later(**args)
20
+ end
21
+ end
22
+ end
@@ -39,11 +39,12 @@ class CreateWorkJob < Hyrax::ApplicationJob
39
39
  end
40
40
 
41
41
  def batch_create_valkyrie_work(work, attributes, user)
42
- uploaded_file_ids = attributes.delete(:uploaded_files)
42
+ form_attributes = attributes
43
+ uploaded_file_ids = form_attributes.delete(:uploaded_files)
43
44
  files = Hyrax::UploadedFile.find(uploaded_file_ids)
44
- permissions_params = attributes.delete(:permissions_attributes)
45
+ permissions_params = form_attributes.delete(:permissions_attributes)
45
46
  form = Hyrax::FormFactory.new.build(work, nil, nil)
46
- form.validate(attributes)
47
+ form.validate(form_attributes)
47
48
 
48
49
  transactions['change_set.create_work']
49
50
  .with_step_args(
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ class QueuedDeleteJob < ApplicationJob
4
+ include QueuedJobBehavior
5
+
6
+ def perform(size: 200)
7
+ redis_queue.delete_queue(size: size)
8
+ requeue(size: size)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ class QueuedIndexingJob < ApplicationJob
4
+ include QueuedJobBehavior
5
+
6
+ def perform(size: 200)
7
+ redis_queue.index_queue(size: size)
8
+ requeue(size: size)
9
+ end
10
+ end
11
+ end
@@ -2,6 +2,11 @@
2
2
  # Responsible for conditionally enqueuing the file and thumbnail migration
3
3
  # logic of an ActiveFedora object.
4
4
  class MigrateFilesToValkyrieJob < Hyrax::ApplicationJob
5
+ # Define a logger for this job
6
+ def logger
7
+ FileUtils.mkdir_p(Hyrax.config.working_path)
8
+ @logger ||= Logger.new(Hyrax.config.working_path.join('migrate_files_to_valkyrie_job.log'))
9
+ end
5
10
  ##
6
11
  #
7
12
  # @param resource [Hyrax::FileSet]
@@ -33,6 +38,7 @@ class MigrateFilesToValkyrieJob < Hyrax::ApplicationJob
33
38
  # @todo should we trigger a job if the member is a child work?
34
39
  paths = Hyrax::DerivativePath.derivatives_for_reference(resource)
35
40
  paths.each do |path|
41
+ next unless File.size?(path) # skip blank files
36
42
  container = container_for(path)
37
43
  mime_type = Marcel::MimeType.for(extension: File.extname(path))
38
44
  directives = { url: path, container: container, mime_type: mime_type }
@@ -50,27 +56,33 @@ class MigrateFilesToValkyrieJob < Hyrax::ApplicationJob
50
56
 
51
57
  files = Hyrax.custom_queries.find_many_file_metadata_by_ids(ids: resource.file_ids)
52
58
  files.each do |file|
53
- # If it doesn't start with fedora, we've likely already migrated it.
54
- next unless /^fedora:/.match?(file.file_identifier.to_s)
55
- resource.file_ids.delete(file.id)
59
+ begin
60
+ # If it doesn't start with fedora, we've likely already migrated it.
61
+ next unless /^fedora:/.match?(file.file_identifier.to_s)
62
+ resource.file_ids.delete(file.id)
56
63
 
57
- Tempfile.create do |tempfile|
58
- tempfile.binmode
59
- tempfile.write(URI.open(file.file_identifier.to_s.gsub("fedora:", "http:")).read)
60
- tempfile.rewind
64
+ Tempfile.create do |tempfile|
65
+ tempfile.binmode
66
+ tempfile.write(URI.open(file.file_identifier.to_s.gsub("fedora:", "http:")).read)
67
+ tempfile.rewind
61
68
 
62
- # valkyrie_file = Hyrax.storage_adapter.upload(resource: resource, file: tempfile, original_filename: file.original_filename)
63
- valkyrie_file = Hyrax::ValkyrieUpload.file(
64
- filename: resource.label,
65
- file_set: resource,
66
- io: tempfile,
67
- use: file.pcdm_use.select {|use| Hyrax::FileMetadata::Use.use_list.include?(use)},
68
- user: User.find_or_initialize_by(User.user_key_field => resource.depositor),
69
- mime_type: file.mime_type,
70
- skip_derivatives: true
71
- )
72
- valkyrie_file = copy_attributes(valkyrie_file:, original_file: file)
73
- Hyrax.persister.save(resource: valkyrie_file)
69
+ # valkyrie_file = Hyrax.storage_adapter.upload(resource: resource, file: tempfile, original_filename: file.original_filename)
70
+ valkyrie_file = Hyrax::ValkyrieUpload.file(
71
+ filename: resource.label,
72
+ file_set: resource,
73
+ io: tempfile,
74
+ use: file.pcdm_use.select {|use| Hyrax::FileMetadata::Use.use_list.include?(use)},
75
+ user: User.find_or_initialize_by(User.user_key_field => resource.depositor),
76
+ mime_type: file.mime_type,
77
+ skip_derivatives: true
78
+ )
79
+ valkyrie_file = copy_attributes(valkyrie_file:, original_file: file)
80
+ Hyrax.persister.save(resource: valkyrie_file)
81
+ end
82
+ rescue StandardError => e
83
+ # Log errors specific to file migration
84
+ logger.error("Error migrating file #{file.id} for resource #{resource.id}: #{e.message}")
85
+ logger.error(e.backtrace.join("\n"))
74
86
  end
75
87
  end
76
88
  # reindex the file set after migrating files to include characterization info
@@ -93,9 +105,9 @@ class MigrateFilesToValkyrieJob < Hyrax::ApplicationJob
93
105
  #
94
106
  # @param filename [String] the name of the derivative file: i.e. 'x-thumbnail.jpg'
95
107
  # @return [String]
96
- def container_for(filename)
108
+ def container_for(path)
97
109
  # we want the portion between the '-' and the '.'
98
- file_blob = File.basename(filename, '.*').split('-').last
110
+ file_blob = File.basename(path, '.*').split('-').last
99
111
 
100
112
  case file_blob
101
113
  when 'thumbnail'
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # migrates a resource's sipity entity so it can be found
4
+ class MigrateSipityEntityJob < ApplicationJob
5
+ # input [String] id of a migrated resource
6
+ def perform(id:)
7
+ resource = Hyrax.query_service.find_by(id: id)
8
+ new_gid = Hyrax::GlobalID(resource).to_s
9
+ return if Sipity::Entity.find_by(proxy_for_global_id: new_gid)
10
+
11
+ work = resource.internal_resource.constantize.find(id)
12
+ original_gid = Hyrax::GlobalID(work).to_s
13
+ return if new_gid == original_gid
14
+ original_entity = Sipity::Entity.find_by(proxy_for_global_id: original_gid)
15
+ return if original_entity.nil?
16
+ original_entity.update(proxy_for_global_id: new_gid)
17
+ rescue ActiveFedora::ObjectNotFoundError
18
+ # this happens when the resource was never in Fedora so there is nothing to migrate.
19
+ # We don't want to retry the job so we don't raise an error.
20
+ end
21
+ end
@@ -82,9 +82,12 @@ module Hyrax
82
82
 
83
83
  # Method to return the model
84
84
  def hydra_model(classifier: nil)
85
+ # finds the model from the solr document
85
86
  model = first('has_model_ssim')&.safe_constantize
86
- model = (first('has_model_ssim')&.+ 'Resource')&.safe_constantize if Hyrax.config.valkyrie_transition?
87
- model || model_classifier(classifier).classifier(self).best_model
87
+ # this returns nil if it isn't a valid model
88
+ resource_model = (first('has_model_ssim')&.+ 'Resource')&.safe_constantize if Hyrax.config.valkyrie_transition?
89
+ # if valkyrie_transition is enabled, we generally want to use the resource model if it exists
90
+ resource_model || model || model_classifier(classifier).classifier(self).best_model
88
91
  end
89
92
 
90
93
  def depositor(default = '')
@@ -44,16 +44,31 @@ module Hyrax
44
44
 
45
45
  # @return [Array<RDF::URI>] list of all uses
46
46
  def use_list
47
- [ORIGINAL_FILE,
48
- THUMBNAIL_IMAGE,
49
- EXTRACTED_TEXT,
50
- INTERMEDIATE_FILE,
51
- PRESERVATION_FILE,
52
- SERVICE_FILE,
53
- TRANSCRIPT]
47
+ [
48
+ EXTRACTED_TEXT,
49
+ INTERMEDIATE_FILE,
50
+ ORIGINAL_FILE,
51
+ PRESERVATION_FILE,
52
+ SERVICE_FILE,
53
+ THUMBNAIL_IMAGE,
54
+ TRANSCRIPT
55
+ ]
54
56
  end
55
57
  module_function :use_list
56
58
 
59
+ def keys
60
+ [
61
+ :extracted_file,
62
+ :intermediate_file,
63
+ :original_file,
64
+ :preservation_file,
65
+ :service_file,
66
+ :thumbnail_file,
67
+ :transcript_file
68
+ ]
69
+ end
70
+ module_function :keys
71
+
57
72
  ##
58
73
  # @param use [RDF::URI, Symbol]
59
74
  #
@@ -88,7 +88,7 @@ module Hyrax
88
88
  end
89
89
 
90
90
  def unwrap_metric(metric)
91
- metric.metric_values.first.value.to_i
91
+ metric&.metric_values&.first&.value.to_i
92
92
  end
93
93
  end
94
94
  end
@@ -76,7 +76,11 @@ module Hyrax
76
76
  end
77
77
 
78
78
  def account_info
79
- @account_info ||= JSON.parse(account_json_string)
79
+ @account_info ||= if account_json_string.is_a? Hash
80
+ account_json_string
81
+ else
82
+ JSON.parse(account_json_string)
83
+ end
80
84
  end
81
85
 
82
86
  KEYS.each do |key|
@@ -49,7 +49,7 @@ module Hyrax
49
49
 
50
50
  def process_files(stats, user, start_date)
51
51
  file_ids_for_user(user).each do |file_id|
52
- file = ::FileSet.find(file_id)
52
+ file = Hyrax.query_service.find_by(id: file_id)
53
53
  view_stats = extract_stats_for(object: file, from: FileViewStat, start_date: start_date, user: user)
54
54
  stats = tally_results(view_stats, :views, stats) if view_stats.present?
55
55
  delay
@@ -8,7 +8,7 @@ class MigrateResourceService
8
8
  end
9
9
 
10
10
  def model
11
- @model || Wings::ModelRegistry.lookup(resource.class).to_s
11
+ @model ||= Wings::ModelRegistry.lookup(resource.class).to_s
12
12
  end
13
13
 
14
14
  def call
@@ -1,5 +1,5 @@
1
1
  <div class="card">
2
-
2
+
3
3
  <div class="card-header">
4
4
  <h4><%= t('.top_collections') %>
5
5
  <div class="btn-group float-right">
@@ -41,15 +41,11 @@
41
41
  </tbody>
42
42
  </table>
43
43
  </div>
44
-
44
+
45
45
  <div class="card-footer">
46
46
  <div class="float-right">
47
- <%= paginate @top_collections %>
47
+ <%= paginate @top_collections, outer_window: 2, theme: 'blacklight' %>
48
48
  </div>
49
49
  </div>
50
50
 
51
51
  </div>
52
-
53
-
54
-
55
-
@@ -14,8 +14,8 @@
14
14
  <tbody>
15
15
  <% @top_file_set_downloads.each do |download| %>
16
16
  <tr>
17
- <td><%= download[1] %></td>
18
- <td><%= download[0] %></td>
17
+ <td><%= download[1] %></td>
18
+ <td><%= download[0] %></td>
19
19
  <td><%= download[2] %></td>
20
20
  </tr>
21
21
  <% end %>
@@ -24,10 +24,7 @@
24
24
  </div>
25
25
  <div class="card-footer">
26
26
  <div class="float-right">
27
- <%= paginate @top_file_set_downloads %>
27
+ <%= paginate @top_file_set_downloads, outer_window: 2, theme: 'blacklight' %>
28
28
  </div>
29
29
  </div>
30
30
  </div>
31
-
32
-
33
-
@@ -24,16 +24,15 @@
24
24
  <td><%= work[0] %></td>
25
25
  <td><%= work[2] %></td>
26
26
  <td><%= work[3] %></td>
27
- </tr>
27
+ </tr>
28
28
  <% end %>
29
29
  </tbody>
30
30
  </table>
31
31
  </div>
32
-
33
32
 
34
33
  <div class="card-footer">
35
34
  <div class="float-right">
36
- <%= paginate @top_works %>
35
+ <%= paginate @top_works, outer_window: 2, theme: 'blacklight' %>
37
36
  </div>
38
37
  </div>
39
38
 
@@ -30,12 +30,7 @@
30
30
  </div>
31
31
  <div class="card-footer">
32
32
  <div class="float-right">
33
- <%= paginate @files %>
33
+ <%= paginate @files, outer_window: 2, theme: 'blacklight' %>
34
34
  </div>
35
35
  </div>
36
36
  </div>
37
-
38
-
39
-
40
-
41
-
@@ -3,7 +3,7 @@ name: hyrax
3
3
  description: An open-source, Samvera-powered digital repository system
4
4
  type: application
5
5
  version: 3.7.0
6
- appVersion: 5.0.0
6
+ appVersion: 5.0.5
7
7
  dependencies:
8
8
  - name: fcrepo
9
9
  version: 2.0.0
@@ -7,3 +7,7 @@ Valkyrie::IndexingAdapter.register(
7
7
  Valkyrie::IndexingAdapter.register(
8
8
  Valkyrie::Indexing::NullIndexingAdapter.new, :null_index
9
9
  )
10
+ Valkyrie::IndexingAdapter.register(
11
+ Valkyrie::Indexing::RedisQueue::IndexingAdapter.new,
12
+ :redis_queue
13
+ )
@@ -20,4 +20,5 @@ attributes:
20
20
  type: string
21
21
  predicate: http://id.loc.gov/vocabulary/relators/dpt
22
22
  index_keys:
23
+ - "depositor_ssim"
23
24
  - "depositor_tesim"
@@ -68,14 +68,14 @@ services:
68
68
  chrome:
69
69
  image: selenium/standalone-chromium:4
70
70
  environment:
71
- # - START_XVFB=false
71
+ # - START_XVFB=false
72
72
  - SE_NODE_SESSION_TIMEOUT=800
73
73
  - SE_ENABLE_TRACING=false
74
74
  - SE_ENABLE_BROWSER_LEFTOVERS_CLEANUP=true
75
75
  - SE_BROWSER_ARGS_DISABLE_DSHM=--disable-dev-shm-usage
76
76
  - SE_BROWSER_ARGS_HEADLESS=--headless=new
77
- # logging:
78
- # driver: none
77
+ # logging:
78
+ # driver: none
79
79
  volumes:
80
80
  - /dev/shm:/dev/shm
81
81
  shm_size: 2g
@@ -118,7 +118,7 @@ services:
118
118
  memcached:
119
119
  image: bitnami/memcached
120
120
  ports:
121
- - '11211:11211'
121
+ - "11211:11211"
122
122
  networks:
123
123
  - hyrax
124
124
 
@@ -32,7 +32,7 @@ You can also try [Running Hyrax-based application in local VM](https://github.co
32
32
  During development, running only the dependent services in a container environment may be beneficial. This avoids potential headaches concerning file permissions and eases the use of debugging tools. The application generation instructions below use [Lando](https://lando.dev) to achieve this setup.
33
33
 
34
34
  This document contains instructions specific to setting up an app with __Hyrax
35
- v5.0.3__. If you are looking for instructions on installing a different
35
+ v5.0.5__. If you are looking for instructions on installing a different
36
36
  version, be sure to select the appropriate branch or tag from the drop-down
37
37
  menu above.
38
38
 
@@ -148,7 +148,7 @@ Generate a new Rails application using the template.
148
148
  **NOTE:** `HYRAX_SKIP_WINGS` is needed here to avoid loading the Wings compatibility layer during the application generation process.
149
149
 
150
150
  ```shell
151
- HYRAX_SKIP_WINGS=true rails _6.1.7.7_ new my_app --database=postgresql -m https://raw.githubusercontent.com/samvera/hyrax/hyrax-v5.0.3/template.rb
151
+ HYRAX_SKIP_WINGS=true rails _6.1.7.7_ new my_app --database=postgresql -m https://raw.githubusercontent.com/samvera/hyrax/hyrax-v5.0.5/template.rb
152
152
  ```
153
153
 
154
154
  Generating a new Rails application using Hyrax's template above takes cares of a number of steps for you, including:
@@ -13,6 +13,7 @@ module Freyja
13
13
  # was modified in the database between been read into memory and persisted
14
14
  # rubocop:disable Lint/UnusedMethodArgument
15
15
  def save(resource:, external_resource: false, perform_af_validation: false)
16
+ was_wings = resource.respond_to?(:wings?) && resource.wings?
16
17
  orm_object = resource_factory.from_resource(resource: resource)
17
18
  orm_object.transaction do
18
19
  orm_object.save!
@@ -23,18 +24,24 @@ module Freyja
23
24
  "Called from #{Gem.location_of_caller.join(':')}"
24
25
  end
25
26
  end
26
- convert_and_migrate_resource(orm_object)
27
+ convert_and_migrate_resource(orm_object, was_wings)
27
28
 
28
29
  rescue ActiveRecord::StaleObjectError
29
30
  raise Valkyrie::Persistence::StaleObjectError, "The object #{resource.id} has been updated by another process."
30
31
  end
31
32
  # rubocop:enable Lint/UnusedMethodArgument
32
33
 
33
- def convert_and_migrate_resource(orm_object)
34
+ def convert_and_migrate_resource(orm_object, was_wings)
34
35
  new_resource = resource_factory.to_resource(object: orm_object)
35
- if Hyrax.config.valkyrie_transition?
36
+ # if the resource was wings and is now a Valkyrie resource, we need to migrate sipity, files, and members
37
+ if Hyrax.config.valkyrie_transition? && was_wings && !new_resource.wings?
36
38
  MigrateFilesToValkyrieJob.perform_later(new_resource) if new_resource.is_a?(Hyrax::FileSet) && new_resource.file_ids.size == 1 && new_resource.file_ids.first.id.to_s.match('/files/')
37
- MigrateResourcesJob.perform_later(ids: new_resource.member_ids.map(&:to_s)) if new_resource.is_a?(Hyrax::Work)
39
+ # migrate any members if the resource is a Hyrax work
40
+ if new_resource.is_a?(Hyrax::Work)
41
+ member_ids = new_resource.member_ids.map(&:to_s)
42
+ MigrateResourcesJob.perform_later(ids: member_ids) unless member_ids.empty?
43
+ MigrateSipityEntityJob.perform_now(id: new_resource.id.to_s)
44
+ end
38
45
  end
39
46
  new_resource
40
47
  end
@@ -132,8 +132,23 @@ module Hyrax
132
132
  attr_writer :analytics_reporting
133
133
  attr_reader :analytics_reporting
134
134
  def analytics_reporting?
135
- @analytics_reporting ||=
135
+ @analytics_reporting ||= begin
136
+ required_env_vars = %w[
137
+ HYRAX_ANALYTICS_REPORTING
138
+ GOOGLE_ANALYTICS_ID
139
+ GOOGLE_ANALYTICS_PROPERTY_ID
140
+ ]
141
+
142
+ required_env_vars << if ENV['GOOGLE_ACCOUNT_JSON'].blank?
143
+ 'GOOGLE_ACCOUNT_JSON_PATH'
144
+ else
145
+ 'GOOGLE_ACCOUNT_JSON'
146
+ end
147
+
148
+ return false if required_env_vars.any? { |var| ENV.fetch(var, '').blank? }
149
+
136
150
  ActiveModel::Type::Boolean.new.cast(ENV.fetch('HYRAX_ANALYTICS_REPORTING', false))
151
+ end
137
152
  end
138
153
 
139
154
  # Currently supports 'google' or 'matomo'
@@ -141,7 +156,7 @@ module Hyrax
141
156
  attr_writer :analytics_provider
142
157
  def analytics_provider
143
158
  @analytics_provider ||=
144
- ENV.fetch('HYRAX_ANALYTICS_PROVIDER', 'google')
159
+ ENV.fetch('HYRAX_ANALYTICS_PROVIDER', 'ga4')
145
160
  end
146
161
 
147
162
  ##
@@ -477,31 +492,31 @@ module Hyrax
477
492
  # Path on the local file system where derivatives will be stored
478
493
  attr_writer :derivatives_path
479
494
  def derivatives_path
480
- @derivatives_path ||= ENV.fetch('HYRAX_DERIVATIVES_PATH', Rails.root.join('tmp', 'derivatives'))
495
+ @derivatives_path ||= Pathname.new(ENV.fetch('HYRAX_DERIVATIVES_PATH', Rails.root.join('tmp', 'derivatives')))
481
496
  end
482
497
 
483
498
  # Path on the local file system where originals will be staged before being ingested into Fedora.
484
499
  attr_writer :working_path
485
500
  def working_path
486
- @working_path ||= ENV.fetch('HYRAX_UPLOAD_PATH', Rails.root.join('tmp', 'uploads'))
501
+ @working_path ||= Pathname.new(ENV.fetch('HYRAX_UPLOAD_PATH', Rails.root.join('tmp', 'uploads')))
487
502
  end
488
503
 
489
504
  # @todo do we use both upload_path and working path?
490
505
  # Path on the local file system where originals will be staged before being ingested into Fedora.
491
506
  attr_writer :upload_path
492
507
  def upload_path
493
- @upload_path ||= ->() { ENV.fetch('HYRAX_UPLOAD_PATH') { Rails.root.join('tmp', 'uploads') } }
508
+ @upload_path ||= ->() { Pathname.new(ENV.fetch('HYRAX_UPLOAD_PATH') { Rails.root.join('tmp', 'uploads') }) }
494
509
  end
495
510
 
496
511
  attr_writer :cache_path
497
512
  def cache_path
498
- @cache_path ||= ->() { ENV.fetch('HYRAX_CACHE_PATH') { Rails.root.join('tmp', 'cache') } }
513
+ @cache_path ||= ->() { Pathname.new(ENV.fetch('HYRAX_CACHE_PATH') { Rails.root.join('tmp', 'cache') }) }
499
514
  end
500
515
 
501
516
  # Path on the local file system where where log and banners will be stored.
502
517
  attr_writer :branding_path
503
518
  def branding_path
504
- @branding_path ||= ENV.fetch('HYRAX_BRANDING_PATH', Rails.root.join('public', 'branding'))
519
+ @branding_path ||= Pathname.new(ENV.fetch('HYRAX_BRANDING_PATH', Rails.root.join('public', 'branding')))
505
520
  end
506
521
 
507
522
  # @!endgroup
@@ -20,10 +20,11 @@ module Hyrax
20
20
  ##
21
21
  # @param [Hyrax::Work] obj
22
22
  # @param [Enumerable<UploadedFile>] uploaded_files
23
- # @param [Enumerable<Hash>] file_set_params
23
+ # @param [Enumerable<Hash>] file_set_params or nil
24
24
  #
25
25
  # @return [Dry::Monads::Result]
26
26
  def call(obj, uploaded_files: [], file_set_params: [])
27
+ return Success(obj) if uploaded_files.empty? && file_set_params.blank? # Skip if no files to attach
27
28
  if @handler.new(work: obj).add(files: uploaded_files, file_set_params: file_set_params).attach
28
29
  file_sets = obj.member_ids.map do |member|
29
30
  Hyrax.query_service.find_by(id: member) if Hyrax.query_service.find_by(id: member).is_a? Hyrax::FileSet
data/lib/hyrax/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Hyrax
3
- VERSION = '5.0.3'
3
+ VERSION = '5.0.5'
4
4
  end
data/lib/hyrax.rb CHANGED
@@ -23,6 +23,7 @@ require 'hyrax/valkyrie_can_can_adapter'
23
23
  require 'retriable'
24
24
  require 'valkyrie/indexing_adapter'
25
25
  require 'valkyrie/indexing/solr/indexing_adapter'
26
+ require 'valkyrie/indexing/redis_queue/indexing_adapter'
26
27
  require 'valkyrie/indexing/null_indexing_adapter'
27
28
 
28
29
  ##
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+ module Valkyrie
3
+ module Indexing
4
+ module RedisQueue
5
+ class IndexingAdapter
6
+ ##
7
+ # @!attribute [r] connection
8
+ # @return [RSolr::Client]
9
+ attr_writer :connection
10
+ attr_accessor :index_queue_name, :delete_queue_name
11
+
12
+ ##
13
+ # @param connection [RSolr::Client] The RSolr connection to index to.
14
+ def initialize(connection: nil, index_queue_name: 'toindex', delete_queue_name: 'todelete')
15
+ @connection = connection
16
+ @index_queue_name = index_queue_name
17
+ @delete_queue_name = delete_queue_name
18
+ end
19
+
20
+ def connection
21
+ @connection ||= default_connection
22
+ end
23
+
24
+ def save(resource:)
25
+ persist([resource])
26
+ end
27
+
28
+ def save_all(resources:)
29
+ persist(resources)
30
+ end
31
+
32
+ # Deletes a Solr Document using the ID
33
+ # @return [Array<Valkyrie::Resource>] resources which have been deleted from Solr
34
+ def delete(resource:)
35
+ connection.zadd(delete_queue_name, Time.current.to_i, resource.id.to_s)
36
+ end
37
+
38
+ # Delete the Solr index of all Documents
39
+ def wipe!
40
+ connection.del(index_queue_name)
41
+ connection.del(index_queue_name + "-error")
42
+ connection.del(delete_queue_name)
43
+ connection.del(delete_queue_name + "-error")
44
+ end
45
+
46
+ def reset!
47
+ self.connection = default_connection
48
+ end
49
+
50
+ def index_queue(size: 200)
51
+ set = connection.zpopmin(index_queue_name, size)
52
+ return [] if set.blank?
53
+ # we have to load these one at a time because find_all_by_id gets duplicates during wings transition
54
+ resources = set.map { |id, _time| Hyrax.query_service.find_by(id: id) }
55
+ solr_indexer = Valkyrie::IndexingAdapter.find(:solr_index)
56
+ solr_indexer.save_all(resources: resources)
57
+ solr_indexer.connection.commit
58
+ rescue
59
+ # if anything goes wrong, try to requeue the items
60
+ set.each { |id, time| connection.zadd(index_queue_name + "-error", time, id) }
61
+ raise
62
+ end
63
+
64
+ # If a batch fails, try running them one at a time to get down to just records that really fail
65
+ def index_error_queue(size: 200)
66
+ size.times do
67
+ set = connection.zpopmin(index_queue_name + "-error", 1)
68
+ return [] if set.blank?
69
+ # we have to load these one at a time because find_all_by_id gets duplicates during wings transition
70
+ resources = set.map { |id, _time| Hyrax.query_service.find_by(id: id) }
71
+ solr_indexer = Valkyrie::IndexingAdapter.find(:solr_index)
72
+ solr_indexer.save_all(resources: resources)
73
+ solr_indexer.connection.commit
74
+ end
75
+ rescue
76
+ # if anything goes wrong, try to requeue the items
77
+ set.each { |id, _time| connection.zadd(index_queue_name + "-error", Time.now.to_i, id) }
78
+ raise
79
+ end
80
+
81
+ # We reach in to solr directly here to prevent needing to load the objects unnecessarily
82
+ def delete_queue(size: 200)
83
+ set = connection.zpopmin(delete_queue_name, size)
84
+ return [] if set.blank?
85
+ solr_indexer = Valkyrie::IndexingAdapter.find(:solr_index)
86
+ set.each do |id, _time|
87
+ solr_indexer.connection.delete_by_id id.to_s, { softCommit: true }
88
+ end
89
+ solr_indexer.connection.commit
90
+ rescue
91
+ # if anything goes wrong, try to requeue the items
92
+ set.each { |id, time| connection.zadd(delete_queue_name + "-error", time, id) }
93
+ raise
94
+ end
95
+
96
+ # If a batch fails, try running them one at a time to get down to just records that really fail
97
+ def delete_error_queue(size: 200)
98
+ size.times do
99
+ set = connection.zpopmin(delete_queue_name + "-error", 1)
100
+ return [] if set.blank?
101
+ solr_indexer = Valkyrie::IndexingAdapter.find(:solr_index)
102
+ set.each do |id, _time|
103
+ solr_indexer.connection.delete_by_id id.to_s, { softCommit: true }
104
+ end
105
+ solr_indexer.connection.commit
106
+ end
107
+ rescue
108
+ # if anything goes wrong, try to requeue the items
109
+ set.each { |id, _time| connection.zadd(delete_queue_name + "-error", Time.now.to_i, id) }
110
+ raise
111
+ end
112
+
113
+ def list_index
114
+ connection.zrange(index_queue_name, 0, -1, with_scores: true)
115
+ end
116
+
117
+ def list_delete
118
+ connection.zrange(delete_queue_name, 0, -1, with_scores: true)
119
+ end
120
+
121
+ def list_index_errors
122
+ connection.zrange(index_queue_name + "-error", 0, -1, with_scores: true)
123
+ end
124
+
125
+ def list_delete_errors
126
+ connection.zrange(delete_queue_name + "-error", 0, -1, with_scores: true)
127
+ end
128
+ private
129
+
130
+ def persist(resources)
131
+ resources.map do |r|
132
+ connection.zadd(index_queue_name, Time.current.to_i, r.id.to_s)
133
+ end
134
+ end
135
+
136
+ def default_connection
137
+ Hyrax.config.redis_connection
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
data/template.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
- gem 'hyrax', '5.0.3'
2
+ gem 'hyrax', '5.0.4'
3
3
  run 'bundle install'
4
4
  generate 'hyrax:install', '-f'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hyrax
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.3
4
+ version: 5.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
@@ -11,10 +11,9 @@ authors:
11
11
  - Jeremy Friesen
12
12
  - Trey Pendragon
13
13
  - Esmé Cowles
14
- autorequire:
15
14
  bindir: bin
16
15
  cert_chain: []
17
- date: 2025-02-04 00:00:00.000000000 Z
16
+ date: 2025-06-04 00:00:00.000000000 Z
18
17
  dependencies:
19
18
  - !ruby/object:Gem::Dependency
20
19
  name: rails
@@ -2068,6 +2067,7 @@ files:
2068
2067
  - app/jobs/characterize_job.rb
2069
2068
  - app/jobs/concerns/hyrax/members_permission_job_behavior.rb
2070
2069
  - app/jobs/concerns/hyrax/permission_job_behavior.rb
2070
+ - app/jobs/concerns/hyrax/queued_job_behavior.rb
2071
2071
  - app/jobs/content_delete_event_job.rb
2072
2072
  - app/jobs/content_deposit_event_job.rb
2073
2073
  - app/jobs/content_event_job.rb
@@ -2086,6 +2086,8 @@ files:
2086
2086
  - app/jobs/hyrax/grant_read_job.rb
2087
2087
  - app/jobs/hyrax/grant_read_to_members_job.rb
2088
2088
  - app/jobs/hyrax/propagate_change_depositor_job.rb
2089
+ - app/jobs/hyrax/queued_delete_job.rb
2090
+ - app/jobs/hyrax/queued_indexing_job.rb
2089
2091
  - app/jobs/hyrax/revoke_edit_from_members_job.rb
2090
2092
  - app/jobs/hyrax/revoke_edit_job.rb
2091
2093
  - app/jobs/iiif_manifest_cache_prewarm_job.rb
@@ -2097,6 +2099,7 @@ files:
2097
2099
  - app/jobs/lease_expiry_job.rb
2098
2100
  - app/jobs/migrate_files_to_valkyrie_job.rb
2099
2101
  - app/jobs/migrate_resources_job.rb
2102
+ - app/jobs/migrate_sipity_entity_job.rb
2100
2103
  - app/jobs/resolrize_job.rb
2101
2104
  - app/jobs/stream_notifications_job.rb
2102
2105
  - app/jobs/user_edit_profile_event_job.rb
@@ -3364,6 +3367,7 @@ files:
3364
3367
  - lib/tasks/universal_viewer.rake
3365
3368
  - lib/tasks/workflow.rake
3366
3369
  - lib/valkyrie/indexing/null_indexing_adapter.rb
3370
+ - lib/valkyrie/indexing/redis_queue/indexing_adapter.rb
3367
3371
  - lib/valkyrie/indexing/solr/indexing_adapter.rb
3368
3372
  - lib/valkyrie/indexing_adapter.rb
3369
3373
  - lib/wings.rb
@@ -3425,7 +3429,6 @@ licenses:
3425
3429
  - Apache-2.0
3426
3430
  metadata:
3427
3431
  rubygems_mfa_required: 'true'
3428
- post_install_message:
3429
3432
  rdoc_options: []
3430
3433
  require_paths:
3431
3434
  - lib
@@ -3440,8 +3443,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
3440
3443
  - !ruby/object:Gem::Version
3441
3444
  version: '0'
3442
3445
  requirements: []
3443
- rubygems_version: 3.5.22
3444
- signing_key:
3446
+ rubygems_version: 3.6.2
3445
3447
  specification_version: 4
3446
3448
  summary: Hyrax is a front-end based on the robust Samvera framework, providing a user
3447
3449
  interface for common repository features. Hyrax offers the ability to create repository