hyrax 3.3.0 → 3.4.2

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 (255) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +26 -17
  3. data/.dassie/.env +2 -1
  4. data/.dassie/Gemfile +1 -1
  5. data/.dassie/app/forms/collection_resource_form.rb +8 -0
  6. data/.dassie/app/indexers/collection_resource_indexer.rb +8 -0
  7. data/.dassie/app/models/collection_resource.rb +35 -0
  8. data/.dassie/config/initializers/file_services.rb +4 -0
  9. data/.dassie/config/initializers/hyrax.rb +12 -1
  10. data/.dassie/config/metadata/collection_resource.yaml +23 -0
  11. data/.dassie/db/seeds.rb +74 -17
  12. data/.dassie/spec/forms/collection_resource_form_spec.rb +13 -0
  13. data/.dassie/spec/indexers/collection_resource_indexer_spec.rb +14 -0
  14. data/.dassie/spec/models/collection_resource_spec.rb +13 -0
  15. data/.github/release.yml +26 -0
  16. data/.gitignore +3 -0
  17. data/.regen +1 -1
  18. data/.rubocop.yml +1 -1
  19. data/.rubocop_fixme.yml +22 -3
  20. data/CONTAINERS.md +18 -13
  21. data/Dockerfile +4 -3
  22. data/app/actors/hyrax/actors/file_actor.rb +6 -4
  23. data/app/actors/hyrax/actors/transfer_request_actor.rb +3 -7
  24. data/app/assets/javascripts/hyrax/analytics_events.js +8 -2
  25. data/app/assets/javascripts/hyrax/autocomplete/linked_data.es6 +1 -3
  26. data/app/assets/javascripts/hyrax/collections_v2.es6 +13 -0
  27. data/app/controllers/concerns/hyrax/collections_controller_behavior.rb +1 -3
  28. data/app/controllers/concerns/hyrax/controller.rb +21 -0
  29. data/app/controllers/concerns/hyrax/works_controller_behavior.rb +83 -59
  30. data/app/controllers/hyrax/admin/admin_sets_controller.rb +105 -19
  31. data/app/controllers/hyrax/admin/permission_template_accesses_controller.rb +12 -19
  32. data/app/controllers/hyrax/batch_edits_controller.rb +12 -3
  33. data/app/controllers/hyrax/batch_uploads_controller.rb +4 -0
  34. data/app/controllers/hyrax/citations_controller.rb +1 -1
  35. data/app/controllers/hyrax/dashboard/collections_controller.rb +176 -83
  36. data/app/controllers/hyrax/single_use_links_viewer_controller.rb +1 -1
  37. data/app/forms/hyrax/forms/administrative_set_form.rb +19 -1
  38. data/app/forms/hyrax/forms/batch_edit_form.rb +1 -1
  39. data/app/forms/hyrax/forms/collection_form.rb +1 -1
  40. data/app/forms/hyrax/forms/dashboard/nest_collection_form.rb +21 -6
  41. data/app/forms/hyrax/forms/pcdm_collection_form.rb +30 -2
  42. data/app/forms/hyrax/forms/permission_template_form.rb +17 -9
  43. data/app/forms/hyrax/forms/resource_form.rb +23 -5
  44. data/app/forms/hyrax/forms/widgets/admin_set_visibility.rb +1 -1
  45. data/app/helpers/hyrax/collections_helper.rb +14 -0
  46. data/app/helpers/hyrax/membership_helper.rb +1 -1
  47. data/app/helpers/hyrax/trophy_helper.rb +1 -1
  48. data/app/helpers/hyrax/url_helper.rb +1 -1
  49. data/app/indexers/hyrax/administrative_set_indexer.rb +8 -2
  50. data/app/indexers/hyrax/deep_indexing_service.rb +1 -1
  51. data/app/indexers/hyrax/file_set_indexer.rb +1 -0
  52. data/app/indexers/hyrax/pcdm_collection_indexer.rb +3 -2
  53. data/app/indexers/hyrax/thumbnail_indexer.rb +31 -0
  54. data/app/indexers/hyrax/valkyrie_file_set_indexer.rb +6 -6
  55. data/app/indexers/hyrax/valkyrie_indexer.rb +4 -2
  56. data/app/indexers/hyrax/valkyrie_work_indexer.rb +13 -0
  57. data/app/inputs/controlled_vocabulary_input.rb +2 -0
  58. data/app/jobs/change_depositor_event_job.rb +47 -0
  59. data/app/jobs/characterize_job.rb +43 -3
  60. data/app/jobs/concerns/hyrax/members_permission_job_behavior.rb +1 -1
  61. data/app/jobs/content_depositor_change_event_job.rb +2 -1
  62. data/app/jobs/hyrax/propagate_change_depositor_job.rb +32 -0
  63. data/app/jobs/import_url_job.rb +4 -6
  64. data/app/jobs/inherit_permissions_job.rb +1 -1
  65. data/app/jobs/valkyrie_create_derivatives_job.rb +25 -0
  66. data/app/jobs/valkyrie_ingest_job.rb +41 -35
  67. data/app/models/admin_set.rb +10 -2
  68. data/app/models/collection_branding_info.rb +8 -6
  69. data/app/models/concerns/hyrax/collection_behavior.rb +3 -3
  70. data/app/models/concerns/hyrax/file_set/characterization.rb +7 -1
  71. data/app/models/concerns/hyrax/solr_document/metadata.rb +1 -0
  72. data/app/models/concerns/hyrax/solr_document_behavior.rb +9 -3
  73. data/app/models/concerns/hyrax/user.rb +11 -0
  74. data/app/models/concerns/hyrax/work_behavior.rb +1 -1
  75. data/app/models/featured_work_list.rb +0 -1
  76. data/app/models/hyrax/administrative_set.rb +36 -1
  77. data/app/models/hyrax/collection_type.rb +2 -2
  78. data/app/models/hyrax/file_metadata.rb +37 -3
  79. data/app/models/hyrax/file_set.rb +43 -4
  80. data/app/models/hyrax/group.rb +19 -0
  81. data/app/models/hyrax/pcdm_collection.rb +56 -1
  82. data/app/models/hyrax/permission_template.rb +11 -5
  83. data/app/models/hyrax/work.rb +91 -0
  84. data/app/models/job_io_wrapper.rb +1 -1
  85. data/app/models/proxy_deposit_request.rb +1 -1
  86. data/app/presenters/hyrax/admin_set_presenter.rb +2 -2
  87. data/app/presenters/hyrax/member_presenter_factory.rb +2 -4
  88. data/app/presenters/hyrax/pcdm_member_presenter_factory.rb +2 -2
  89. data/app/presenters/hyrax/work_show_presenter.rb +10 -6
  90. data/app/search_builders/hyrax/dashboard/collections_search_builder.rb +2 -2
  91. data/app/search_builders/hyrax/dashboard/managed_search_filters.rb +44 -4
  92. data/app/search_builders/hyrax/dashboard/nested_collections_search_builder.rb +2 -2
  93. data/app/search_builders/hyrax/my/collections_search_builder.rb +11 -4
  94. data/app/services/hyrax/access_control_list.rb +20 -6
  95. data/app/services/hyrax/adapters/nesting_index_adapter.rb +3 -3
  96. data/app/services/hyrax/admin_set_create_service.rb +21 -37
  97. data/app/services/hyrax/change_content_depositor_service.rb +2 -2
  98. data/app/services/hyrax/change_depositor_service.rb +70 -0
  99. data/app/services/hyrax/characterization/valkyrie_characterization_service.rb +4 -6
  100. data/app/services/hyrax/collections/collection_member_service.rb +3 -5
  101. data/app/services/hyrax/collections/nested_collection_query_service.rb +24 -12
  102. data/app/services/hyrax/custom_queries/navigators/child_file_sets_navigator.rb +45 -0
  103. data/app/services/hyrax/custom_queries/navigators/child_filesets_navigator.rb +7 -2
  104. data/app/services/hyrax/custom_queries/navigators/parent_work_navigator.rb +54 -0
  105. data/app/services/hyrax/default_middleware_stack.rb +3 -0
  106. data/app/services/hyrax/file_set_derivatives_service.rb +21 -2
  107. data/app/services/hyrax/file_set_type_service.rb +2 -5
  108. data/app/services/hyrax/listeners/file_metadata_listener.rb +31 -1
  109. data/app/services/hyrax/listeners/member_cleanup_listener.rb +27 -11
  110. data/app/services/hyrax/listeners/metadata_index_listener.rb +39 -0
  111. data/app/services/hyrax/listeners/proxy_deposit_listener.rb +14 -8
  112. data/app/services/hyrax/location_service.rb +33 -0
  113. data/app/services/hyrax/multiple_membership_checker.rb +46 -1
  114. data/app/services/hyrax/resource_visibility_propagator.rb +1 -1
  115. data/app/services/hyrax/simple_schema_loader.rb +5 -1
  116. data/app/services/hyrax/solr_query_service.rb +12 -7
  117. data/app/services/hyrax/thumbnail_path_service.rb +1 -1
  118. data/app/services/hyrax/valkyrie_persist_derivatives.rb +50 -0
  119. data/app/services/hyrax/valkyrie_upload.rb +94 -0
  120. data/app/services/hyrax/work_uploads_handler.rb +0 -10
  121. data/app/services/hyrax/workflow/workflow_importer.rb +7 -9
  122. data/app/services/hyrax/workflow/workflow_schema.rb +3 -5
  123. data/app/strategies/hyrax/strategies/yaml_strategy.rb +4 -6
  124. data/app/uploaders/hyrax/uploaded_file_uploader.rb +4 -4
  125. data/app/utils/hyrax/data_destroyers/collection_branding_destroyer.rb +29 -0
  126. data/app/utils/hyrax/data_destroyers/collection_types_destroyer.rb +26 -0
  127. data/app/utils/hyrax/data_destroyers/default_admin_set_id_cache_destroyer.rb +26 -0
  128. data/app/utils/hyrax/data_destroyers/featured_works_destroyer.rb +27 -0
  129. data/app/utils/hyrax/data_destroyers/permission_templates_destroyer.rb +30 -0
  130. data/app/utils/hyrax/data_destroyers/repository_metadata_destroyer.rb +42 -0
  131. data/app/utils/hyrax/data_destroyers/stats_destroyer.rb +33 -0
  132. data/app/utils/hyrax/data_maintenance.rb +51 -0
  133. data/app/utils/hyrax/required_data_seeder.rb +21 -0
  134. data/app/utils/hyrax/required_data_seeders/collection_seeder.rb +26 -0
  135. data/app/utils/hyrax/required_data_seeders/collection_type_seeder.rb +36 -0
  136. data/app/utils/hyrax/test_data_seeder.rb +24 -0
  137. data/app/utils/hyrax/test_data_seeders/collection_seeder.rb +91 -0
  138. data/app/utils/hyrax/test_data_seeders/collection_type_seeder.rb +72 -0
  139. data/app/utils/hyrax/test_data_seeders/user_seeder.rb +52 -0
  140. data/app/validators/hyrax/collection_membership_validator.rb +39 -0
  141. data/app/views/catalog/_index_header_list_default.html.erb +8 -1
  142. data/app/views/catalog/_thumbnail_list_default.html.erb +8 -3
  143. data/app/views/collections/edit_fields/_based_near.html.erb +7 -7
  144. data/app/views/hyrax/admin/admin_sets/_form_participant_table.html.erb +2 -2
  145. data/app/views/hyrax/admin/admin_sets/_form_participants.html.erb +2 -2
  146. data/app/views/hyrax/admin/admin_sets/_form_visibility.html.erb +2 -2
  147. data/app/views/hyrax/admin/admin_sets/_form_workflow.erb +1 -1
  148. data/app/views/hyrax/admin/collection_types/index.html.erb +1 -1
  149. data/app/views/hyrax/base/_form.html.erb +1 -1
  150. data/app/views/hyrax/base/_form_child_work_relationships.html.erb +1 -1
  151. data/app/views/hyrax/dashboard/collections/_default_group.html.erb +2 -2
  152. data/app/views/hyrax/dashboard/collections/_form.html.erb +24 -17
  153. data/app/views/hyrax/dashboard/collections/_form_branding.html.erb +1 -0
  154. data/app/views/hyrax/dashboard/collections/_form_discovery.html.erb +6 -3
  155. data/app/views/hyrax/dashboard/collections/_form_share.html.erb +2 -2
  156. data/app/views/hyrax/dashboard/collections/_form_share_table.html.erb +3 -3
  157. data/app/views/hyrax/dashboard/collections/_list_collections.html.erb +2 -2
  158. data/app/views/hyrax/dashboard/sidebar/_activity.html.erb +1 -1
  159. data/app/views/hyrax/dashboard/works/_default_group.html.erb +1 -1
  160. data/app/views/hyrax/dashboard/works/_list_works.html.erb +1 -1
  161. data/app/views/hyrax/file_sets/_actions.html.erb +2 -2
  162. data/app/views/hyrax/my/_facet_pagination.html.erb +12 -9
  163. data/app/views/hyrax/my/_work_action_menu.html.erb +8 -9
  164. data/app/views/hyrax/my/collections/_default_group.html.erb +2 -2
  165. data/app/views/hyrax/my/collections/_list_collections.html.erb +2 -2
  166. data/app/views/hyrax/my/collections/index.html.erb +3 -2
  167. data/app/views/hyrax/my/works/_default_group.html.erb +1 -1
  168. data/app/views/hyrax/my/works/_list_works.html.erb +1 -2
  169. data/app/views/hyrax/my/works/_tabs.html.erb +6 -1
  170. data/app/views/hyrax/my/works/index.html.erb +4 -2
  171. data/chart/hyrax/Chart.yaml +11 -7
  172. data/chart/hyrax/README.md +22 -1
  173. data/chart/hyrax/templates/_helpers.tpl +4 -0
  174. data/chart/hyrax/templates/cron-embargo.yaml +5 -0
  175. data/chart/hyrax/templates/cron-lease.yaml +5 -0
  176. data/chart/hyrax/templates/deployment-worker.yaml +11 -0
  177. data/chart/hyrax/templates/ingress.yaml +7 -6
  178. data/chart/hyrax/values.yaml +152 -0
  179. data/config/features.rb +48 -50
  180. data/config/initializers/listeners.rb +0 -1
  181. data/config/initializers/{valkryrie_storage.rb → storage_adapter_initializer.rb} +5 -0
  182. data/config/locales/hyrax.de.yml +18 -17
  183. data/config/locales/hyrax.en.yml +30 -28
  184. data/config/locales/hyrax.es.yml +10 -9
  185. data/config/locales/hyrax.fr.yml +2 -1
  186. data/config/locales/hyrax.it.yml +3 -2
  187. data/config/locales/hyrax.pt-BR.yml +2 -1
  188. data/config/locales/hyrax.zh.yml +2 -1
  189. data/config/metadata/basic_metadata.yaml +2 -0
  190. data/config/metadata/core_metadata.yaml +1 -1
  191. data/docker-compose.yml +47 -42
  192. data/documentation/developing-your-hyrax-based-app.md +1 -1
  193. data/documentation/legacyREADME.md +1 -1
  194. data/hyrax.gemspec +5 -3
  195. data/lib/generators/hyrax/collection_resource/USAGE +20 -0
  196. data/lib/generators/hyrax/collection_resource/collection_resource_generator.rb +133 -0
  197. data/lib/generators/hyrax/collection_resource/templates/collection.rb.erb +34 -0
  198. data/lib/generators/hyrax/collection_resource/templates/collection_form.rb.erb +7 -0
  199. data/lib/generators/hyrax/collection_resource/templates/collection_form_spec.rb.erb +13 -0
  200. data/lib/generators/hyrax/collection_resource/templates/collection_indexer.rb.erb +7 -0
  201. data/lib/generators/hyrax/collection_resource/templates/collection_indexer_spec.rb.erb +13 -0
  202. data/lib/generators/hyrax/collection_resource/templates/collection_metadata.yaml +22 -0
  203. data/lib/generators/hyrax/collection_resource/templates/collection_spec.rb.erb +12 -0
  204. data/lib/generators/hyrax/install_generator.rb +9 -0
  205. data/lib/hyrax/administrative_set_name.rb +18 -0
  206. data/lib/hyrax/collection_name.rb +2 -0
  207. data/lib/hyrax/configuration.rb +22 -0
  208. data/lib/hyrax/controlled_vocabularies/location.rb +9 -2
  209. data/lib/hyrax/controlled_vocabularies/resource_label_caching.rb +42 -0
  210. data/lib/hyrax/controlled_vocabularies.rb +1 -0
  211. data/lib/hyrax/publisher.rb +49 -0
  212. data/lib/hyrax/schema.rb +16 -13
  213. data/lib/hyrax/specs/capybara.rb +1 -1
  214. data/lib/hyrax/specs/shared_specs/hydra_works.rb +11 -5
  215. data/lib/hyrax/specs/shared_specs/indexers.rb +117 -3
  216. data/lib/hyrax/transactions/admin_set_create.rb +2 -1
  217. data/lib/hyrax/transactions/admin_set_destroy.rb +22 -0
  218. data/lib/hyrax/transactions/admin_set_update.rb +21 -0
  219. data/lib/hyrax/transactions/collection_destroy.rb +22 -0
  220. data/lib/hyrax/transactions/collection_update.rb +5 -2
  221. data/lib/hyrax/transactions/container.rb +97 -23
  222. data/lib/hyrax/transactions/create_work.rb +3 -0
  223. data/lib/hyrax/transactions/destroy_work.rb +3 -0
  224. data/lib/hyrax/transactions/steps/apply_collection_permission_template.rb +2 -0
  225. data/lib/hyrax/transactions/steps/apply_permission_template.rb +2 -0
  226. data/lib/hyrax/transactions/steps/apply_visibility.rb +2 -0
  227. data/lib/hyrax/transactions/steps/change_depositor.rb +46 -0
  228. data/lib/hyrax/transactions/steps/check_for_empty_admin_set.rb +36 -0
  229. data/lib/hyrax/transactions/steps/delete_access_control.rb +32 -0
  230. data/lib/hyrax/transactions/steps/delete_resource.rb +19 -3
  231. data/lib/hyrax/transactions/steps/destroy_work.rb +3 -1
  232. data/lib/hyrax/transactions/steps/ensure_permission_template.rb +2 -0
  233. data/lib/hyrax/transactions/steps/save.rb +24 -6
  234. data/lib/hyrax/transactions/steps/save_access_control.rb +2 -2
  235. data/lib/hyrax/transactions/steps/save_collection_banner.rb +59 -0
  236. data/lib/hyrax/transactions/steps/save_collection_logo.rb +109 -0
  237. data/lib/hyrax/transactions/steps/save_work.rb +3 -0
  238. data/lib/hyrax/transactions/steps/set_user_as_creator.rb +41 -0
  239. data/lib/hyrax/transactions/steps/update_work_members.rb +51 -0
  240. data/lib/hyrax/transactions/update_work.rb +4 -3
  241. data/lib/hyrax/transactions/work_create.rb +1 -1
  242. data/lib/hyrax/transactions/work_destroy.rb +2 -1
  243. data/lib/hyrax/transactions/work_update.rb +19 -0
  244. data/lib/hyrax/version.rb +1 -1
  245. data/lib/wings/active_fedora_converter/file_metadata_node.rb +48 -0
  246. data/lib/wings/active_fedora_converter/instance_builder.rb +68 -0
  247. data/lib/wings/active_fedora_converter.rb +3 -3
  248. data/lib/wings/attribute_transformer.rb +5 -1
  249. data/lib/wings/services/custom_queries/find_file_metadata.rb +19 -8
  250. data/lib/wings/setup.rb +3 -1
  251. data/lib/wings/valkyrie/persister.rb +2 -0
  252. data/lib/wings/valkyrie/query_service.rb +6 -7
  253. data/lib/wings/valkyrie/storage.rb +7 -1
  254. data/template.rb +1 -1
  255. metadata +99 -12
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyrax
4
+ module CustomQueries
5
+ module Navigators
6
+ ##
7
+ # Navigate from a resource to it's parent work.
8
+ #
9
+ # @see https://github.com/samvera/valkyrie/wiki/Queries#custom-queries
10
+ # @since 3.4.0
11
+ class ParentWorkNavigator
12
+ # Define the queries that can be fulfilled by this navigator.
13
+ def self.queries
14
+ [:find_parent_work, :find_parent_work_id]
15
+ end
16
+
17
+ attr_reader :query_service
18
+
19
+ def initialize(query_service:)
20
+ @query_service = query_service
21
+ end
22
+
23
+ ##
24
+ # Find parent work of a given resource, and map to Valkyrie Resources
25
+ # @note There should be only one parent resource. A warning is logged if
26
+ # more than one resource is found and the first of the resources is returned.
27
+ # @param [Valkyrie::Resource] resource
28
+ #
29
+ # @return [Array<Valkyrie::Resource>]
30
+ def find_parent_work(resource:)
31
+ results = Hyrax.query_service.find_inverse_references_by(resource: resource,
32
+ property: :member_ids).select(&:work?)
33
+ if results.count > 1
34
+ Hyrax.logger.warn("#{resource.work? ? 'Work' : 'File set'} " \
35
+ "#{resource.id} is in #{results.count} works when it " \
36
+ "should be in no more than one.")
37
+ end
38
+ results.first
39
+ end
40
+
41
+ ##
42
+ # Find the id of the parent work of a given resource, and map to Valkyrie Resources IDs
43
+ # @note There should be only one parent resource. A warning is logged if
44
+ # more than one resource is found and the first of the resources is returned.
45
+ # @param [Valkyrie::Resource] resource
46
+ #
47
+ # @return [Array<Valkyrie::ID>]
48
+ def find_parent_work_id(resource:)
49
+ find_parent_work(resource: resource)&.id
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -42,6 +42,9 @@ module Hyrax
42
42
  # Decode the private/public/institution on the form into permisisons on
