hyrax 3.0.1 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
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