hyrax 3.2.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +3 -6
  3. data/.dassie/.env +1 -2
  4. data/.dassie/Gemfile +7 -3
  5. data/.dassie/app/models/user.rb +0 -2
  6. data/.dassie/config/analytics.yml +12 -5
  7. data/.dassie/config/environments/development.rb +2 -0
  8. data/.dassie/config/initializers/hyrax.rb +2 -0
  9. data/.dassie/db/migrate/20210921150120_enable_uuid_extension.valkyrie_engine.rb +7 -0
  10. data/.dassie/db/migrate/20210921150121_create_orm_resources.valkyrie_engine.rb +19 -0
  11. data/.dassie/db/migrate/20210921150122_add_model_type_to_orm_resources.valkyrie_engine.rb +7 -0
  12. data/.dassie/db/migrate/20210921150123_change_model_type_to_internal_model.valkyrie_engine.rb +7 -0
  13. data/.dassie/db/migrate/20210921150124_create_path_gin_index.valkyrie_engine.rb +7 -0
  14. data/.dassie/db/migrate/20210921150125_create_internal_resource_index.valkyrie_engine.rb +7 -0
  15. data/.dassie/db/migrate/20210921150126_create_updated_at_index.valkyrie_engine.rb +7 -0
  16. data/.dassie/db/migrate/20210921150127_add_optimistic_locking_to_orm_resources.valkyrie_engine.rb +7 -0
  17. data/.dassie/db/migrate/20211130181150_create_default_administrative_set.rb +8 -0
  18. data/.dassie/db/schema.rb +20 -1
  19. data/.env +7 -4
  20. data/.github/workflows/main.yml +17 -0
  21. data/.github/workflows/release.yml +17 -0
  22. data/.gitignore +1 -0
  23. data/.regen +1 -1
  24. data/CONTAINERS.md +13 -10
  25. data/README.md +37 -0
  26. data/app/assets/javascripts/hyrax/admin/graphs.es6 +34 -37
  27. data/app/assets/javascripts/hyrax/analytics_events.js +69 -0
  28. data/app/assets/javascripts/hyrax/collapse.js +24 -0
  29. data/app/assets/javascripts/hyrax/collections.js +1 -2
  30. data/app/assets/javascripts/hyrax/ga_events.js +2 -8
  31. data/app/assets/javascripts/hyrax/reports-buttons.js +33 -0
  32. data/app/assets/javascripts/hyrax.js +2 -1
  33. data/app/assets/stylesheets/_bootstrap-default-overrides.scss +9 -0
  34. data/app/authorities/qa/authorities/collections.rb +4 -5
  35. data/app/authorities/qa/authorities/find_works.rb +1 -1
  36. data/app/controllers/concerns/hyrax/breadcrumbs_for_collection_analytics.rb +26 -0
  37. data/app/controllers/concerns/hyrax/breadcrumbs_for_works_analytics.rb +26 -0
  38. data/app/controllers/concerns/hyrax/controller.rb +22 -0
  39. data/app/controllers/hyrax/admin/analytics/analytics_controller.rb +40 -0
  40. data/app/controllers/hyrax/admin/analytics/collection_reports_controller.rb +61 -0
  41. data/app/controllers/hyrax/admin/analytics/work_reports_controller.rb +122 -0
  42. data/app/controllers/hyrax/collections_controller.rb +4 -1
  43. data/app/controllers/hyrax/dashboard/collections_controller.rb +15 -6
  44. data/app/controllers/hyrax/dashboard_controller.rb +8 -0
  45. data/app/controllers/hyrax/stats_controller.rb +3 -1
  46. data/app/forms/hyrax/forms/pcdm_collection_form.rb +3 -0
  47. data/app/indexers/hyrax/valkyrie_file_set_indexer.rb +1 -1
  48. data/app/jobs/characterize_job.rb +28 -1
  49. data/app/jobs/valkyrie_ingest_job.rb +56 -0
  50. data/app/models/concerns/hyrax/ability.rb +26 -5
  51. data/app/models/concerns/hyrax/solr_document/metadata.rb +1 -0
  52. data/app/models/file_download_stat.rb +4 -4
  53. data/app/models/hyrax/default_administrative_set.rb +42 -0
  54. data/app/models/hyrax/statistic.rb +31 -4
  55. data/app/presenters/hyrax/admin/dashboard_presenter.rb +8 -6
  56. data/app/presenters/hyrax/admin/repository_growth_presenter.rb +10 -5
  57. data/app/presenters/hyrax/admin/user_activity_presenter.rb +8 -12
  58. data/app/presenters/hyrax/file_set_presenter.rb +2 -0
  59. data/app/presenters/hyrax/menu_presenter.rb +4 -0
  60. data/app/presenters/hyrax/pcdm_member_presenter_factory.rb +1 -1
  61. data/app/presenters/hyrax/work_show_presenter.rb +5 -2
  62. data/app/presenters/hyrax/work_usage.rb +1 -0
  63. data/app/search_builders/hyrax/README.md +1 -1
  64. data/app/search_builders/hyrax/dashboard/collections_search_builder.rb +1 -1
  65. data/app/search_builders/hyrax/my/collections_search_builder.rb +1 -1
  66. data/app/services/hyrax/admin_set_create_service.rb +76 -14
  67. data/app/services/hyrax/analytics/google/events.rb +37 -0
  68. data/app/services/hyrax/analytics/google/events_daily.rb +72 -0
  69. data/app/services/hyrax/analytics/google/visits.rb +44 -0
  70. data/app/services/hyrax/analytics/google/visits_daily.rb +49 -0
  71. data/app/services/hyrax/analytics/google.rb +204 -0
  72. data/app/services/hyrax/analytics/matomo.rb +193 -0
  73. data/app/services/hyrax/analytics/results.rb +79 -0
  74. data/app/services/hyrax/analytics.rb +12 -82
  75. data/app/services/hyrax/characterization/valkyrie_characterization_service.rb +134 -0
  76. data/app/services/hyrax/collections/nested_collection_query_service.rb +8 -3
  77. data/app/services/hyrax/listeners/acl_index_listener.rb +3 -1
  78. data/app/services/hyrax/listeners/active_fedora_acl_index_listener.rb +3 -1
  79. data/app/services/hyrax/listeners/batch_notification_listener.rb +3 -1
  80. data/app/services/hyrax/listeners/file_metadata_listener.rb +19 -0
  81. data/app/services/hyrax/listeners/file_set_lifecycle_listener.rb +6 -2
  82. data/app/services/hyrax/listeners/file_set_lifecycle_notification_listener.rb +6 -2
  83. data/app/services/hyrax/listeners/member_cleanup_listener.rb +3 -0
  84. data/app/services/hyrax/listeners/metadata_index_listener.rb +9 -3
  85. data/app/services/hyrax/listeners/object_lifecycle_listener.rb +9 -3
  86. data/app/services/hyrax/listeners/proxy_deposit_listener.rb +3 -1
  87. data/app/services/hyrax/listeners/trophy_cleanup_listener.rb +3 -0
  88. data/app/services/hyrax/listeners/workflow_listener.rb +3 -1
  89. data/app/services/hyrax/listeners.rb +8 -0
  90. data/app/services/hyrax/restriction_service.rb +4 -0
  91. data/app/services/hyrax/statistics/users/over_time.rb +8 -5
  92. data/app/services/hyrax/statistics/works/over_time.rb +10 -0
  93. data/app/services/hyrax/work_uploads_handler.rb +4 -1
  94. data/app/views/hyrax/admin/analytics/_date_range_form.html.erb +11 -0
  95. data/app/views/hyrax/admin/analytics/collection_reports/_custom_range.html.erb +39 -0
  96. data/app/views/hyrax/admin/analytics/collection_reports/_monthly_summary.html.erb +48 -0
  97. data/app/views/hyrax/admin/analytics/collection_reports/_summary.html.erb +55 -0
  98. data/app/views/hyrax/admin/analytics/collection_reports/_top_collections.html.erb +55 -0
  99. data/app/views/hyrax/admin/analytics/collection_reports/index.html.erb +70 -0
  100. data/app/views/hyrax/admin/analytics/collection_reports/show.html.erb +94 -0
  101. data/app/views/hyrax/admin/analytics/work_reports/_custom_range.html.erb +43 -0
  102. data/app/views/hyrax/admin/analytics/work_reports/_monthly_summary.html.erb +35 -0
  103. data/app/views/hyrax/admin/analytics/work_reports/_summary.html.erb +60 -0
  104. data/app/views/hyrax/admin/analytics/work_reports/_top_file_set_downloads.html.erb +33 -0
  105. data/app/views/hyrax/admin/analytics/work_reports/_top_works.html.erb +40 -0
  106. data/app/views/hyrax/admin/analytics/work_reports/_work_counts.html.erb +18 -0
  107. data/app/views/hyrax/admin/analytics/work_reports/_work_files.html.erb +41 -0
  108. data/app/views/hyrax/admin/analytics/work_reports/index.html.erb +77 -0
  109. data/app/views/hyrax/admin/analytics/work_reports/show.html.erb +90 -0
  110. data/app/views/hyrax/admin/stats/show.html.erb +1 -1
  111. data/app/views/hyrax/base/_relationships_parent_row.html.erb +0 -1
  112. data/app/views/hyrax/base/show.html.erb +6 -0
  113. data/app/views/hyrax/collections/show.html.erb +4 -0
  114. data/app/views/hyrax/dashboard/_repository_growth.html.erb +5 -5
  115. data/app/views/hyrax/dashboard/_resource_type_graph.html.erb +41 -0
  116. data/app/views/hyrax/dashboard/_sidebar.html.erb +4 -1
  117. data/app/views/hyrax/dashboard/_tabs.html.erb +11 -0
  118. data/app/views/hyrax/dashboard/_user_activity.html.erb +17 -23
  119. data/app/views/hyrax/dashboard/_user_activity_graph.html.erb +55 -0
  120. data/app/views/hyrax/dashboard/_visibility_graph.html.erb +31 -0
  121. data/app/views/hyrax/dashboard/_work_type_graph.html.erb +41 -0
  122. data/app/views/hyrax/dashboard/collections/_form.html.erb +2 -1
  123. data/app/views/hyrax/dashboard/show_admin.html.erb +24 -45
  124. data/app/views/hyrax/dashboard/sidebar/_activity.html.erb +22 -0
  125. data/app/views/hyrax/file_sets/_actions.html.erb +4 -3
  126. data/app/views/hyrax/file_sets/show.html.erb +6 -0
  127. data/app/views/hyrax/my/collections/index.html.erb +1 -1
  128. data/app/views/hyrax/stats/_downloads.html.erb +18 -0
  129. data/app/views/hyrax/stats/_pageviews.html.erb +18 -0
  130. data/app/views/hyrax/stats/work.html.erb +17 -9
  131. data/app/views/layouts/_head_tag_content.html.erb +7 -2
  132. data/app/views/{_ga.html.erb → shared/_ga.html.erb} +3 -7
  133. data/app/views/shared/_matomo.html.erb +15 -0
  134. data/chart/hyrax/Chart.yaml +1 -1
  135. data/chart/hyrax/values.yaml +1 -1
  136. data/config/i18n-tasks.yml +2 -2
  137. data/config/initializers/listeners.rb +5 -5
  138. data/config/locales/hyrax.de.yml +194 -0
  139. data/config/locales/hyrax.en.yml +190 -12
  140. data/config/locales/hyrax.es.yml +194 -0
  141. data/config/locales/hyrax.fr.yml +194 -0
  142. data/config/locales/hyrax.it.yml +194 -0
  143. data/config/locales/hyrax.pt-BR.yml +194 -0
  144. data/config/locales/hyrax.zh.yml +194 -0
  145. data/config/routes.rb +4 -0
  146. data/docker-compose.yml +3 -1
  147. data/documentation/developing-your-hyrax-based-app.md +2 -2
  148. data/documentation/legacyREADME.md +1 -1
  149. data/hyrax.gemspec +3 -1
  150. data/lib/generators/hyrax/templates/config/analytics.yml +13 -7
  151. data/lib/generators/hyrax/templates/config/initializers/hyrax.rb +0 -13
  152. data/lib/generators/hyrax/templates/db/migrate/20211130181150_create_default_administrative_set.rb.erb +8 -0
  153. data/lib/generators/hyrax/work/templates/feature_spec.rb.erb +3 -1
  154. data/lib/hyrax/configuration.rb +67 -5
  155. data/lib/hyrax/engine.rb +7 -6
  156. data/lib/hyrax/publisher.rb +4 -0
  157. data/lib/hyrax/transactions/admin_set_create.rb +22 -0
  158. data/lib/hyrax/transactions/container.rb +11 -0
  159. data/lib/hyrax/version.rb +1 -1
  160. data/lib/tasks/regenerate_derivatives.rake +1 -1
  161. data/lib/wings/setup.rb +15 -0
  162. data/lib/wings/valkyrie/persister.rb +16 -0
  163. data/template.rb +1 -1
  164. data/vendor/assets/javascripts/morris/morris.min.js +1 -7
  165. data/vendor/assets/stylesheets/morris.js/0.5.1/morris.css +1 -1
  166. metadata +87 -11
  167. data/app/views/hyrax/dashboard/_repository_objects.html.erb +0 -28
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ class ValkyrieIngestJob < Hyrax::ApplicationJob
3
+ queue_as Hyrax.config.ingest_queue_name
4
+
5
+ ##
6
+ # @param [Valkyrie::StorageAdapter::StreamFile] file
7
+ def perform(file)
8
+ ingest(file: file)
9
+ end
10
+
11
+ ##
12
+ # @param [Valkyrie::StorageAdapter::StreamFile] file
13
+ #
14
+ # @return [void]
15
+ def ingest(file:)
16
+ file_set = Hyrax.query_service.find_by(id: file.file_set_uri)
17
+ updated_metadata = upload_file(file: file, file_set: file_set)
18
+
19
+ add_file_to_file_set(file_set: file_set, file_metadata: updated_metadata)
20
+ end
21
+
22
+ ##
23
+ # @todo this should publish something to allow the fileset
24
+ # to reindex its membership
25
+ # @param [Hyrax::FileSet] file_set the file set to add to
26
+ # @param [Hyrax::FileMetadata] file_metadata the metadata object representing
27
+ # the file to add
28
+ #
29
+ # @return [Hyrax::FileSet] updated file set
30
+ def add_file_to_file_set(file_set:, file_metadata:)
31
+ file_set.file_ids << file_metadata.id
32
+ Hyrax.persister.save(resource: file_set)
33
+ end
34
+
35
+ ##
36
+ # @param [Hyrax::UploadedFile] file
37
+ # @param [Hyrax::FileSet] file_set
38
+ #
39
+ # @return [Hyrax::FileMetadata] the metadata representing the uploaded file
40
+ def upload_file(file:, file_set:)
41
+ carrier_wave_sanitized_file = file.uploader.file
42
+ uploaded = Hyrax.storage_adapter
43
+ .upload(resource: file_set,
44
+ file: carrier_wave_sanitized_file,
45
+ original_filename: carrier_wave_sanitized_file.original_filename)
46
+
47
+ file_metadata = Hyrax.custom_queries.find_file_metadata_by(id: uploaded.id)
48
+ file_metadata.file_set_id = file.file_set_uri
49
+ file_metadata.file_identifier = uploaded.id
50
+ file_metadata.size = uploaded.size
51
+
52
+ Hyrax.publisher.publish("object.file.uploaded", metadata: file_metadata)
53
+
54
+ file_metadata
55
+ end
56
+ end
@@ -1,7 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
  module Hyrax