43
43
  # the model
44
44
  middleware.use Hyrax::Actors::InterpretVisibilityActor
45
+ #
46
+ # Handles transfering ownership of works from one user to another
47
+ middleware.use Hyrax::Actors::TransferRequestActor
45
48
 
46
49
  # Copies default permissions from the PermissionTemplate to the work
47
50
  middleware.use Hyrax::Actors::ApplyPermissionTemplateActor
@@ -3,13 +3,22 @@ module Hyrax
3
3
  # Responsible for creating and cleaning up the derivatives of a file_set
4
4
  class FileSetDerivativesService
5
5
  attr_reader :file_set
6
- delegate :uri, :mime_type, to: :file_set
6
+ delegate :mime_type, to: :file_set
7
7
 
8
8
  # @param file_set [Hyrax::FileSet] At least for this class, it must have #uri and #mime_type
9
9
  def initialize(file_set)
10
10
  @file_set = file_set
11
11
  end
12
12
 
13
+ def uri
14
+ # If given a FileMetadata object, use its parent ID.
15
+ if file_set.respond_to?(:file_set_id)
16
+ file_set.file_set_id.to_s
17
+ else
18
+ file_set.uri
19
+ end
20
+ end
21
+
13
22
  def cleanup_derivatives
14
23
  derivative_path_factory.derivatives_for_reference(file_set).each do |path|
