hyrax 3.0.2 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +22 -0
  3. data/.dassie/Gemfile +10 -5
  4. data/.dassie/config/initializers/hyrax.rb +5 -0
  5. data/.dockerignore +3 -0
  6. data/.env +0 -1
  7. data/.rubocop.yml +4 -0
  8. data/CONTAINERS.md +1 -1
  9. data/Dockerfile +12 -6
  10. data/Gemfile +21 -27
  11. data/app/actors/hyrax/actors/base_actor.rb +1 -1
  12. data/app/actors/hyrax/actors/create_with_remote_files_actor.rb +85 -63
  13. data/app/actors/hyrax/actors/create_with_remote_files_ordered_members_actor.rb +7 -42
  14. data/app/controllers/concerns/hyrax/collections_controller_behavior.rb +20 -8
  15. data/app/controllers/concerns/hyrax/embargoes_controller_behavior.rb +21 -9
  16. data/app/controllers/concerns/hyrax/leases_controller_behavior.rb +14 -5
  17. data/app/controllers/concerns/hyrax/works_controller_behavior.rb +22 -3
  18. data/app/controllers/hyrax/admin/workflows_controller.rb +8 -2
  19. data/app/controllers/hyrax/dashboard/collection_members_controller.rb +13 -9
  20. data/app/controllers/hyrax/dashboard/collections_controller.rb +12 -10
  21. data/app/controllers/hyrax/file_sets_controller.rb +49 -13
  22. data/app/controllers/hyrax/permissions_controller.rb +3 -4
  23. data/app/controllers/hyrax/workflow_actions_controller.rb +3 -1
  24. data/app/forms/hyrax/forms/collection_form.rb +7 -3
  25. data/app/forms/hyrax/forms/dashboard/nest_collection_form.rb +24 -2
  26. data/app/forms/hyrax/forms/file_set_form.rb +46 -0
  27. data/app/forms/hyrax/forms/permission.rb +23 -0
  28. data/app/forms/hyrax/forms/permission_template_form.rb +8 -2
  29. data/app/forms/hyrax/forms/resource_form.rb +10 -17
  30. data/app/forms/hyrax/forms/work_form.rb +5 -2
  31. data/app/helpers/hyrax/batch_edits_helper.rb +3 -1
  32. data/app/helpers/hyrax/collections_helper.rb +88 -2
  33. data/app/helpers/hyrax/dashboard_helper_behavior.rb +3 -7
  34. data/app/helpers/hyrax/file_set_helper.rb +25 -6
  35. data/app/helpers/hyrax/work_form_helper.rb +53 -0
  36. data/app/indexers/hyrax/administrative_set_indexer.rb +18 -0
  37. data/app/indexers/hyrax/valkyrie_indexer.rb +3 -3
  38. data/app/inputs/controlled_vocabulary_input.rb +2 -5
  39. data/app/jobs/attach_files_to_work_job.rb +19 -10
  40. data/app/jobs/attach_files_to_work_with_ordered_members_job.rb +6 -5
  41. data/app/jobs/inherit_permissions_job.rb +9 -5
  42. data/app/models/admin_set.rb +6 -25
  43. data/app/models/concerns/hyrax/ability.rb +3 -1
  44. data/app/models/concerns/hyrax/collection_behavior.rb +17 -44
  45. data/app/models/concerns/hyrax/file_set/characterization.rb +18 -12
  46. data/app/models/concerns/hyrax/solr_document_behavior.rb +9 -52
  47. data/app/models/concerns/hyrax/suppressible.rb +5 -0
  48. data/app/models/concerns/hyrax/user.rb +9 -3
  49. data/app/models/hyrax/file_set.rb +6 -0
  50. data/app/models/hyrax/pcdm_collection.rb +1 -0
  51. data/app/models/hyrax/permission_template.rb +98 -12
  52. data/app/models/hyrax/virus_scanner.rb +27 -18
  53. data/app/models/sipity/agent.rb +1 -0
  54. data/app/models/sipity/entity.rb +30 -8
  55. data/app/models/sipity/workflow.rb +1 -0
  56. data/app/models/sipity.rb +42 -0
  57. data/app/presenters/hyrax/admin_set_options_presenter.rb +2 -10
  58. data/app/presenters/hyrax/admin_set_presenter.rb +5 -1
  59. data/app/presenters/hyrax/admin_set_selection_presenter.rb +116 -0
  60. data/app/presenters/hyrax/collection_presenter.rb +31 -6
  61. data/app/presenters/hyrax/file_set_presenter.rb +6 -1
  62. data/app/presenters/hyrax/file_usage.rb +3 -2
  63. data/app/presenters/hyrax/stats_usage_presenter.rb +2 -1
  64. data/app/presenters/hyrax/trophy_presenter.rb +33 -4
  65. data/app/presenters/hyrax/user_profile_presenter.rb +11 -1
  66. data/app/presenters/hyrax/version_list_presenter.rb +19 -0
  67. data/app/presenters/hyrax/version_presenter.rb +3 -2
  68. data/app/presenters/hyrax/work_show_presenter.rb +25 -4
  69. data/app/presenters/hyrax/work_usage.rb +5 -3
  70. data/app/renderers/hyrax/renderers/attribute_renderer.rb +10 -2
  71. data/app/search_builders/hyrax/admin_set_search_builder.rb +1 -1
  72. data/app/search_builders/hyrax/my/collections_search_builder.rb +1 -1
  73. data/app/services/hyrax/admin_set_create_service.rb +3 -1
  74. data/app/services/hyrax/collections/collection_member_search_service.rb +72 -0
  75. data/app/services/hyrax/collections/collection_member_service.rb +112 -27
  76. data/app/services/hyrax/collections/migration_service.rb +4 -2
  77. data/app/services/hyrax/collections/nested_collection_persistence_service.rb +12 -13
  78. data/app/services/hyrax/collections/nested_collection_query_service.rb +2 -0
  79. data/app/services/hyrax/collections/permissions_create_service.rb +6 -4
  80. data/app/services/hyrax/contextual_path.rb +23 -0
  81. data/app/services/hyrax/custom_queries/find_file_metadata.rb +7 -5
  82. data/app/services/hyrax/custom_queries/navigators/parent_collections_navigator.rb +46 -0
  83. data/app/services/hyrax/edit_permissions_service.rb +27 -20
  84. data/app/services/hyrax/find_objects_via_solr_service.rb +11 -7
  85. data/app/services/hyrax/multiple_membership_checker.rb +51 -31
  86. data/app/services/hyrax/resource_status.rb +7 -0
  87. data/app/services/hyrax/search_service.rb +4 -2
  88. data/app/services/hyrax/solr_query_builder_service.rb +29 -6
  89. data/app/services/hyrax/solr_query_service.rb +224 -0
  90. data/app/services/hyrax/solr_service.rb +8 -1
  91. data/app/services/hyrax/statistics/depositors/summary.rb +2 -1
  92. data/app/services/hyrax/work_uploads_handler.rb +17 -2
  93. data/app/services/hyrax/workflow/actionable_objects.rb +70 -0
  94. data/app/services/hyrax/workflow/object_in_workflow_decorator.rb +31 -0
  95. data/app/services/hyrax/workflow/status_list_service.rb +43 -13
  96. data/app/views/hyrax/base/_form_relationships.html.erb +1 -2
  97. data/app/views/hyrax/base/_form_rendering.html.erb +1 -1
  98. data/app/views/hyrax/base/_form_representative.html.erb +1 -1
  99. data/app/views/hyrax/base/_form_thumbnail.html.erb +1 -1
  100. data/app/views/hyrax/base/_guts4form.html.erb +2 -2
  101. data/app/views/hyrax/base/_representative_media.html.erb +1 -1
  102. data/app/views/hyrax/base/_show_actions.html.erb +1 -1
  103. data/app/views/hyrax/dashboard/collections/_form.html.erb +3 -3
  104. data/app/views/hyrax/dashboard/collections/_list_collections.html.erb +1 -1
  105. data/app/views/hyrax/dashboard/collections/edit.html.erb +4 -2
  106. data/app/views/hyrax/dashboard/collections/new.html.erb +4 -2
  107. data/app/views/hyrax/dashboard/collections/show.html.erb +1 -1
  108. data/app/views/hyrax/file_sets/edit.html.erb +1 -1
  109. data/app/views/hyrax/file_sets/media_display/_audio.html.erb +1 -1
  110. data/app/views/hyrax/file_sets/media_display/_default.html.erb +1 -1
  111. data/app/views/hyrax/file_sets/media_display/_image.html.erb +1 -1
  112. data/app/views/hyrax/file_sets/media_display/_office_document.html.erb +1 -1
  113. data/app/views/hyrax/file_sets/media_display/_pdf.html.erb +1 -1
  114. data/app/views/hyrax/file_sets/media_display/_video.html.erb +1 -1
  115. data/app/views/hyrax/file_sets/show.html.erb +1 -1
  116. data/app/views/hyrax/my/_admin_set_action_menu.html.erb +0 -11
  117. data/app/views/hyrax/my/_collection_action_menu.html.erb +1 -2
  118. data/app/views/hyrax/my/collections/_list_collections.html.erb +1 -1
  119. data/app/views/hyrax/my/collections/_modal_add_subcollection.html.erb +3 -5
  120. data/bin/solrcloud-assign-configset.sh +8 -5
  121. data/bin/solrcloud-upload-configset.sh +4 -2
  122. data/chart/hyrax/Chart.yaml +3 -3
  123. data/chart/hyrax/README.md +47 -1
  124. data/chart/hyrax/templates/_helpers.tpl +1 -1
  125. data/chart/hyrax/templates/configmap-env.yaml +1 -3
  126. data/chart/hyrax/templates/deployment-worker.yaml +6 -3
  127. data/chart/hyrax/templates/deployment.yaml +8 -3
  128. data/chart/hyrax/values.yaml +12 -0
  129. data/config/brakeman.ignore +2 -2
  130. data/config/locales/hyrax.de.yml +1 -1
  131. data/config/locales/hyrax.en.yml +1 -1
  132. data/config/locales/hyrax.es.yml +1 -1
  133. data/config/locales/hyrax.fr.yml +1 -1
  134. data/config/locales/hyrax.it.yml +1 -1
  135. data/config/locales/hyrax.pt-BR.yml +1 -1
  136. data/config/locales/hyrax.zh.yml +1 -1
  137. data/docker-compose.yml +1 -0
  138. data/documentation/developing-your-hyrax-based-app.md +1 -1
  139. data/documentation/legacyREADME.md +1 -1
  140. data/lib/generators/hyrax/templates/config/initializers/hyrax.rb +5 -0
  141. data/lib/hyrax/active_fedora_dummy_model.rb +62 -0
  142. data/lib/hyrax/configuration.rb +8 -0
  143. data/lib/hyrax/engine.rb +1 -0
  144. data/lib/hyrax/errors.rb +2 -0
  145. data/lib/hyrax/specs/capybara.rb +3 -1
  146. data/lib/hyrax/specs/shared_specs/valkyrie_storage_versions.rb +9 -0
  147. data/lib/hyrax/transactions/container.rb +21 -0
  148. data/lib/hyrax/transactions/file_set_destroy.rb +21 -0
  149. data/lib/hyrax/transactions/steps/add_file_sets.rb +3 -2
  150. data/lib/hyrax/transactions/steps/add_to_parent.rb +36 -0
  151. data/lib/hyrax/transactions/steps/remove_file_set_from_work.rb +47 -0
  152. data/lib/hyrax/transactions/work_create.rb +2 -1
  153. data/lib/hyrax/valkyrie_can_can_adapter.rb +1 -0
  154. data/lib/hyrax/version.rb +1 -1
  155. data/lib/hyrax.rb +9 -0
  156. data/lib/tasks/collection_type_global_id.rake +1 -1
  157. data/lib/tasks/regenerate_derivatives.rake +12 -0
  158. data/lib/wings/orm_converter.rb +18 -2
  159. data/lib/wings/setup.rb +1 -0
  160. data/lib/wings/valkyrie/storage.rb +56 -1
  161. data/template.rb +1 -1
  162. metadata +17 -2