3
3
  ##
4
- # Provides Hyrax's engine level user/group permissions.
4
+ # Provides Hyrax's engine level user/group authorizations.
5
+ #
6
+ # Authorization (allow or deny) of the following actions is managed by the
7
+ # rules defined here:
8
+ #
9
+ # - read:
10
+ # - show:
11
+ # - edit:
12
+ # - update:
13
+ # - create:
14
+ # - discover:
15
+ # - manage:
16
+ # - download:
17
+ # - destroy:
18
+ # - collect:
19
+ # - toggle_trophy:
20
+ # - transfer:
21
+ # - accept:
22
+ # - reject:
23
+ # - manage_any:
24
+ # - create_any:
25
+ # - view_admin_show_any:
26
+ # - review:
27
+ # - create_collection_type:
5
28
  #
6
29
  # @note This is intended as a mixin layered over
7
30
  # +Blacklight::AccessControls::Ability+ and +Hydra::AccessControls+. Its
@@ -390,10 +413,8 @@ module Hyrax
390
413
  # Returns true if the current user is the depositor of the specified work
391
414
  # @param document_id [String] the id of the document.
392
415
  def user_is_depositor?(document_id)
393
- Hyrax::WorkRelation.new.search_with_conditions(
394
- id: document_id,
395
- DepositSearchBuilder.depositor_field => current_user.user_key
396
- ).any?
416
+ doc = Hyrax::SolrService.search_by_id(document_id, fl: 'depositor_ssim')
417
+ current_user.user_key == doc.fetch('depositor_ssim').first
397
418
  end