15
24
  FileUtils.rm_f(path)
@@ -33,12 +42,22 @@ module Hyrax
33
42
  # The destination_name parameter has to match up with the file parameter
34
43
  # passed to the DownloadsController
35
44
  def derivative_url(destination_name)
36
- path = derivative_path_factory.derivative_path_for_reference(file_set, destination_name)
45
+ path = derivative_path_factory.derivative_path_for_reference(derivative_url_target, destination_name)
37
46
  URI("file://#{path}").to_s
38
47
  end
39
48
 
40
49
  private
41
50
 
51
+ # If given a FileMetadata object pass the file_set_id for derivative URL
52
+ # creation.
53
+ def derivative_url_target
54
+ if file_set.try(:file_set_id)
55
+ file_set.file_set_id.to_s
56
+ else
57
+ file_set
58
+ end
59
+ end
60
+
42
61
  def supported_mime_types
43
62
  file_set.class.pdf_mime_types +
44
63
  file_set.class.office_document_mime_types +
@@ -17,13 +17,10 @@ module Hyrax
17
17
  attr_reader :file_set
18
18
 
19
19
  ##
20
- # @todo make `file_set_characterization_proxy` (or something better?)
21
- # application-level configuration.
22
- #
23
20
  # @param [Hyrax::FileSet] file_set
