hyrax 3.0.1 → 3.0.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +19 -8
  3. data/.dassie/config/role_map.yml +2 -0
  4. data/.dassie/db/seeds.rb +1 -1
  5. data/.env +1 -1
  6. data/CONTAINERS.md +20 -0
  7. data/app/actors/hyrax/actors/file_set_actor.rb +10 -5
  8. data/app/controllers/hyrax/admin/permission_template_accesses_controller.rb +0 -4
  9. data/app/forms/hyrax/forms/resource_form.rb +11 -1
  10. data/app/helpers/hyrax/dashboard_helper_behavior.rb +20 -5
  11. data/app/helpers/hyrax/embargo_helper.rb +4 -0
  12. data/app/helpers/hyrax/lease_helper.rb +4 -0
  13. data/app/helpers/hyrax/url_helper.rb +4 -1
  14. data/app/indexers/hyrax/valkyrie_file_set_indexer.rb +2 -0
  15. data/app/indexers/hyrax/valkyrie_work_indexer.rb +1 -1
  16. data/app/jobs/ingest_local_file_job.rb +18 -2
  17. data/app/models/collection_branding_info.rb +25 -9
  18. data/app/models/concerns/hyrax/embargoable.rb +24 -0
  19. data/app/models/concerns/hyrax/work_behavior.rb +1 -1
  20. data/app/presenters/hyrax/admin_set_options_presenter.rb +13 -1
  21. data/app/presenters/hyrax/pcdm_member_presenter_factory.rb +119 -0
  22. data/app/presenters/hyrax/work_show_presenter.rb +5 -1
  23. data/app/search_builders/hyrax/collection_member_search_builder.rb +6 -1
  24. data/app/search_builders/hyrax/my/collections_search_builder.rb +1 -1
  25. data/app/search_builders/hyrax/nested_collections_parent_search_builder.rb +1 -1
  26. data/app/search_builders/hyrax/single_collection_search_builder.rb +1 -1
  27. data/app/services/hyrax/contextual_path.rb +1 -1
  28. data/app/services/hyrax/edit_permissions_service.rb +47 -21
  29. data/app/services/hyrax/find_objects_via_solr_service.rb +27 -0
  30. data/app/services/hyrax/multiple_membership_checker.rb +6 -2
  31. data/app/services/hyrax/solr_query_builder_service.rb +17 -3
  32. data/app/services/hyrax/visibility_intention.rb +20 -2
  33. data/app/views/hyrax/base/_form_child_work_relationships.html.erb +1 -1
  34. data/app/views/hyrax/base/_form_visibility_error.html.erb +2 -0
  35. data/app/views/hyrax/base/_guts4form.html.erb +1 -1
  36. data/app/views/hyrax/base/_show_actions.html.erb +1 -1
  37. data/app/views/hyrax/base/_work_button_row.html.erb +1 -1
  38. data/app/views/hyrax/batch_uploads/_form.html.erb +1 -1
  39. data/bin/db-migrate-seed.sh +3 -1
  40. data/bin/hyrax-entrypoint.sh +0 -14
  41. data/bin/solrcloud-assign-configset.sh +5 -0
  42. data/bin/solrcloud-upload-configset.sh +14 -5
  43. data/chart/hyrax/Chart.yaml +8 -4
  44. data/chart/hyrax/README.md +5 -4
  45. data/chart/hyrax/templates/_helpers.tpl +14 -0
  46. data/chart/hyrax/templates/branding-pvc.yaml +2 -2
  47. data/chart/hyrax/templates/configmap-env.yaml +7 -1
  48. data/chart/hyrax/templates/deployment-worker.yaml +39 -5
  49. data/chart/hyrax/templates/deployment.yaml +42 -0
  50. data/chart/hyrax/templates/derivatives-pvc.yaml +2 -2
  51. data/chart/hyrax/templates/ingress.yaml +13 -4
  52. data/chart/hyrax/templates/secrets.yaml +8 -0
  53. data/chart/hyrax/templates/uploads-pvc.yaml +2 -2
  54. data/chart/hyrax/values.yaml +71 -1
  55. data/config/initializers/valkryrie_storage.rb +7 -0
  56. data/docker-compose.yml +38 -8
  57. data/documentation/developing-your-hyrax-based-app.md +3 -3
  58. data/documentation/legacyREADME.md +4 -4
  59. data/lib/hyrax/configuration.rb +12 -0
  60. data/lib/hyrax/engine.rb +1 -0
  61. data/lib/hyrax/resource_name.rb +1 -0
  62. data/lib/hyrax/specs/capybara.rb +2 -2
  63. data/lib/hyrax/valkyrie_simple_path_generator.rb +20 -0
  64. data/lib/hyrax/version.rb +1 -1
  65. data/lib/wings.rb +0 -21
  66. data/lib/wings/active_fedora_converter/default_work.rb +15 -0
  67. data/lib/wings/model_transformer.rb +17 -1
  68. data/lib/wings/setup.rb +1 -0
  69. data/template.rb +1 -1
  70. metadata +8 -3