398
419
 
399
420
  def curation_concerns_models
@@ -54,6 +54,7 @@ module Hyrax
54
54
  attribute :admin_set, Solr::Array, "admin_set_tesim"
55
55
  attribute :member_ids, Solr::Array, "member_ids_ssim"
56
56
  attribute :member_of_collection_ids, Solr::Array, "member_of_collection_ids_ssim"
57
+ attribute :member_of_collections, Solr::Array, "member_of_collections_ssim"
57
58
  attribute :description, Solr::Array, "description_tesim"
58
59
  attribute :abstract, Solr::Array, "abstract_tesim"
59
60
  attribute :title, Solr::Array, "title_tesim"
@@ -12,10 +12,10 @@ class FileDownloadStat < Hyrax::Statistic
12
12
  Rails.logger.error("Google Analytics profile has not been established. Unable to fetch statistics.")
13
13
  return []
14
14
  end
15
- profile.hyrax__download(sort: 'date',
16
- start_date: start_date,
17
- end_date: Date.yesterday,
18
- limit: 10_000)
15
+ profile.hyrax__analytics__google__download(sort: 'date',
16
+ start_date: start_date,
17
+ end_date: Date.yesterday,
18
+ limit: 10_000)
19
19
  .for_file(file.id)
20
20
  end