@@ -10,6 +10,11 @@ module Hyrax
10
10
  end
11
11
 
12
12
  ##
13
+ # @deprecated use `Hyrax::ResourceStatus` instead. in most cases,
14
+ # {#suppressed?} is being called on a {SolrDocumentBehavior}. we continue
15
+ # to index `suppressed_bsi` and expose its value as an attribute on solr
16
+ # document objects.
17
+ #
13
18
  # Used to restrict visibility on search results for a work that is inactive. If the state is not set, the
14
19
  # default behavior is to consider the work not to be suppressed.
15
20
  #
@@ -31,7 +31,7 @@ module Hyrax::User
31
31
 
32
32
  scope :guests, ->() { where(guest: true) }
33
33
  scope :registered, ->() { where(guest: false) }
34
- scope :without_system_accounts, -> { where("#{::User.user_key_field} not in (?)", [::User.batch_user_key, ::User.audit_user_key]) }
34
+ scope :without_system_accounts, -> { where("#{::User.user_key_field} not in (?)", [::User.batch_user_key, ::User.audit_user_key, ::User.system_user_key]) }
35
35
 
36
36
  # Validate and normalize ORCIDs
37
37
  validates_with OrcidValidator
@@ -145,7 +145,14 @@ module Hyrax::User
145
145
  end