24
21
  # @param [Symbol] characterization_proxy defaults to the setting provided by
25
22
  # the application's ActiveFedora `FileSet` class.
26
- def initialize(file_set:, characterization_proxy: ::FileSet.characterization_proxy, query_service: Hyrax.custom_queries)
23
+ def initialize(file_set:, characterization_proxy: Hyrax.config.characterization_proxy, query_service: Hyrax.custom_queries)
27
24
  @file_set = file_set
28
25
  @proxy_use = Hyrax::FileMetadata::Use.uri_for(use: characterization_proxy)
29
26
  @queries = query_service
@@ -48,7 +45,7 @@ module Hyrax
48
45
  private
49
46
 
50
47
  def audio_types
51
- return ::FileSet.audio_mime_types if defined?(::FileSet)
48
+ return ::FileSet.audio_mime_types if defined?(::FileSet) && ::FileSet.respond_to?(:audio_mime_types)
52
49
  DEFAULT_AUDIO_TYPES
53
50
  end
54
51
  end
@@ -3,13 +3,43 @@
3
3
  module Hyrax
4
4
  module Listeners
5
5
  ##
6
- # Listens for events related to Hyrax::FileMetadata
6
+ # Listens for events related to {Hyrax::FileMetadata}
7
7
  class FileMetadataListener