21
21
 
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ # This class stores the id of the default `Hyrax::AdministrativeSet`. This is
4
+ # used to populate a cache of the default admin set in Hyrax::Configuration.
5
+ #
6
+ # @see Hyrax::Configuration.default_admin_set
7
+ # @see Hyrax::Configuration.default_admin_set_id
8
+ # @see Hyrax::Configuration.reset_default_admin_set
9
+ class DefaultAdministrativeSet < ActiveRecord::Base
10
+ self.table_name = 'hyrax_default_administrative_set'
11
+
12
+ class << self
13
+ # Set the default admin set id in the first record.
14
+ # @param default_admin_set_id [String | Valkyrie::ID] id of the new default admin set
15
+ def update(default_admin_set_id:)
16
+ validate_id(default_admin_set_id)
17
+ Hyrax.config.reset_default_admin_set
18
+
19
+ # saving default_admin_set_id for the first time
20
+ return new(default_admin_set_id: default_admin_set_id.to_s).save if count.zero?
21
+
22
+ # replacing previously saved default_admin_set_id
23
+ existing = first
24
+ existing.default_admin_set_id = default_admin_set_id.to_s
25
+ existing.save
26
+ end
27
+
28
+ def save_supported?
29
+ ActiveRecord::Base.connection.table_exists? table_name
30
+ end
31
+
32
+ private
33
+
34
+ def validate_id(id)
35
+ # The id is validated prior to updating because a bad default admin set
36
+ # will cause lots of problems.
37
+ return true if id.is_a?(String) || id.is_a?(Valkyrie::ID)
38
+ raise ArgumentError, "default_admin_set_id must be a non-blank String or Valkyrie::ID"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -34,13 +34,40 @@ module Hyrax
34
34
  Rails.logger.error("Google Analytics profile has not been established. Unable to fetch statistics.")
35
35
  return []
36
36
  end
37
- profile.hyrax__pageview(sort: 'date',
38
- start_date: start_date,
39
- end_date: Date.yesterday,
40
- limit: 10_000)
37
+ profile.hyrax__analytics__google__pageviews(sort: 'date',
38
+ start_date: start_date,
39
+ end_date: Date.yesterday,
40
+ limit: 10_000)
41
41
  .for_path(path)
42
42
  end
43
43
 