@@ -4,8 +4,9 @@ module Hyrax
4
4
  class AdminSetOptionsPresenter
5
5
  ##
6
6
  # @param [Hyrax::AdminSetService] service
7
- def initialize(service)
7
+ def initialize(service, current_ability: service.context.current_ability)
8
8
  @service = service
9
+ @current_ability = current_ability
9
10
  end
10
11
 
11
12
  # Return AdminSet selectbox options based on access type
@@ -49,6 +50,17 @@ module Hyrax
49
50
 
50
51
  # Does the workflow for the currently selected permission template allow sharing?
51
52
  def sharing?(permission_template:)
53
+ # This short-circuit builds on a stated "promise" in the UI of
54
+ # editing an admin set:
55
+ #
56
+ # > Managers of this administrative set can edit the set
57
+ # > metadata, participants, and release and visibility
58
+ # > settings. Managers can also edit work metadata, add to or
59
+ # > remove files from a work, and add new works to the set.
60
+ return true if @current_ability.can?(:manage, permission_template)
61
+
62
+ # Otherwise, we check if the workflow was setup, active, and
63
+ # allows_access_grants.
52
64
  wf = workflow(permission_template: permission_template)
53
65
  return false unless wf
54
66
  wf.allows_access_grant?
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ ##
4
+ # constructs presenters for the pcdm:members of an Object, omitting those
5
+ # not readable by a provided +Ability+.
6
+ #
7
+ # this implementation builds the presenters without recourse to the request
8
+ # context and ActiveFedora-specific index structures (i.e. no `list_source`
9
+ # or `proxy_in_ssi`).
10
+ #
11
+ # @see MemberPresenterFactory
12
+ class PcdmMemberPresenterFactory
13
+ class_attribute :file_presenter_class, :work_presenter_class
14
+ self.file_presenter_class = FileSetPresenter
15
+ self.work_presenter_class = WorkShowPresenter
16
+
17
+ attr_reader :ability, :object
18
+
19
+ ##
20
+ # @param [#member_ids] object
21
+ # @param [::Ability] ability
22
+ def initialize(object, ability)
23
+ @object = object
24
+ @ability = ability
25
+ end
26
+
27
+ ##
28
+ # @return [Array<FileSetPresenter, WorkShowPresenter>]
29
+ # @return [Enumerator<FileSetPresenter>]
30
+ def file_set_presenters
31
+ return enum_for(:file_set_presenters) unless block_given?
32
+
33
+ results = query_docs(generic_type: "FileSet")
34
+
35
+ object.member_ids.each do |id|
36
+ id = id.to_s
37
+ indx = results.index { |doc| id == doc['id'] }
38
+ next if indx.nil?
39
+ hash = results.delete_at(indx)
40
+ yield presenter_for(document: ::SolrDocument.new(hash), ability: ability)
41
+ end
42
+ end
43
+
44
+ ##
45
+ # @note defaults to using `object.member_ids`. passing a specific set of
46
+ # ids is supported for compatibility with {MemberPresenterFactory}, but
47
+ # we recommend making sparing use of this feature.
48
+ #
49
+ # @overload member_presenters
50
+ # @return [Enumerator<FileSetPresenter, WorkShowPresenter>]
51
+ # @raise [ArgumentError] if an unindexed id is passed
52
+ # @overload member_presenters
53
+ # @param [Array<#to_s>] ids
54
+ # @return [Enumerator<FileSetPresenter, WorkShowPresenter>]
55
+ # @raise [ArgumentError] if an unindexed id is passed
56
+ def member_presenters(ids = object.member_ids)
57
+ return enum_for(:member_presenters, ids) unless block_given?
58
+
59
+ results = query_docs(ids: ids)
60
+
61
+ ids.each do |id|
62
+ id = id.to_s
63
+ indx = results.index { |doc| id == doc['id'] }
64
+ raise(ArgumentError, "Could not find an indexed document for id: #{id}") if
65
+ indx.nil?
66
+ hash = results.delete_at(indx)
67
+ yield presenter_for(document: ::SolrDocument.new(hash), ability: ability)
68
+ end
69
+ end
70
+
71
+ ##
72
+ # @return [Array<#to_s>]
73
+ def ordered_ids
74
+ object.member_ids
75
+ end
76
+
77
+ ##
78
+ # @return [Array<WorkShowPresenter>]
79
+ def work_presenters
80
+ return enum_for(:work_presenters) unless block_given?
81
+
82
+ results = query_docs(generic_type: "Work")
83
+
84
+ object.member_ids.each do |id|
85
+ id = id.to_s
86
+ indx = results.index { |doc| id == doc['id'] }
87
+ next if indx.nil?
88
+ hash = results.delete_at(indx)
89
+ yield presenter_for(document: ::SolrDocument.new(hash), ability: ability)
90
+ end
91
+ end
92
+
93
+ ##
94
+ # @param [::SolrDocument] document
95
+ # @param [::Ability] ability
96
+ #
97
+ # @return
98
+ def presenter_for(document:, ability:)
99
+ case document['has_model_ssim'].first
100
+ when Hyrax::FileSet.name
101
+ Hyrax::FileSetPresenter.new(document, ability)
102
+ else
103
+ Hyrax::WorkShowPresenter.new(document, ability)
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def query_docs(generic_type: nil, ids: object.member_ids)
110
+ query = "{!terms f=id}#{ids.join(',')}"
111
+ query += "{!term f=generic_type_si}#{generic_type}" if generic_type
112
+
113
+ Hyrax::SolrService
114
+ .post(query, rows: 10_000)
115
+ .fetch('response')
116
+ .fetch('docs')
117
+ end
118
+ end
119
+ end
@@ -94,7 +94,7 @@ module Hyrax
94
94
  return nil if representative_id.blank?