146
146
 
147
147
  module ClassMethods
148
- # Override this method if you aren't using email/password
148
+ def system_user
149
+ find_or_create_system_user(system_user_key)
150
+ end
151
+
152
+ def system_user_key
153
+ Hyrax.config.system_user_key
154
+ end
155
+
149
156
  def audit_user
150
157
  find_or_create_system_user(audit_user_key)
151
158
  end
@@ -158,7 +165,6 @@ module Hyrax::User
158
165
  Hydra.config.user_key_field
159
166
  end
160
167
 
161
- # Override this method if you aren't using email/password
162
168
  def batch_user
163
169
  find_or_create_system_user(batch_user_key)
164
170
  end
@@ -9,6 +9,12 @@ module Hyrax
9
9
  include Hyrax::Schema(:core_metadata)
10
10
  include Hyrax::Schema(:basic_metadata)
11
11
 
12
+ def self.model_name(name_class: Hyrax::Name)
13
+ @_model_name ||= begin
14
+ name_class.new(self, nil, 'FileSet')
15
+ end
16
+ end
17
+
12
18
  attribute :file_ids, Valkyrie::Types::Array.of(Valkyrie::Types::ID) # id for FileMetadata resources
13
19
  attribute :original_file_id, Valkyrie::Types::ID # id for FileMetadata resource