44
+ def query_works(query)
45
+ models = Hyrax.config.curation_concerns.map { |m| "\"#{m}\"" }
46
+ ActiveFedora::SolrService.query("has_model_ssim:(#{models.join(' OR ')})", fl: query, rows: 100_000)
47
+ end
48
+
49
+ def work_types
50
+ results = query_works("human_readable_type_tesim")
51
+ results.group_by { |result| result['human_readable_type_tesim'].join('') }.transform_values(&:count)
52
+ end
53
+
54
+ def resource_types
55
+ results = query_works("resource_type_tesim")
56
+ resource_types = []
57
+ results.each do |y|
58
+ if y["resource_type_tesim"].nil? || (y["resource_type_tesim"] == [""])
59
+ resource_types.push("Unknown")
60
+ elsif y["resource_type_tesim"].count > 1
61
+ y["resource_type_tesim"].each do |t|
62
+ resource_types.push(t)
63
+ end
64
+ else
65
+ resource_types.push(y["resource_type_tesim"].join(""))
66
+ end
67
+ end
68
+ resource_types.group_by { |rt| rt }.transform_values(&:count)
69
+ end
70
+
44
71
  private
45
72
 
46
73
  def cached_stats(object, start_date, _method)
@@ -3,20 +3,22 @@ module Hyrax
3
3
  module Admin
4
4
  class DashboardPresenter
5
5
  # @return [Fixnum] the number of currently registered users
6
- def user_count
7
- ::User.where(guest: false).count
6
+ def user_count(start_date, end_date)
7
+ ::User.where(guest: false)
8
+ .where({ created_at: start_date.to_date.beginning_of_day..end_date.to_date.end_of_day })
9
+ .count
8
10
  end
9
11
 
10
12
  def repository_objects
11
13
  @repository_objects ||= Admin::RepositoryObjectPresenter.new
12
14
  end
13
15
 
14
- def repository_growth
15
- @repository_growth ||= Admin::RepositoryGrowthPresenter.new
16
+ def repository_growth(start_date, end_date)
17
+ @repository_growth ||= Admin::RepositoryGrowthPresenter.new(start_date, end_date)
16
18
  end
17
19
 
18
- def user_activity
19
- @user_activity ||= Admin::UserActivityPresenter.new
20
+ def user_activity(start_date, end_date)
21
+ @user_activity ||= Admin::UserActivityPresenter.new(start_date, end_date)
20
22
  end
21
23
  end
22
24
  end
@@ -2,26 +2,31 @@
2
2
  module Hyrax
3
3
  module Admin
4
4
  class RepositoryGrowthPresenter
5
- def initialize
6
- @x_min = 90.days.ago.beginning_of_day
5
+ def initialize(start_date, end_date)
6
+ @x_min = start_date
7
+ @x_max = end_date
7
8
  @date_format = ->(x) { x.strftime('%F') }
8
9
  end
9
10
 
10
11
  def as_json(*)
11
12
  works.to_a.zip(collections.to_a).map do |e|
12
- { y: e.first.first, a: e.first.last, b: e.last.last }
13
+ { y: e.first.first, works: e.first.last, collections: e.last.last }
13
14
  end
14
15
  end
15
16
 
16
17
  private
17
18
 
18
19
  def works