95
95
  @representative_presenter ||=
96
96
  begin
97
- result = member_presenters_for([representative_id]).first
97
+ result = member_presenters([representative_id]).first
98
98
  return nil if result.try(:id) == id
99
99
  result.try(:representative_presenter) || result
100
100
  end
@@ -181,9 +181,13 @@ module Hyrax
181
181
  paginated_item_list(page_array: authorized_item_ids)
182
182
  end
183
183
 
184
+ ##
185
+ # @deprecated use `#member_presenters(ids)` instead
186
+ #
184
187
  # @param [Array<String>] ids a list of ids to build presenters for
185
188
  # @return [Array<presenter_class>] presenters for the array of ids (not filtered by class)
186
189
  def member_presenters_for(an_array_of_ids)
190
+ Deprecation.warn("Use `#member_presenters` instead.")
187
191
  member_presenters(an_array_of_ids)
188
192
  end
189
193
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module Hyrax
3
3
  # This search builder requires that a accessor named "collection" exists in the scope
4
- class CollectionMemberSearchBuilder < ::SearchBuilder
4
+ class CollectionMemberSearchBuilder < ::Hyrax::CollectionSearchBuilder
5
5
  include Hyrax::FilterByType
6
6
  attr_writer :collection, :search_includes_models
7
7
 
@@ -41,6 +41,11 @@ module Hyrax
41
41
  solr_parameters[:fq] << "#{collection_membership_field}:#{collection.id}"
42
42
  end
43
43
 