8
+ ##
9
+ # Called when 'file.characterized' event is published;
10
+ # allows post-characterization handling, like derivatives generation.
11
+ #
12
+ # @param [Dry::Events::Event] event
13
+ # @return [void]
14
+ def on_file_characterized(event)
15
+ CreateDerivativesJob
16
+ .perform_later(event[:file_set], event[:file_id], event[:path_hint])
17
+ end
18
+
19
+ ##
20
+ # Called when 'file.metadata.updated' event is published; reindexes a
21
+ # {Hyrax::FileSet} when a file claiming to be its `pcdm_use:OriginalFile`
22
+ #
23
+ # @param [Dry::Events::Event] event
24
+ # @return [void]
25
+ def on_file_metadata_updated(event)
26
+ return unless event[:metadata].original_file?
27
+
28
+ file_set = Hyrax.query_service.find_by(id: event[:metadata].file_set_id)
29
+ Hyrax.index_adapter.save(resource: file_set)
30
+ rescue Valkyrie::Persistence::ObjectNotFoundError => err
31
+ Hyrax.logger.warn "tried to index file with id #{event[:metadata].id} " \
32
+ "in response to an event of type #{event.id} but " \
33
+ "encountered an error #{err.message}. should this " \
34
+ "object be in a FileSet #{event[:metadata]}"
35
+ end
36
+
8
37
  ##
9
38
  # Called when 'object.file.uploaded' event is published
10
39
  # @param [Dry::Events::Event] event
11
40
  # @return [void]
12
41
  def on_object_file_uploaded(event)
42
+ # Run characterization
13
43
  Hyrax.config
14
44
  .characterization_service
15
45
  .run(metadata: event[:metadata], file: event[:metadata].file)
@@ -3,7 +3,7 @@
3
3
  module Hyrax
4
4
  module Listeners
5
5
  ##
6
- # Listens for object deleted events and cleans up associated members
6
+ # Listens for resource deleted events and cleans up associated members
7
7
  class MemberCleanupListener
8
8
  # Called when 'object.deleted' event is published
9
9
  # @param [Dry::Events::Event] event
@@ -12,16 +12,32 @@ module Hyrax
12
12
  return unless event.payload.key?(:object) # legacy callback
13
13
  return if event[:object].is_a?(ActiveFedora::Base) # handled by legacy code
14
14
 