14
20
  attribute :thumbnail_id, Valkyrie::Types::ID # id for FileMetadata resource
@@ -11,6 +11,7 @@ module Hyrax
11
11
 
12
12
  attribute :collection_type_gid, Valkyrie::Types::String
13
13
  attribute :member_ids, Valkyrie::Types::Array.of(Valkyrie::Types::ID).meta(ordered: true)
14
+ attribute :member_of_collection_ids, Valkyrie::Types::Set.of(Valkyrie::Types::ID)
14
15
 
15
16
  ##
16
17
  # @api private
@@ -1,20 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
  module Hyrax
3
3
  ##
4
- # Defines behavior that is applied to objects added as members of an AdminSet
4
+ # Holds policy data about the workflow and permissions applied objects when
5
+ # they are deposited through an Administrative Set or a Collection. Each
6
+ # template record has a {#source} (through {#source_id}); the template's
7
+ # rules inform the behavior of objects deposited through that {#source_model}.
5
8
  #
6
- # * access rights to stamp on each object
7
- # * calculate embargo/lease release dates
9
+ # The {PermissionTemplate} specifies:
8
10
  #
9
- # There is an interplay between an AdminSet and a PermissionTemplate.
11
+ # - an {#active_workflow} that the object will enter and be processed through.
12
+ # - {#access_grants} that can be applied to each object (especially at deposit
13
+ # time).
14
+ # - an embargo configuration ({#release_date} {#release_period}) for default
15
+ # embargo behavior.
10
16
  #
11
- # @see Hyrax::AdminSet for further discussion
12
- class PermissionTemplate < ActiveRecord::Base
17
+ # Additionally, the {PermissionTemplate} grants authority to perform actions
18
+ # that relate to the Administrative Set/Collection itself. Rules for who can
19
+ # deposit to, view(?!), or manage the admin set are governed by related
20
+ # {PermissionTemplateAccess} records. Administrat Sets should have a manager
21
+ # granted by some such record.
22
+ #
23
+ # @todo write up what "default embargo behavior", when it is applied, and how
24
+ # it interacts with embargoes specified by user input.
25
+ #
26
+ # @example cerating a permission template and manager for an admin set
27
+ # admin_set = Hyrax::AdministrativeSet.new(title: 'My Admin Set')
28
+ # admin_set = Hyrax.persister.save(resource: admin_set)
29
+ #
30
+ # template = PermissionTemplate.create!(source_id: admin_set.id.to_s)
31
+ # Hyrax::PermissionTemplateAccess.create!(permission_template: template,
32
+ # agent_type: Hyrax::PermissionTemplateAccess::USER,
33
+ # agent_id: user.user_key,
34
+ # access: Hyrax::PermissionTemplateAccess::MANAGE)
35
+ #
36
+ # @see Hyrax::AdministrativeSet
37
+ class PermissionTemplate < ActiveRecord::Base # rubocop:disable Metrics/ClassLength
13
38
  self.table_name = 'permission_templates'
14
39
 