44
+ # This overrides the models in FilterByType
45
+ def models
46
+ work_classes + collection_classes
47
+ end
48
+
44
49
  private
45
50
 
46
51
  def only_works?
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # Added to allow for the My controller to show only things I have edit access to
3
- class Hyrax::My::CollectionsSearchBuilder < ::SearchBuilder
3
+ class Hyrax::My::CollectionsSearchBuilder < ::Hyrax::CollectionSearchBuilder
4
4
  include Hyrax::My::SearchBuilderBehavior
5
5
  include Hyrax::FilterByType
6
6
 
@@ -2,7 +2,7 @@
2
2
  module Hyrax
3
3
  ##
4
4
  # Searches for all collections that are parents of a given collection.
5
- class NestedCollectionsParentSearchBuilder < ::SearchBuilder
5
+ class NestedCollectionsParentSearchBuilder < ::Hyrax::CollectionSearchBuilder
6
6
  include Hyrax::FilterByType
7
7
  attr_reader :child, :page, :limit
8
8
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Hyrax
3
- class SingleCollectionSearchBuilder < ::SearchBuilder
3
+ class SingleCollectionSearchBuilder < ::Hyrax::CollectionSearchBuilder
4
4
  include SingleResult
5
5
  end
6
6
  end
@@ -11,7 +11,7 @@ module Hyrax
11
11
 
12
12
  def show
13
13
  if parent_presenter
