hyrax 5.0.4 → 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.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/app/controllers/concerns/hyrax/valkyrie_downloads_controller_behavior.rb +1 -0
- data/app/controllers/hyrax/admin/analytics/work_reports_controller.rb +4 -4
- data/app/jobs/batch_create_job.rb +2 -2
- data/app/jobs/concerns/hyrax/queued_job_behavior.rb +22 -0
- data/app/jobs/hyrax/queued_delete_job.rb +11 -0
- data/app/jobs/hyrax/queued_indexing_job.rb +11 -0
- data/app/jobs/migrate_files_to_valkyrie_job.rb +33 -21
- data/app/jobs/migrate_sipity_entity_job.rb +21 -0
- data/app/models/concerns/hyrax/solr_document_behavior.rb +5 -2
- data/app/models/hyrax/file_metadata.rb +22 -7
- data/app/services/hyrax/analytics/ga4/base.rb +1 -1
- data/app/services/hyrax/analytics/ga4.rb +5 -1
- data/app/services/hyrax/user_stat_importer.rb +1 -1
- data/app/services/migrate_resource_service.rb +1 -1
- data/app/views/hyrax/admin/analytics/collection_reports/_top_collections.html.erb +3 -7
- data/app/views/hyrax/admin/analytics/work_reports/_top_file_set_downloads.html.erb +3 -6
- data/app/views/hyrax/admin/analytics/work_reports/_top_works.html.erb +2 -3
- data/app/views/hyrax/admin/analytics/work_reports/_work_files.html.erb +1 -6
- data/chart/hyrax/Chart.yaml +1 -1
- data/config/initializers/indexing_adapter_initializer.rb +4 -0
- data/config/metadata/core_metadata.yaml +1 -0
- data/docker-compose-dassie.yml +4 -4
- data/documentation/developing-your-hyrax-based-app.md +2 -2
- data/lib/freyja/persister.rb +11 -4
- data/lib/hyrax/configuration.rb +22 -7
- data/lib/hyrax/transactions/steps/add_file_sets.rb +2 -1
- data/lib/hyrax/version.rb +1 -1
- data/lib/hyrax.rb +1 -0
- data/lib/valkyrie/indexing/redis_queue/indexing_adapter.rb +142 -0
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97ecad85144a7592892b75201b53b2d021541fb2a02188783149f51c07f89e31
|
4
|
+
data.tar.gz: '0943ac40e085f7beb166f24cbfc32fd90cdee77bf504f4457864ee145bb4e24a'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4380562339d0c5506e0fe73ec1a5970bf440e2f28398424d722b21a1d42632979a314adea6be892056d476d5fe94fb5da79e731decbcb01a03c794e425d54da2
|
7
|
+
data.tar.gz: ed3bfd922ff65c64b9d0c8411f5806f9ed80a9f2dcc03e1afcbc140e96e7e5bdf13fe4066ed6e3e76839a4edb61438c5c0e60b97707e592712f4617478ae7c33
|
data/Gemfile
CHANGED
@@ -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'),
|
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
|
-
|
37
|
+
private
|
38
38
|
|
39
39
|
def accessible_works
|
40
40
|
models = Hyrax::ModelRegistry.work_rdf_representations.map { |m| "\"#{m}\"" }
|
@@ -32,8 +32,8 @@ class BatchCreateJob < Hyrax::ApplicationJob
|
|
32
32
|
title = [titles[upload_id]] if titles[upload_id]
|
33
33
|
resource_type = Array.wrap(resource_types[upload_id]) if resource_types[upload_id]
|
34
34
|
job_attributes = job_attributes.merge(uploaded_files: [upload_id],
|
35
|
-
|
36
|
-
|
35
|
+
title: title,
|
36
|
+
resource_type: resource_type)
|
37
37
|
child_operation = Hyrax::Operation.create!(user: user,
|
38
38
|
operation_type: "Create Work",
|
39
39
|
parent: operation)
|
@@ -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
|
@@ -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
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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(
|
108
|
+
def container_for(path)
|
97
109
|
# we want the portion between the '-' and the '.'
|
98
|
-
file_blob = File.basename(
|
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
|
-
|
87
|
-
|
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
|
-
[
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
#
|
@@ -76,7 +76,11 @@ module Hyrax
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def account_info
|
79
|
-
@account_info ||=
|
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 =
|
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
|
@@ -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
|
-
|
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
|
|
data/chart/hyrax/Chart.yaml
CHANGED
data/docker-compose-dassie.yml
CHANGED
@@ -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
|
-
-
|
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.
|
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.
|
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:
|
data/lib/freyja/persister.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
data/lib/hyrax/configuration.rb
CHANGED
@@ -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', '
|
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
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
|
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.
|
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-
|
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.
|
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
|