40
+ ##
41
+ # @!attribute [rw] source_id
42
+ # @return [String] identifier for the {Collection} or {AdministrativeSet}
43
+ # to which this template applies.
44
+ # @!attribute [rw] access_grants
45
+ # @return [Hyrax::PermissionTemplateAccess]
46
+ # @!attribute [rw] active_workflow
47
+ # @return [Sipity::Workflow]
48
+ # @!attribute [rw] available_workflows
49
+ # @return [Enumerable<Sipity::Workflow>]
15
50
  has_many :access_grants, class_name: 'Hyrax::PermissionTemplateAccess', dependent: :destroy
16
51
  accepts_nested_attributes_for :access_grants, reject_if: :all_blank
17
52
 
53
+ # The list of workflows that could be activated; It includes the active workflow
54
+ has_many :available_workflows, class_name: 'Sipity::Workflow', dependent: :destroy
55
+
56
+ # In a perfect world, there would be a join table that enforced uniqueness on the ID.
57
+ has_one :active_workflow, -> { where(active: true) }, class_name: 'Sipity::Workflow', foreign_key: :permission_template_id
58
+
18
59
  ##
19
60
  # @api public
20
61
  #
@@ -28,12 +69,6 @@ module Hyrax
28
69
  access_grants.where(agent_type: agent_type, access: access).pluck(:agent_id)
29
70
  end
30
71
 
31
- # The list of workflows that could be activated; It includes the active workflow
32
- has_many :available_workflows, class_name: 'Sipity::Workflow', dependent: :destroy
33
-
34
- # In a perfect world, there would be a join table that enforced uniqueness on the ID.
35
- has_one :active_workflow, -> { where(active: true) }, class_name: 'Sipity::Workflow', foreign_key: :permission_template_id
36
-
37
72
  ##
38
73
  # @note this is a convenience method for +Hyrax.query_service.find_by(id: template.source_id)+
39
74
  #
@@ -153,6 +188,57 @@ module Hyrax
153
188
  visibility == value
154
189
  end
155
190
 
191
+ ##
192
+ # @return [Array<String>]
193
+ def edit_users
194
+ agent_ids_for(access: 'manage', agent_type: 'user')
195
+ end
196
+
197
+ ##
198
+ # @return [Array<String>]
199
+ def edit_groups
200
+ agent_ids_for(access: 'manage', agent_type: 'group')
201
+ end
202
+
203
+ ##
204
+ # @return [Array<String>]
205
+ def read_users
206
+ (agent_ids_for(access: 'view', agent_type: 'user') +
207
+ agent_ids_for(access: 'deposit', agent_type: 'user')).uniq
208
+ end
209
+
210
+ ##
211
+ # @return [Array<String>]
212
+ def read_groups
213
+ (agent_ids_for(access: 'view', agent_type: 'group') +
214
+ agent_ids_for(access: 'deposit', agent_type: 'group')).uniq -
215
+ [::Ability.registered_group_name, ::Ability.public_group_name]
216
+ end
217
+
218
+ ##
219
+ # @return [Boolean]
220
+ def reset_access_controls(interpret_visibility: false)
221
+ reset_access_controls_for(collection: source_model,
222
+ interpret_visibility: interpret_visibility)
223
+ end
224
+
225
+ ##
226
+ # @return [Boolean]
227
+ def reset_access_controls_for(collection:, interpret_visibility: false)
228
+ interpreted_read_groups = read_groups
229
+
230
+ if interpret_visibility
231
+ visibilities = Hyrax::VisibilityMap.instance
232
+ interpreted_read_groups -= visibilities.deletions_for(visibility: collection.visibility)
233
+ interpreted_read_groups += visibilities.additions_for(visibility: collection.visibility)
234
+ end
235
+
236
+ collection.update!(edit_users: edit_users,
237
+ edit_groups: edit_groups,
238
+ read_users: read_users,
239
+ read_groups: interpreted_read_groups.uniq)
240
+ end
241
+
156
242
  private
157
243
 
158
244
  # If template requires no delays, check if date is exactly today
@@ -1,23 +1,28 @@
1
1
  # frozen_string_literal: true
2
- # The default virus scanner Hyrax::Works, ported from hydra_works.
3
- # If ClamAV is present, it will be used to check for the presence of a virus. If ClamAV is not
4
- # installed or otherwise not available to your application, Hyrax::Works does no virus checking
5
- # add assumes files have no viruses.
6
- #
7
- # @example to use a virus checker other than Hyrax::VirusScanner:
8
- # class MyScanner < Hyrax::Works::VirusScanner
9
- # def infected?
10
- # my_result = Scanner.check_for_viruses(file)
11
- # [return true or false]
12
- # end
13
- # end
14
- #
15
- # # Then set Hyrax::Works to use your scanner either in a config file or initializer:
16
- # Hyrax.config.virus_scanner = MyScanner
2
+
17
3
  module Hyrax