15
- Hyrax.custom_queries.find_child_filesets(resource: event[:object]).each do |file_set|
16
- begin
17
- Hyrax.persister.delete(resource: file_set)
18
- Hyrax.publisher
19
- .publish('object.deleted', object: file_set, id: file_set.id, user: user)
20
- rescue StandardError # we don't uncaught errors looping filesets
21
- Hyrax.logger.warn "Failed to delete #{file_set.class}:#{file_set.id} " \
22
- "during cleanup for resource: #{event[:object]}. " \
23
- 'This member may now be orphaned.'
24
- end
15
+ Hyrax.custom_queries.find_child_file_sets(resource: event[:object]).each do |file_set|
16
+ Hyrax.persister.delete(resource: file_set)
17
+ Hyrax.publisher
18
+ .publish('object.deleted', object: file_set, id: file_set.id, user: event[:user])
19
+ rescue StandardError # we don't uncaught errors looping filesets
20
+ Hyrax.logger.warn "Failed to delete #{file_set.class}:#{file_set.id} " \
21
+ "during cleanup for resource: #{event[:object]}. " \
22
+ 'This member may now be orphaned.'
23
+ end
24
+ end
25
+
26
+ # Called when 'collection.deleted' event is published
27
+ # @param [Dry::Events::Event] event
28
+ # @return [void]
29
+ def on_collection_deleted(event)
30
+ return unless event.payload.key?(:collection) # legacy callback
31
+ return if event[:collection].is_a?(ActiveFedora::Base) # handled by legacy code
32
+
33
+ Hyrax.custom_queries.find_members_of(collection: event[:collection]).each do |resource|
34
+ resource.member_of_collection_ids -= [event[:collection].id]
35
+ Hyrax.persister.save(resource: resource)
36
+ Hyrax.publisher
37
+ .publish('collection.membership.updated', collection: event[:collection], user: event[:user])
38
+ rescue StandardError
39
+ Hyrax.logger.warn "Failed to remove collection reference from #{work.class}:#{work.id} " \
40
+ "during cleanup for collection: #{event[:collection]}. "
25
41
  end
26
42
  end
27
43
  end
@@ -22,6 +22,34 @@ module Hyrax
22
22
  Hyrax.index_adapter.save(resource: event[:collection])
23
23
  end
24
24
 
25
+ ##
26
+ # Re-index the resource.
27
+ #
28
+ # Called when 'file.metadata.updated' event is published
29
+ # @param [Dry::Events::Event] event
30
+ # @return [void]
31
+ def on_file_metadata_updated(event)
32
+ return unless resource? event[:metadata]
33
+ Hyrax.index_adapter.save(resource: event[:metadata])
34
+ end
35
+
36
+ ##
37
+ # Re-index the resource.
38
+ #
39
+ # Called when 'object.membership.updated' event is published
40
+ # @param [Dry::Events::Event] event
41
+ # @return [void]
42
+ def on_object_membership_updated(event)
43
+ resource = event.to_h.fetch(:object) { Hyrax.query_service.find_by(id: event[:object_id]) }
44
+ return unless resource?(resource)
45
+
46
+ Hyrax.index_adapter.save(resource: resource)
47
+ rescue Valkyrie::Persistence::ObjectNotFoundError => err
48
+ Hyrax.logger.error("Tried to index for an #{event.id} event with " \
49
+ "payload #{event.payload}, but failed due to error:\n"\
50
+ "\t#{err.message}")
51
+ end
52
+
25
53
  ##
26
54
  # Re-index the resource.
27
55
  #
@@ -44,6 +72,17 @@ module Hyrax
44
72
  Hyrax.index_adapter.delete(resource: event[:object])
45
73
  end
46
74
 
75
+ ##
76
+ # Remove the resource from the index.
77
+ #
78
+ # Called when 'collection.deleted' event is published
79
+ # @param [Dry::Events::Event] event
80
+ # @return [void]
81
+ def on_collection_deleted(event)
82
+ return unless resource?(event.payload[:collection])
83
+ Hyrax.index_adapter.delete(resource: event[:collection])
84
+ end
85
+
47
86
  private
48
87
 
49
88
  def resource?(resource)
@@ -3,19 +3,25 @@
3
3
  module Hyrax
4
4
  module Listeners
5
5
  ##
6
- # Listens for deposit events, and checks for proxy situations. When a user
6
+ # @deprecated transfer requests are now carried out synchronously during
7
+ # object save
8
+ #
9
+ ## Listens for deposit events, and checks for proxy situations. When a user
7
10
  # deposits an item `on_behalf_of` another, ensures transfer is handled.
8
11
  class ProxyDepositListener
9
12
  ##
10
13
  # Called when 'object.deposited' event is published
11
- # @param [Dry::Events::Event] event
14
+ # @param [Dry::Events::Event] _event
12
15
  # @return [void]
13
- def on_object_deposited(event)
14
- return if event[:object].try(:on_behalf_of).blank? ||
15
- (event[:object].on_behalf_of == event[:object].depositor)
16
-
17
- ContentDepositorChangeEventJob
18
- .perform_later(event[:object], ::User.find_by_user_key(event[:object].on_behalf_of))
16
+ def on_object_deposited(_event)
17
+ Deprecation.warn(
18
+ "The ProxyDepositListener was deprecated, effective immediately, in \
19
+ response to a difficult-to-diagnose race condition bug. This listener \
20
+ is now a no-op. To retain functionality ensure that \
21
+ DefaultMiddlewareStack is configured to use \
22
+ Hyrax::Actors::TransferRequestActor. To quiet this deprecation remove any \
23
+ local initializer configuration that subscribes this listener."
24
+ )
19
25
  end
20
26
  end
21
27
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ class LocationService < ::Qa::Authorities::Geonames
4
+ CACHE_KEY_PREFIX = 'hyrax_geonames_label-v1-'
5
+ CACHE_EXPIRATION = 1.week
6
+
7
+ def full_label(uri)
8
+ return if uri.blank?
9
+ id = extract_id uri
10
+ Rails.cache.fetch(cache_key(id), expires_in: CACHE_EXPIRATION) do
11
+ label.call(find(id))
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def extract_id(obj)
18
+ uri = case obj
19
+ when String
20
+ URI(obj)
21
+ when URI
22
+ obj
23
+ else
24
+ raise ArgumentError, "#{obj} is not a valid type"
25
+ end
26
+ uri.path.split('/').last
27
+ end
28
+
29
+ def cache_key(id)
30
+ "#{CACHE_KEY_PREFIX}#{id}"
31
+ end
32
+ end
33
+ end
@@ -11,6 +11,28 @@ module Hyrax
11
11
  @item = item