19
- Hyrax::Statistics::Works::OverTime.new(x_min: @x_min,
20
+ Hyrax::Statistics::Works::OverTime.new(delta_x: 1,
21
+ x_min: @x_min,
22
+ x_max: @x_max,
20
23
  x_output: @date_format).points
21
24
  end
22
25
 
23
26
  def collections
24
- Hyrax::Statistics::Collections::OverTime.new(x_min: @x_min,
27
+ Hyrax::Statistics::Collections::OverTime.new(delta_x: 1,
28
+ x_min: @x_min,
29
+ x_max: @x_max,
25
30
  x_output: @date_format).points
26
31
  end
27
32
  end
@@ -2,28 +2,24 @@
2
2
  module Hyrax
3
3
  module Admin
4
4
  class UserActivityPresenter
5
- def initialize
6
- @x_min = 90.days.ago.beginning_of_day
7
- @date_format = ->(x) { x.strftime('%b %-d') }
5
+ def initialize(start_date, end_date)
6
+ @x_min = start_date
7
+ @x_max = end_date
8
+ @date_format = ->(x) { x }
8
9
  end
9
10
 
10
11
  def as_json(*)
11
- new_users.to_a.zip(returning_users.to_a).map do |e|
12
- { y: e.first.first, a: e.first.last, b: e.last.try(:last) }
13
- end
12
+ new_users.to_a
14
13
  end
15
14
 
16
15
  private
17
16
 
18
17
  def new_users
19
- Hyrax::Statistics::Users::OverTime.new(x_min: @x_min,
18
+ Hyrax::Statistics::Users::OverTime.new(delta_x: 1,
19
+ x_min: @x_min,
20
+ x_max: @x_max,
20
21
  x_output: @date_format).points
21
22
  end
22
-
23
- # TODO: using google analytics
24
- def returning_users
25
- []
26
- end
27
23
  end
28
24
  end
29
25
  end
@@ -34,6 +34,8 @@ module Hyrax
34
34
  :original_file_id,
35
35
  to: :solr_document
36
36
 
37
+ delegate :member_of_collection_ids, to: :parent
38
+
37
39
  def workflow
38
40
  nil
39
41
  end
@@ -43,6 +43,10 @@ module Hyrax
43
43
  end
44
44
  end
45
45
 
46
+ def analytics_reporting_section?
47
+ %w[work_reports collection_reports].include?(controller_name)
48
+ end
49
+
46
50
  # Draw a collaspable menu section. The passed block should contain <li> items.
47
51
  def collapsable_section(text, id:, icon_class:, open:, &block)
48
52
  CollapsableSectionPresenter.new(view_context: view_context,
@@ -19,7 +19,7 @@ module Hyrax
19
19
  ##
20
20
  # @param [#member_ids] object
21
21
  # @param [::Ability] ability
22
- def initialize(object, ability)
22
+ def initialize(object, ability, _request = nil)
23
23
  @object = object
24
24
  @ability = ability
25
25
  end
@@ -10,10 +10,11 @@ module Hyrax
10
10
  attr_writer :member_presenter_factory
11
11
  attr_accessor :solr_document, :current_ability, :request
12
12
 
13
- class_attribute :collection_presenter_class
13
+ class_attribute :collection_presenter_class, :presenter_factory_class
14
14
 
15
15
  # modify this attribute to use an alternate presenter class for the collections
16
16
  self.collection_presenter_class = CollectionPresenter
17
+ self.presenter_factory_class = MemberPresenterFactory
17
18
 
18
19
  # Methods used by blacklight helpers
19
20
  delegate :has?, :first, :fetch, :export_formats, :export_as, to: :solr_document
@@ -304,7 +305,9 @@ module Hyrax
304
305
 
305
306
  def member_presenter_factory
306
307
  @member_presenter_factory ||=
307
- MemberPresenterFactory.new(solr_document, current_ability, request)
308
+ self.class
309
+ .presenter_factory_class
310
+ .new(solr_document, current_ability, request)
308
311
  end
309
312
 
310
313
  def graph
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hyrax
4
+ # TODO: - Analytics do we still need this?
4
5
  # Follows the model established by {FileUsage}.
5
6
  #
6
7
  # Called by the stats controller, it finds cached work pageview data,
@@ -66,4 +66,4 @@ module Hyrax
66
66
  end
67
67
  ```
68
68
 
69
- There is no point having the other `filter_models` methods apply `:fq`s that we then try to undo or overwrite. In general, directly overwriting the whole `default_processor_chain` or solr parameters like `:fq` is less flexible than appending constraints sufficient for your use case. In particular, you might find that you have overwritten components that implement access controls, thereby making your SearchBuilder less useful and less secure. When in doubt, examine the actual solr queries produced.
69
+ There is no point having the other `filter_models` methods apply `:fq`s that we then try to undo or overwrite. In general, directly overwriting the whole `default_processor_chain` or solr parameters like `:fq` is less flexible than appending constraints sufficient for your use case. In particular, you might find that you have overwritten components that implement access controls, thereby making your SearchBuilder less useful and less secure. When in doubt, examine the actual solr queries produced.
@@ -9,7 +9,7 @@ module Hyrax
9
9
 
10
10
  # This overrides the models in FilterByType
11
11
  def models
12
- [::AdminSet, ::Collection, Hyrax.config.collection_model.safe_constantize].uniq.compact
12
+ [::AdminSet, ::Collection, Hyrax.config.collection_class].uniq.compact
13
13
  end
14
14
 
15
15
  # adds a filter to exclude collections and admin sets created by the
@@ -21,6 +21,6 @@ class Hyrax::My::CollectionsSearchBuilder < ::Hyrax::CollectionSearchBuilder
21
21
  # This overrides the models in FilterByType
22
22
  # @return [Array<Class>] a list of classes to include
23
23
  def models
24
- [::AdminSet, Hyrax::AdministrativeSet, Hyrax.config.collection_model.safe_constantize].compact
24
+ [::AdminSet, Hyrax::AdministrativeSet, ::Collection, Hyrax.config.collection_class].uniq.compact
25
25
  end
26
26
  end
@@ -13,8 +13,9 @@ module Hyrax
13
13
  DEFAULT_ID = 'admin_set/default'
14
14
  DEFAULT_TITLE = ['Default Admin Set'].freeze
15
15
 
16
- class_attribute :permissions_create_service
16
+ class_attribute :permissions_create_service, :default_admin_set_persister
17
17
  self.permissions_create_service = Hyrax::Collections::PermissionsCreateService
18
+ self.default_admin_set_persister = Hyrax::DefaultAdministrativeSet
18
19
 
19
20
  class << self
20
21
  # @api public
@@ -40,16 +41,15 @@ module Hyrax
40
41
  # @see Hyrax::AdministrativeSet
41
42
  # @raise [RuntimeError] if admin set cannot be persisted
42
43
  def find_or_create_default_admin_set
43
- Hyrax.query_service.find_by(id: DEFAULT_ID)
44
- rescue Valkyrie::Persistence::ObjectNotFoundError
45
- create_default_admin_set!
44
+ find_default_admin_set || create_default_admin_set!
46
45
  end
47
46
 
48
47
  # @api public
49
- # Is the admin_set the default Hyrax::AdministrativeSet
50
- # @param id [#to_s] the id of the admin set to check
48
+ # @param id [#to_s] id of the admin set to check
49
+ # @return [Boolean] true if the id is for the default admin set; otherwise, false
51
50
  def default_admin_set?(id:)
52
- id.to_s == DEFAULT_ID
51
+ return false if id.blank?
52
+ id.to_s == default_admin_set_id
53
53
  end
54
54
 
55
55
  # @api public
@@ -88,18 +88,80 @@ module Hyrax
88
88
  # TODO: Parameters admin_set_id and title are defined to support .create_default_admin_set
89
89
  # which is deprecated. When it is removed, the parameters will no longer be required.
90
90
  def create_default_admin_set!(admin_set_id: DEFAULT_ID, title: DEFAULT_TITLE)
91
- admin_set = Hyrax::AdministrativeSet.new(id: admin_set_id, title: Array.wrap(title))
92
- new(admin_set: admin_set, creating_user: nil).create!
91
+ admin_set = create_admin_set(suggested_id: admin_set_id, title: title)
92
+ admin_set = new(admin_set: admin_set, creating_user: nil, default_admin_set: true).create!
93
+ default_admin_set_persister.update(default_admin_set_id: admin_set.id) if save_default?
94
+ admin_set
95
+ end
96
+
97
+ def save_default?
98
+ default_admin_set_persister.save_supported?
99
+ end
100
+
101
+ # Create an instance of `Hyrax::AdministrativeSet` with the suggested_id if supported.
102
+ # @return [Hyrax::AdministrativeSet] the new admin set
103
+ def create_admin_set(suggested_id:, title:)
104
+ if suggested_id.blank? || Hyrax.config.disable_wings || !Hyrax.metadata_adapter.is_a?(Wings::Valkyrie::MetadataAdapter)
105
+ # allow persister to assign id
106
+ Hyrax::AdministrativeSet.new(title: Array.wrap(title))
107
+ else
108
+ # use suggested_id
109
+ Hyrax::AdministrativeSet.new(id: suggested_id, title: Array.wrap(title))
110
+ end
111
+ end
112
+
113
+ # Find default AdministrativeSet using saved id
114
+ # @return [Hyrax::AdministrativeSet] the default admin set; nil if id not saved
115
+ # @raise [RuntimeError] if an admin set with the saved id doesn't exist
116
+ def find_default_admin_set
117
+ id = default_admin_set_id
118
+ return if id.blank?
119
+ Hyrax.query_service.find_by(id: id)
120
+ rescue Valkyrie::Persistence::ObjectNotFoundError
121
+ # The default ID is DEFAULT_ID when saving is not supported. It is ok
122
+ # for this default id to be known but not found. The admin set will be
123
+ # created with DEFAULT_ID by find_or_create_default_admin_set.
124
+ return unless save_default?
125
+
126
+ # id is saved in the default_admin_set_persister's table but doesn't exist
127
+ # NOTE: This is a corrupt state and shouldn't happen. Manual intervention
128
+ # is required to determine the correct value for the default admin
129
+ # set id. The saved id either needs to be updated to the correct
130
+ # value or deleted to allow a new default admin set to be found
131
+ # (i.e. an admin set with id DEFAULT_ID) or generated.
132
+ raise "Corrupt default admin set. Persisted admin set with saved default_admin_set_id doesn't exist."
133
+ end
134
+
135
+ # Find default AdministrativeSet using DEFAULT_ID.
136
+ # @note Use of hardcoded ID is being deprecated as some Valkyrie adapters
137
+ # do not support hardcoded IDs (e.g. postgres)
138
+ # @return [Hyrax::AdministrativeSet] the default admin set; nil if not found
139
+ def find_unsaved_default_admin_set
140
+ admin_set = Hyrax.query_service.find_by(id: DEFAULT_ID)
141
+ default_admin_set_persister.update(default_admin_set_id: DEFAULT_ID) if save_default?
142
+ admin_set
143
+ rescue Valkyrie::Persistence::ObjectNotFoundError
144
+ # a default admin set hasn't been created yet
145
+ end
146
+
147
+ # @return [String | nil] the default admin set id; returns nil if not set
148
+ # @note For general use, it is better to use `Hyrax.config.default_admin_set_id`.
149
+ def default_admin_set_id
150
+ return DEFAULT_ID unless save_default?
151
+ id = default_admin_set_persister.first&.default_admin_set_id
152
+ id = find_unsaved_default_admin_set&.id&.to_s if id.blank?
153
+ id
93
154
  end
94
155
  end
95
156
 
96
157
  # @param admin_set [Hyrax::AdministrativeSet | AdminSet] the admin set to operate on
97
158
  # @param creating_user [User] the user who created the admin set (if any).
98
159
  # @param workflow_importer [#call] imports the workflow
99
- def initialize(admin_set:, creating_user:, workflow_importer: default_workflow_importer)
160
+ def initialize(admin_set:, creating_user:, workflow_importer: default_workflow_importer, default_admin_set: false)
100
161
  @admin_set = admin_set
101
162
  @creating_user = creating_user
102
163
  @workflow_importer = workflow_importer
164
+ @default_admin_set = default_admin_set
103
165
  end
104
166
 
105
167
  attr_reader :creating_user, :admin_set, :workflow_importer
@@ -125,8 +187,8 @@ module Hyrax
125
187
 
126
188
  private
127
189
 
128
- def default_admin_set?(id:)
129
- self.class.default_admin_set?(id: id)
190
+ def default_admin_set?
191
+ @default_admin_set
130
192
  end
131
193
 
132
194
  def admin_group_name
@@ -144,7 +206,7 @@ module Hyrax
144
206
  permission_template = permissions_create_service.create_default(collection: result,
145
207
  creating_user: creating_user)
146
208
  workflow = create_workflows_for(permission_template: permission_template)
147
- create_default_access_for(permission_template: permission_template, workflow: workflow) if default_admin_set?(id: admin_set.id)
209
+ create_default_access_for(permission_template: permission_template, workflow: workflow) if default_admin_set?
148
210
  end
149
211
  end
150
212
  end
@@ -163,7 +225,7 @@ module Hyrax
163
225
  permission_template = permissions_create_service.create_default(collection: admin_set,
164
226
  creating_user: creating_user)
165
227
  workflow = create_workflows_for(permission_template: permission_template)
166
- create_default_access_for(permission_template: permission_template, workflow: workflow) if default_admin_set?(id: admin_set.id)
228
+ create_default_access_for(permission_template: permission_template, workflow: workflow) if default_admin_set?
167
229
  end
168
230
  end
169
231
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module Analytics
4
+ module Google
5
+ module Events
6
+ extend Legato::Model
7
+
8
+ metrics :total_events
9
+ dimensions :event_category, :event_action, :event_label
10
+
11
+ # Filter by event id
12
+ filter :for_id, &->(id) { matches(:eventLabel, id) }
13
+
14
+ # Filter by event action
15
+ filter(:work_view) { |_event_action| matches(:eventAction, 'work-view') }
16
+ filter(:work_in_collection_view) { |_event_action| matches(:eventAction, 'work-in-collection-view') }
17
+ filter(:collection_page_view) { |_event_action| matches(:eventAction, 'collection-page-view') }
18
+ filter(:file_set_download) { |_event_action| matches(:eventAction, 'file-set-download') }
19
+ filter(:work_in_collection_download) { |_event_action| matches(:eventAction, 'work-in-collection-download') }
20
+ filter(:file_set_in_work_download) { |_event_action| matches(:eventAction, 'file-set-in-work-download') }
21
+ filter(:collection_file_download) { |_event_action| matches(:eventAction, 'file-set-in-collection-download') }
22
+
23
+ def self.list(profile, start_date, end_date, action)
24
+ action = action.underscore
25
+ results = []
26
+ Events.results(profile,
27
+ start_date: start_date,
28
+ end_date: end_date,
29
+ sort: ['-totalEvents']).send(action).each do |result|
30
+ results.push([result.eventLabel, result.totalEvents.to_i])
31
+ end
32
+ results
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+ module Hyrax
3
+ module Analytics
4
+ module Google
5
+ module EventsDaily
6
+ extend Legato::Model
7
+
8
+ metrics :total_events
9
+ dimensions :date, :event_category, :event_action
10
+
11
+ # Filter by event id
12
+ filter :for_id, &->(id) { matches(:eventLabel, id) }
13
+
14
+ # Filter by event action
15
+ filter(:work_view) { |_event_action| matches(:eventAction, 'work-view') }
16
+ filter(:work_in_collection_view) { |_event_action| matches(:eventAction, 'work-in-collection-view') }
17
+ filter(:collection_page_view) { |_event_action| matches(:eventAction, 'collection-page-view') }
18
+ filter(:file_set_download) { |_event_action| matches(:eventAction, 'file-set-download') }
19
+ filter(:work_in_collection_download) { |_event_action| matches(:eventAction, 'work-in-collection-download') }
20
+ filter(:file_set_in_work_download) { |_event_action| matches(:eventAction, 'file-set-in-work-download') }
21
+ filter(:collection_file_download) { |_event_action| matches(:eventAction, 'file-set-in-collection-download') }
22
+
23
+ # returns a daily number of events for a specific action
24
+ def self.summary(profile, start_date, end_date, action)
25
+ action = action.underscore
26
+ response = EventsDaily.results(profile,
27
+ start_date: start_date,
28
+ end_date: end_date).send(action)
29
+ dates = (start_date.to_date...end_date.to_date)
30
+ results_array(response, dates)
31
+ end
32
+
33
+ # returns a daily number of events for a specific action
34
+ def self.by_id(profile, start_date, end_date, id, action)
35
+ action = action.underscore
36
+ response = EventsDaily.results(profile,
37
+ start_date: start_date,
38
+ end_date: end_date).for_id(id).send(action)
39
+ dates = (start_date.to_date...end_date.to_date)
40
+ results_array(response, dates)
41
+ end
42
+
43
+ # def self.pageviews(profile, start_date, end_date, ref)
44
+ # ref = ref.underscore
45
+ # response = PageviewsDaily.results(profile,
46
+ # start_date: start_date,
47
+ # end_date: end_date).send(ref)
48
+ # dates = (start_date.to_date...end_date.to_date)
49
+ # results_array(response, dates)
50
+ # end
51
+
52
+ # takes all the dates in between the date range and generate an array [date, totalEvents]
53
+ def self.results_array(response, dates)
54
+ results = []
55
+ response.to_a.each do |result|
56
+ results.push([result.date.to_date, result.totalEvents.to_i])
57
+ end
58
+ new_results = []
59
+ dates.each do |date|
60
+ match = results.detect { |a, _b| a == date }
61
+ if match
62
+ new_results.push(match)
63
+ else
64
+ new_results.push([date, 0])
65
+ end
66
+ end
67
+ Hyrax::Analytics::Results.new(new_results)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end