4
+ ##
5
+ # The default virus scanner ported from +Hyrax::Works+.
6
+ #
7
+ # If ClamAV is present, it will be used to check for the presence of a virus.
8
+ # If ClamAV is not installed or otherwise not available to your application,
9
+ # +Hyrax::Works+ does no virus checking add assumes files have no viruses.
10
+ #
11
+ # @example to use a virus checker other than Hyrax::VirusScanner:
12
+ # class MyScanner < Hyrax::Works::VirusScanner
13
+ # def infected?
14
+ # my_result = Scanner.check_for_viruses(file)
15
+ # [return true or false]
16
+ # end
17
+ # end
18
+ #
19
+ # # Then set Hyrax::Works to use your scanner either in a config file or initializer:
20
+ # Hyrax.config.virus_scanner = MyScanner
21
+ #
18
22
  class VirusScanner
19
23
  attr_reader :file
20
24
 
25
+ ##
21
26
  # @api public
22
27
  # @param file [String]
23
28
  def self.infected?(file)
@@ -28,7 +33,9 @@ module Hyrax
28
33
  @file = file
29
34
  end
30
35
 
31
- # Override this method to use your own virus checking software
36
+ ##
37
+ # @note Override this method to use your own virus checking software
38
+ #
32
39
  # @return [Boolean]
33
40
  def infected?
34
41
  defined?(ClamAV) ? clam_av_scanner : null_scanner
@@ -41,8 +48,10 @@ module Hyrax
41
48
  true
42
49
  end
43
50
 
44
- # Always return zero if there's nothing available to check for viruses. This means that
45
- # we assume all files have no viruses because we can't conclusively say if they have or not.
51
+ ##
52
+ # Always return zero if there's nothing available to check for viruses.
53
+ # This means that we assume all files have no viruses because we can't
54
+ # conclusively say if they have or not.
46
55
  def null_scanner
47
56
  warning "Unable to check #{file} for viruses because no virus scanner is defined" unless
48
57
  Rails.env.test?
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Sipity
3
+ ##
3
4
  # A proxy for something that can take an action.
4
5
  #
5
6
  # * A User can be an agent
@@ -1,19 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
  module Sipity
3
- # A proxy for the entity that is being processed via workflows.
3
+ ##
4
+ # A proxy for the entity (e.g. a repository Object) that is being processed
5
+ # via workflows.
4
6
  #
5
- # By using a proxy, we need not worry about polluting the proxy's concerns
6
- # with things related to workflow processing.
7
+ # Objects of this class (and their underlying database records) represent
8
+ # another object in the context of the workflow process. By using a proxy,
9
+ # we can avoid polluting the repository object's data and behavior with
10
+ # things related to workflow processing. This means we can move an object into
11
+ # and through a workflow without making changes independently from curatorial
12
+ # considerations (e.g. metadata changes).
13
+ #
14
+ # Keeping this object and interface separate also presents a clear seam for
15
+ # alternative solutions.
16
+ #
17
+ # The {Sipity::Entity} relates to repository objects via a +GlobalID+ URI via
18
+ # {#proxy_for_global_id}. Since the repository objects aren't assumed to be
19
+ # +ActiveRecord+ or +ActiveModel+ compatible, we use the URI-based system
20
+ # provided by +GlobalID+ to ensure that this relationship functions independent
21
+ # of the modelling system used for the respository objects. {#proxy_for} is
22
+ # provided as a convenience method for retrieving the underlying repository
23
+ # object.
24
+ #
25
+ # Each {Sipity::Entity} holds a relationship to a {Sipity::Workflow}, which is
26
+ # the active workflow on the object represented by the {Entity}. It also holds
27
+ # a reference to a {Sipity::WorkflowState}, which is the current state of the
28
+ # object within the workflow.
7
29
  #
8
- # The goal is to keep this behavior separate, so that we can possibly
9
- # extract the information.
10
30
  # @example To get the Sipity::Entity for a work
11
31
  # work = GenericWork.first