12
12
  end
13
13
 
14
+ # @api public
15
+ #
16
+ # Validate member_of_collection_ids does not have any membership conflicts
17
+ # based on the collection types of the members.
18
+ #
19
+ # Collections that have a collection type declaring
20
+ # `allow_multiple_membership` as `false` require that its members do not
21
+ # also belong to other collections of the same type.
22
+ #
23
+ # @return [True, String] true if no conflicts; otherwise, an error message string
24
+ # @deprecated Use #check instead; for removal in 4.0.0
25
+ def validate
26
+ Deprecation.warn "#{self.class}##{__method__} is deprecated; use #check instead."
27
+ return true if item.member_of_collection_ids.empty? || item.member_of_collection_ids.count <= 1
28
+ return true unless single_membership_collection_types_exist?
29
+
30
+ collections_to_check = filter_to_single_membership_collections(item.member_of_collection_ids)
31
+ problematic_collections = check_collections(collections_to_check)
32
+ errs = build_error_message(problematic_collections)
33
+ errs.presence || true
34
+ end
35
+
14
36
  # @api public
15
37
  #
16
38
  # Scan a list of collection_ids for multiple single-membership collections.
@@ -42,15 +64,18 @@ module Hyrax
42
64
 
43
65
  private
44
66
 
67
+ # @return [Boolean] true if there a any collection types that restrict membership
45
68
  def single_membership_collection_types_exist?
46
69
  single_membership_collection_types_gids.present?
47
70
  end
48
71
 
72
+ # @return [Array<String] global ids of collection types that restrict membership
49
73
  def single_membership_collection_types_gids
50
74
  @single_membership_collection_types_gids ||=
51
75
  Hyrax::CollectionType.gids_that_do_not_allow_multiple_membership&.map(&:to_s)
52
76
  end
53
77
 
78
+ # @return [Array<Hyrax::PcdmCollection>] list of collection instances for single membership collections
54
79
  def filter_to_single_membership_collections(collection_ids)
55
80
  return [] if collection_ids.blank?
56
81
  field_pairs = {
@@ -58,11 +83,14 @@ module Hyrax
58
83
  }
59
84
  Hyrax::SolrQueryService.new
60
85
  .with_generic_type(generic_type: "Collection")
61
- .with_ids(ids: Array[collection_ids])
86
+ .with_ids(ids: Array(collection_ids).map(&:to_s))
62
87
  .with_field_pairs(field_pairs: field_pairs, join_with: ' OR ')
63
88
  .get_objects(use_valkyrie: true).to_a
64
89
  end
65
90
 
91
+ # @param proposed [Array<Hyrax::PcdmCollection>] collections with restricted membership that are being added
92
+ # @param include_current_members [Boolean] true if current collections for item should also be checked
93
+ # @return [Array<Hyrax::PcdmCollection>] collections with restricted membership
66
94
  def collections_to_check(proposed, include_current_members)
67
95
  # ActorStack does a wholesale collection membership replacement, such that
68
96
  # proposed collections include existing and new collections. Parameter
@@ -72,6 +100,19 @@ module Hyrax
72
100
  proposed | filter_to_single_membership_collections(item.member_of_collection_ids)
73
101
  end
74
102
 
103
+ # @param collections_to_check [Array<Hyrax::PcdmCollection>] collections with restricted membership
104
+ # @return [Array<Array>] collections groups by collection type
105
+ # @example example return result
106
+ # [
107
+ # [
108
+ # <Collection(id: 1, collection_type_gid: 1, ...)>,
109
+ # <Collection(id: 4, collection_type_gid: 1, ...)>
110
+ # ],
111
+ # [
112
+ # <Collection(id: 13, collection_type_gid: 8, ...)>,
113
+ # <Collection(id: 26, collection_type_gid: 8, ...)>
114
+ # ]
115
+ # ]
75
116
  def check_collections(collections_to_check)
76
117
  # uniq insures we include a collection only once when it is in the list multiple
77
118
  # group_by groups collections of the same collection type together
@@ -82,6 +123,7 @@ module Hyrax
82
123
  .select { |_gid, list| list.count > 1 }
83
124
  end
84
125
 
126
+ # @return [nil, String] nil if no errors; otherwise, errors appended into a single human readable message
85
127
  def build_error_message(problematic_collections)
86
128
  return if problematic_collections.blank?
87
129
  error_message_clauses = problematic_collections.map do |gid, list|
@@ -93,10 +135,13 @@ module Hyrax
93
135
  "#{error_message_clauses.join('; ')}"
94
136
  end
95
137
 
138
+ # @return [String] title of the collection type
96
139
  def collection_type_title_from_gid(gid)
97
140
  Hyrax::CollectionType.find_by_gid(gid).title
98
141
  end
99
142
 
143
+ # @return [String] comma separated (with and before final) list of titles
144
+ # @example "Title 1, Title 2, and Title 3"
100
145
  def collection_titles_from_list(collection_list)
101
146
  collection_list.map do |collection|
102
147
  collection.title.first
@@ -39,7 +39,7 @@ module Hyrax
39
39
  #
40
40
  # @raise [RuntimeError] if visibility propagation fails
41
41
  def propagate
42
- queries.find_child_filesets(resource: source).each do |file_set|
42
+ queries.find_child_file_sets(resource: source).each do |file_set|
43
43
  file_set.visibility = source.visibility
44
44
  embargo_manager.copy_embargo_to(target: file_set)
45
45
  lease_manager.copy_lease_to(target: file_set)
@@ -76,7 +76,11 @@ module Hyrax
76
76
  ##
77
77
  # @return [Dry::Types::Type]
78
78
  def type