14
- polymorphic_path([:hyrax, :parent, presenter.model_name.singular],
14
+ polymorphic_path([:hyrax, :parent, presenter.model_name.singular.to_sym],
15
15
  parent_id: parent_presenter.id,
16
16
  id: presenter.id)
17
17
  else
@@ -158,38 +158,64 @@ module Hyrax
158
158
  # find all of the work's collections a user can manage
159
159
  # @return [Array] of collection ids
160
160
  def object_managed_collection_ids
161
- @object_managed_collection_ids ||= object_member_of & managed_collection_ids
161
+ @object_managed_collection_ids ||= object_member_of_ids & managed_collection_ids
162
162
  end
163
163
 
164
- # find all of the work's collections a user cannot manage
165
- # note: if the collection type doesn't include "sharing_applies_to_new_works", we don't limit access
164
+ # find all of the work's collections a user cannot manage note: if
165
+ # the collection type doesn't include
166
+ # "sharing_applies_to_new_works", we don't limit access
167
+ #
166
168
  # @return [Array] of collection ids with limited access
167
169
  def object_unauthorized_collection_ids
168
170
  @object_unauthorized_collection_ids ||= begin
169
- limited_access = []
170
- unauthorized_collection_ids = object_member_of - object_managed_collection_ids
171
- if unauthorized_collection_ids.any?
172
- unauthorized_collection_ids.each do |id|
173
- # TODO: Can we instead use a SOLR query? This seems to be somewhat expensive. However, as this is
174
- # used in administration instead of user front-end displays, I'm not as concerned.
175
- collection = ActiveFedora::Base.find(id)
176
- limited_access << id if (collection.instance_of? AdminSet) || collection.share_applies_to_new_works?
177
- end
171
+ unauthorized_collection_ids = object_member_of_ids - object_managed_collection_ids
172
+ qualified_resources = Hyrax.query_service.find_many_by_ids(ids: unauthorized_collection_ids).select do |resource|
173
+ qualifies_as_unauthorized_collection?(resource: resource)
178
174
  end
179
- limited_access
175
+ qualified_resources.map { |resource| resource.id.to_s }
180
176
  end
181
177
  end
182
178
 
179
+ # Does the given resource qualify as a collection the current user cannot manage.
180
+ #
181
+ # @see {#object_unauthorized_collection_ids}
182
+ #
183
+ # @param resource [Valkyrie::Resource, AdminSet, Collection, #collection_type_gid, #share_applies_to_new_works?]
184
+ # the given resource, hopefully a collection-like thing
185
+ # (e.g. AdminSet, Hyrax::AdminSet, Hyrax::PcdmCollection,
186
+ # Collection)
187
+ #
188
+ # @return [Boolean]
189
+ #
190
+ # @todo Refactor inner working of code as there's lots of branching logic with potential hidden assumptions.
191
+ def qualifies_as_unauthorized_collection?(resource:)
192
+ case resource
193
+ when AdminSet, Hyrax::AdministrativeSet
194
+ # Prior to this refactor, we looked at AdminSet only; However with the advent of the
195
+ # Hyrax::AdministrativeSet, we need to test both cases.
196
+ true
197
+ else
198
+ if resource.respond_to?(:share_applies_to_new_works?)
199
+ # The Collection model has traditionally delegated #share_applies_to_new_works? to
200
+ # the underlying collection_type
201
+ # (see https://github.com/samvera/hyrax/blob/696da5db/spec/models/collection_spec.rb#L189)
202
+ resource.share_applies_to_new_works?
203
+ elsif resource.respond_to?(:collection_type_gid)
204
+ # This is likely a Hyrax::PcdmCollection object, which means we don't have the delegation
205
+ # behavior. Instead we'll query the collection type directly.
206
+ collection_type = CollectionType.find_by_gid(resource.collection_type_gid)
207
+ collection_type&.share_applies_to_new_works?
208
+ else
209
+ # How might we get here?
210
+ false
211
+ end
212
+ end
213
+ end
214
+
183
215
  # find all of the collection ids an object is a member of
184
216
  # @return [Array] array of collection ids
185
- def object_member_of
186
- @object_member_of ||= begin
187
- belongs_to = []
188
- # get all of work's collection ids from the form
189
- belongs_to += @object.member_of_collection_ids
190
- belongs_to << @object.admin_set_id if @object.admin_set_id.present?
191
- belongs_to
192
- end
217
+ def object_member_of_ids
218
+ @object_member_of_ids ||= (@object.member_of_collection_ids + [@object.admin_set_id]).select(&:present?)
193
219
  end
194
220
 
195
221
  # The list of all collections this user has manage rights on
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ # Methods in this class search solr to get the ids and then use the query service to find the objects.
4
+ class FindObjectsViaSolrService
5
+ class_attribute :solr_query_builder, :solr_service, :query_service
6
+ self.solr_query_builder = Hyrax::SolrQueryBuilderService
7
+ self.solr_service = Hyrax::SolrService
8
+ self.query_service = Hyrax.query_service
9
+
10
+ class << self
11
+ # Find objects matching search criteria.
12
+ # @param model [Class] if not using Valkyrie, this is expected to be an ActiveFedora::Base object that supports #where
13
+ # @param field_pairs [Hash] a list of pairs of property name and values
14
+ # @param join_with [String] the value we're joining the clauses with (default: ' OR ' for backward compatibility with ActiveFedora where)
15
+ # @param type [String] The type of query to run. Either 'raw' or 'field' (default: 'field')
16
+ # @param use_valkyrie [Boolean] if true, return Valkyrie resource(s); otherwise, return ActiveFedora object(s)
17
+ # @return [Array<ActiveFedora|Valkyrie::Resource>] objects matching the query
18
+ def find_for_model_by_field_pairs(model:, field_pairs:, join_with: ' OR ', type: 'field', use_valkyrie: Hyrax.config.use_valkyrie?)
19
+ return model.where(field_pairs).to_a unless use_valkyrie
20
+ query = solr_query_builder.construct_query_for_model(model, field_pairs, join_with, type)
21
+ results = solr_service.query(query)
22
+ ids = results.map(&:id)
23
+ query_service.find_many_by_ids(ids: ids).to_a
24
+ end
25
+ end
26
+ end
27
+ end
@@ -53,7 +53,11 @@ module Hyrax
53
53
  def single_membership_collections(collection_ids)
54
54
  return [] if collection_ids.blank?
55
55
 
56
- ::Collection.where(:id => collection_ids, Hyrax.config.collection_type_index_field.to_sym => collection_type_gids_that_disallow_multiple_membership)
56
+ field_pairs = {
57
+ :id => collection_ids,
58
+ Hyrax.config.collection_type_index_field.to_sym => collection_type_gids_that_disallow_multiple_membership
59
+ }
60
+ Hyrax::FindObjectsViaSolrService.find_for_model_by_field_pairs(model: ::Collection, field_pairs: field_pairs, use_valkyrie: true)
57
61
  end
58
62
 
59
63
  def collection_type_gids_that_disallow_multiple_membership
@@ -74,7 +78,7 @@ module Hyrax
74
78
  end
75
79
 
76
80
  def collection_titles_from_list(collection_list)
77
- collection_list.each do |collection|
81
+ collection_list.map do |collection|
78
82
  collection.title.first
79
83
  end.to_sentence
80
84
  end
@@ -14,9 +14,9 @@ module Hyrax
14
14
  end
15
15
 
16
16
  # Construct a solr query from a list of pairs (e.g. [field name, values])
17
- # @param [Array<Array>] field_pairs a list of pairs of property name and values
18
- # @param [String] join_with ('AND') the value we're joining the clauses with
19
- # @param [String] type ('field') The type of query to run. Either 'raw' or 'field'
17
+ # @param [Hash] field_pairs a list of pairs of property name and values
18
+ # @param [String] join_with the value we're joining the clauses with (default: ' AND ')
19
+ # @param [String] type of query to run. Either 'raw' or 'field' (default: 'field')
20
20
  # @return [String] a solr query
21
21
  # @example
22
22
  # construct_query([['library_id_ssim', '123'], ['owner_ssim', 'Fred']])
@@ -32,6 +32,20 @@ module Hyrax
32
32
  ' AND '
33
33
  end
34
34
 
35
+ # Construct a solr query from a list of pairs (e.g. [field name, values]) including the model (e.g. Collection, Monograph)
36
+ # @param [Class] model class
37
+ # @param [Hash] field_pairs a list of pairs of property name and values
38
+ # @param [String] join_with the value we're joining the clauses with (default: ' AND ')
39
+ # @param [String] type of query to run. Either 'raw' or 'field' (default: 'field')
40
+ # @return [String] a solr query
41
+ # @example
42
+ # construct_query(Collection, [['library_id_ssim', '123'], ['owner_ssim', 'Fred']])
43
+ # # => "_query_:\"{!field f=has_model_ssim}Collection\" AND _query_:\"{!field f=library_id_ssim}123\" AND _query_:\"{!field f=owner_ssim}Fred\""
44
+ def construct_query_for_model(model, field_pairs, join_with = default_join_with, type = 'field')
45
+ field_pairs["has_model_ssim"] = model.to_s
46
+ construct_query(field_pairs, join_with, type)
47
+ end
48
+
35
49
  private
36
50
 
37
51
  # @param [Array<Array>] pairs a list of (key, value) pairs. The value itself may
@@ -54,7 +54,7 @@ module Hyrax
54
54
  ##
55
55
  # @return [Boolean]
56
56
  def valid_embargo?
57
- wants_embargo? && release_date.present?
57
+ wants_embargo? && release_date.present? && a_valid_date?(release_date)
58
58
  end
59
59
 
60
60
  ##
@@ -66,7 +66,7 @@ module Hyrax
66
66
  ##
67
67
  # @return [Boolean]
68
68
  def valid_lease?
69
- wants_lease? && release_date.present?
69
+ wants_lease? && release_date.present? && a_valid_date?(release_date)
70
70
  end
71
71
 
72
72
  ##
@@ -74,5 +74,23 @@ module Hyrax
74
74
  def wants_lease?
75
75
  visibility == LEASE_REQUEST
76
76
  end
77
+
78
+ private
79
+
80
+ ##
81
+ # @param date [Object]
82
+ # @return [Boolean]
83
+ # @note If we don't have a valid date, we really can't have a
84
+ # valid release_date
85
+ def a_valid_date?(date)
86
+ return true if date.is_a?(Date)
87
+ return true if date.is_a?(Time)
88
+ Date.parse(date)
89
+ # In Ruby 2.7.x, Date::Error descends from ArgumentError; Once
90
+ # we stop supporting pre-2.7, we can switch this to the more
91
+ # narrow Date::Error
92
+ rescue ArgumentError
93
+ false
94
+ end
77
95
  end
78
96
  end