12
- # work_global_id = Hyrax::GlobalID(work).to_s
13
- # => "gid://whatever/GenericWork/3x816m604"
14
- # Sipity::Entity.where(proxy_for_global_id: work_global_id).first
32
+ # Sipity::Entity(work)
15
33
  # => #<Sipity::Entity id: 1, proxy_for_global_id: "gid://whatever/GenericWork/3x816m604",
16
34
  # workflow_id: 8, workflow_state_id: 20, created_at: "2017-07-07 13:39:42", updated_at: "2017-07-07 13:39:42">
35
+ #
36
+ # @see https://github.com/rails/globalid Rails' GlobalID library
17
37
  class Entity < ActiveRecord::Base
18
38
  self.table_name = 'sipity_entities'
19
39
 
@@ -35,6 +55,8 @@ module Sipity
35
55
  # Defines the method #workflow_name
36
56
  delegate :name, to: :workflow, prefix: :workflow
37
57
 
58
+ ##
59
+ # @return [Object] the thing this +Entity+ represents.
38
60
  def proxy_for
39
61
  @proxy_for ||= GlobalID::Locator.locate(proxy_for_global_id)
40
62
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Sipity
3
+ ##
3
4
  # A named workflow for processing an entity. Originally I had thought of
4
5
  # calling this a Type, but once I extracted the Processing submodule,
5
6
  # type felt to much of a noun, not conveying potentiality. Workflow
data/app/models/sipity.rb CHANGED
@@ -1,6 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ ##
4
+ # {Sipity} is a workflow/state engine.
5
+ #
6
+ # This module and the classes namespaced within it provide a domain model and
7
+ # implementation for managing the movement of repository objects through a
8
+ # defined workflow. The workflows themselves are configured using JSON documents
9
+ # and loaded into the database/domain model as {Sipity::Workflow}. Each workflow
10
+ # can be understood as finite sets of {Sipity::WorkflowState},
11
+ # {Sipity::WorkflowAction} (transitions from state to state), and {Sipity::Role}
12
+ # authorized to carry out each action.
13
+ #
14
+ # Any uniquely identifiable object can be managed by a {Sipity::Workflow}.
15
+ # Normally Hyrax uses workflows to handle the deposit process and maintenance
16
+ # lifecycle of repository objects at the level of the Work (within the Hydra
17
+ # Works model). Objects are represented within the Sipity engine's domain model
18
+ # by a {Sipity::Entity}. Each object has at most one {Sipity::Entity}, is
19
+ # governed by one {Sipity::Workflow}, and in one {Sipity::WorkflowState} at any
20
+ # given time.
21
+ #
22
+ # Some use cases for Sipity workflows include:
23
+ #
24
+ # * Simple unmediated deposit with on-deposit notifications and actions;
25
+ # * Mediated deposit with one or more review steps;
26
+ # * Publication workflows requiring multiple levels of editorial approval
27
+ # and/or peer review;
28
+ # * Preservation processes involving post-deposit selection of objects for
29
+ # replication to external preservation platforms and/or required action
30
+ # in case of failed fixity checks;
31
+ # * Electronic Thesis & Dissertation submission processes involving (e.g.)
32
+ # student deposit, committee and/or departmental approval, centralized/
33
+ # graduate school review, and a final graduation step.
34
+ #
3
35
  module Sipity
36
+ ##
37
+ # Cast a given input (e.g. a +::User+ or {Hyrax::Group} to a {Sipity::Agent}).
38
+ #
39
+ # @param input [Object]
40
+ #
41
+ # @return [Sipity::Agent]
4
42
  def Agent(input, &block) # rubocop:disable Naming/MethodName
5
43
  result = case input
6
44
  when Sipity::Agent
@@ -13,6 +51,10 @@ module Sipity
13
51
 
14
52
  ##
15
53
  # Cast an object to an Entity
54
+ #
55
+ # @param input [Object]
56
+ #
57
+ # @return [Sipity::Entity]
16
58
  # rubocop:disable Naming/MethodName, Metrics/CyclomaticComplexity, Metrics/MethodLength
17
59
  def Entity(input, &block)
18
60
  result = case input
@@ -59,16 +59,8 @@ module Hyrax
59
59
  # > remove files from a work, and add new works to the set.
60
60
  return true if @current_ability.can?(:manage, permission_template)
61
61
 
62
- # Otherwise, we check if the workflow was setup, active, and
63
- # allows_access_grants.
64
- wf = workflow(permission_template: permission_template)
65
- return false unless wf
66
- wf.allows_access_grant?
67
- end
68
-
69
- def workflow(permission_template:)
70
- return unless permission_template.active_workflow
71
- Sipity::Workflow.find_by!(id: permission_template.active_workflow.id)
62
+ # Otherwise, we check if the active workflow allows access grants
63
+ !!permission_template.active_workflow&.allows_access_grant?
72
64
  end