79
- collection_type = config['multiple'] ? Valkyrie::Types::Array : Identity
79
+ collection_type = if config['multiple']
80
+ Valkyrie::Types::Array.constructor { |v| Array(v).select(&:present?) }
81
+ else
82
+ Identity
83
+ end
80
84
  collection_type.of(type_for(config['type']))
81
85
  end
82
86
 
@@ -59,10 +59,11 @@ module Hyrax
59
59
  end
60
60
 
61
61
  ##
62
+ # @param join_with [String] the connector (eg. 'AND', 'OR') used to join each clause (default: 'AND')
62
63
  # @return [String] the combined query that can be submitted to solr
63
- def build
64
+ def build(join_with: 'AND')
64
65
  return 'id:NEVER_USE_THIS_ID' if @query.blank? # forces this method to always return a valid solr query
65
- @query.join(' AND ')
66
+ @query.join(padded_join_with(join_with))
66
67
  end
67
68
 
68
69
  ##
@@ -106,8 +107,8 @@ module Hyrax
106
107
 
107
108
  ##
108
109
  # @param field_pairs [Hash] a list of pairs of property name and values (e.g. { field1: values, field2: values })
109
- # @param join_with [String] the connector used to join the field pairs (default: ' AND ')
110
- # @param type [String] type of query to run. Either 'raw' or 'field' (default: 'field')
110
+ # @param join_with [String] the connector (eg. 'AND', 'OR') used to join the field pairs (default: 'AND')
111
+ # @param type [String] type of query to run (e.g. 'raw', 'field', 'terms') (default: 'field')
111
112
  # @return [SolrQueryService] the existing service with field_pair query appended
112
113
  def with_field_pairs(field_pairs: {}, join_with: default_join_with, type: 'field')
113
114
  pairs_query = construct_query_for_pairs(field_pairs, join_with, type)
@@ -140,7 +141,7 @@ module Hyrax
140
141
 
141
142
  # Construct a solr query from a list of pairs (e.g. { field1: values, field2: values })
142
143
  # @param [Hash] field_pairs a list of pairs of property name and values
143
- # @param [String] join_with the value we're joining the clauses with (default: ' AND ')
144
+ # @param [String] join_with the value (e.g. 'AND', 'OR') we're joining the clauses with (default: 'AND')
144
145
  # @param [String] type of query to run. Either 'raw' or 'field' (default: 'field')
145
146
  # @return [String] a solr query
146
147
  # @example
@@ -150,7 +151,7 @@ module Hyrax
150
151
  clauses = pairs_to_clauses(field_pairs, type)
151
152
  return "" if clauses.count.zero?
152
153
  return clauses.first if clauses.count == 1
153
- "(#{clauses.join(join_with)})"
154
+ "(#{clauses.join(padded_join_with(join_with))})"
154
155
  end
155
156
 
156
157
  # Construct a solr query from the model (e.g. Collection, Monograph)
@@ -184,7 +185,11 @@ module Hyrax
184
185
  end
185
186
 
186
187
  def default_join_with
187
- ' AND '
188
+ 'AND'
189
+ end
190
+
191
+ def padded_join_with(join_with)
192
+ " #{join_with.strip} "
188
193
  end
189
194
 
190
195
  # @param [Array<Array>] pairs a list of (key, value) pairs. The value itself may
@@ -9,7 +9,7 @@ module Hyrax
9
9
 
10
10
  thumb = fetch_thumbnail(object)
11
11
 
12
- return unless thumb
12
+ return default_image unless thumb
13
13
  return call(thumb) unless thumb.file_set?
14
14
  if audio?(thumb)
15
15
  audio_image
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ class ValkyriePersistDerivatives < Hydra::Derivatives::PersistOutputFileService
4
+ # Persists a derivative using the defined Valkyrie storage adapter
5
+ #
6
+ # This Service conforms to the signature of `Hydra::Derivatives::PersistOutputFileService`.
7
+ # This service is a Valkyrized alternative to the default Hydra::Derivatives::PersistOutputFileService.
8
+ # This service will always update existing and does not do versioning of persisted files.
9
+ #
10
+ # to replace the default AF derivative pipeline, set
11
+ # ```
12
+ # Hydra::Derivatives.config.output_file_service = Hyrax::ValkyriePersistDerivatives
13
+ # Hydra::Derivatives.config.source_file_service = Hyrax::LocalFileService
14
+ # ```
15
+ #
16
+ # @param [#read] stream the derivative filestream
17
+ # @param [Hash] directives
18
+ # @option directives [String] :url a url to the file destination
19
+ def self.call(stream, directives)
20
+ filepath = URI(directives.fetch(:url)).path
21
+ fileset_id = fileset_id_from_path(filepath)
22
+ fileset = Hyrax.metadata_adapter.query_service.find_by(id: fileset_id)
23
+
24
+ # Valkyrie storage adapters will typically expect an IO-like object that
25
+ # responds to #path -- here we only have a StringIO, so some
26
+ # transformation is in order
27
+ tmpfile = Tempfile.new(fileset_id, encoding: 'ascii-8bit')
28
+ tmpfile.write stream.read
29
+
30
+ Rails.logger.debug "Uploading thumbnail for FileSet #{fileset_id} as #{filepath}"
31
+ Hyrax.config.derivatives_storage_adapter.upload(
32
+ file: tmpfile,
33
+ original_filename: filepath,
34
+ resource: fileset
35
+ )
36
+ end
37
+
38
+ # The filepath will look something like
39
+ # /app/samvera/hyrax-webapp/derivatives/95/93/tv/12/3-thumbnail.jpeg and
40
+ # we want to extract the FileSet id, which in this case would be 9593tv123
41
+ #
42
+ # @param [String] path
43
+ # @return [String]
44
+ def self.fileset_id_from_path(path)
45
+ path.sub(Hyrax.config.derivatives_path.to_s, "")
46
+ .sub(/-[^\/]+\..*$/, "")
47
+ .delete("/")
48
+ end
49
+ end
50
+ end