73
65
  end
74
66
  end
@@ -12,7 +12,11 @@ module Hyrax
12
12
  end
13
13
 
14
14
  def total_viewable_items
15
- ActiveFedora::Base.where("isPartOf_ssim:#{id}").accessible_by(current_ability).count
15
+ field_pairs = { "isPartOf_ssim" => id.to_s }
16
+ SolrQueryService.new
17
+ .with_field_pairs(field_pairs: field_pairs)
18
+ .accessible_by(ability: current_ability)
19
+ .count
16
20
  end
17
21
 
18
22
  # AdminSet cannot be deleted if default set or non-empty
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ ##
4
+ # @api public
5
+ # @since 3.1.0
6
+ #
7
+ # Presents select options for admin sets.
8
+ #
9
+ # Each entry in the {#select_options} return value provides a label for
10
+ # display, an id to serve as the value, and a data hash which is used as HTML5
11
+ # data entries. The data entries can be used as hooks for Javascript
12
+ # to control input validation taking into account the Admin Set and
13
+ # `PermissionTemplate` rules (`visibility_component.es6` does this, for
14
+ # example).
15
+ #
16
+ # @note this supersedes the older +Hyrax::AdminSetOptionsPresenter+, which
17
+ # actied more like a "service" sending database queries to Solr and
18
+ # ActiveRecord. this version seeks only to present the input data and
19
+ # relies on its caller to pass in the right data.
20
+ class AdminSetSelectionPresenter
21
+ ##
22
+ # @param [Array<#id>]
23
+ def initialize(admin_sets:)
24
+ @admin_sets = admin_sets
25
+ end
26
+
27
+ ##
28
+ # @return [Array<Array<String, String, Hash>>] an array suitable for a form
29
+ # input `collection:` parameter. it should contain a label, an id, and a
30
+ # hash of HTML5 data attributes.
31
+ def select_options
32
+ @admin_sets.map do |admin_set|
33
+ case admin_set
34
+ when OptionsEntry
35
+ admin_set.result
36
+ else
37
+ OptionsEntry.new(admin_set: admin_set).result
38
+ end
39
+ end
40
+ end
41
+
42
+ ##
43
+ # @api public
44
+ class OptionsEntry
45
+ ##
46
+ # @!attribute [rw] admin_set
47
+ # @return [AdministrativeSet, SolrDocument]
48
+ # @!attribute [rw] permission_template
49
+ # @return [PermissionTemplate]
50
+ # @!attribute [rw] permit_sharing
51
+ # @return [Boolean]
52
+ attr_accessor :admin_set, :permission_template, :permit_sharing
53
+
54
+ ##
55
+ # @param [AdministrativeSet, SolrDocument] admin_set
56
+ # @param [PermissionTemplate] permission_template
57
+ # @param [Boolean] permit_sharing
58
+ def initialize(admin_set:, permission_template: nil, permit_sharing: false)
59
+ @admin_set = admin_set
60
+ @permission_template = permission_template
61
+ @permit_sharing = permit_sharing
62
+ end
63
+
64
+ ##
65
+ # @return [Array<String, String, Hash>]
66
+ def result
67
+ [label, id, data]
68
+ end
69
+
70
+ ##
71
+ # @return [String]
72
+ def label
73
+ Array(admin_set.title).first || admin_set.to_s
74
+ end
75
+
76
+ ##
77
+ # @return [String]
78
+ def id
79
+ admin_set.id.to_s
80
+ end
81
+
82
+ ##
83
+ # @return [Hash{String => Object}]
84
+ def data
85
+ {}.tap do |data|
86
+ data['data-sharing'] = permit_sharing
87
+
88
+ if permission_template
89
+ data.merge!(data_for(permission_template))
90
+ else
91
+ data['data-release-no-delay'] = true
92
+ end
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ ##
99
+ # @api private
100
+ def data_for(template)
101
+ {}.tap do |data|
102
+ if template.release_no_delay?
103
+ data['data-release-no-delay'] = true
104
+ elsif template.release_date.present?
105
+ data['data-release-date'] = template.release_date
106
+ end
107
+
108
+ data['data-release-before-date'] = true if
109
+ template.release_before_date?
110
+ data['data-visibility'] = template.visibility if
111
+ template.visibility.present?
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end