hyrax 5.0.1 → 5.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +7 -176
- data/.dassie/.env +8 -3
- data/.dassie/Gemfile +13 -2
- data/.dassie/app/controllers/hyrax/generic_work_resources_controller.rb +17 -0
- data/.dassie/app/controllers/hyrax/generic_works_controller.rb +7 -1
- data/.dassie/app/forms/generic_work_resource_form.rb +20 -0
- data/.dassie/app/indexers/generic_work_resource_indexer.rb +16 -0
- data/.dassie/app/models/admin_set_resource.rb +9 -0
- data/.dassie/app/models/collection_resource.rb +2 -0
- data/.dassie/app/models/file_set.rb +2 -0
- data/.dassie/app/models/generic_work_resource.rb +10 -0
- data/.dassie/app/views/hyrax/generic_work_resources/_generic_work_resource.html.erb +2 -0
- data/.dassie/config/analytics.yml +6 -1
- data/.dassie/config/application.rb +24 -0
- data/.dassie/config/initializers/hyrax.rb +13 -3
- data/.dassie/config/initializers/wings.rb +109 -0
- data/.dassie/config/metadata/generic_work_resource.yaml +22 -0
- data/.dassie/config/valkyrie_index.yml +4 -10
- data/.dassie/db/migrate/20240506070809_valkyrie_id_to_string.rb +5 -0
- data/.dassie/db/schema.rb +2 -2
- data/.dassie/spec/indexers/generic_work_resource_indexer_spec.rb +13 -0
- data/.dassie/spec/models/generic_work_resource_spec.rb +12 -0
- data/.dassie/spec/views/generic_work_resources/_generic_work_resource.html.erb_spec.rb +7 -0
- data/.dockerignore +6 -4
- data/.github/release.yml +3 -0
- data/.github/workflows/lint-build-test.yml +130 -0
- data/.github/workflows/test-results.yml +40 -0
- data/.koppie/.env +7 -5
- data/.koppie/Gemfile +12 -1
- data/.koppie/config/analytics.yml +6 -1
- data/.koppie/config/environments/test.rb +2 -0
- data/.koppie/config/initializers/1_valkyrie.rb +6 -2
- data/.koppie/config/solr.yml +1 -1
- data/.regen +1 -1
- data/.rubocop.yml +5 -0
- data/Dockerfile +16 -36
- data/Gemfile +2 -0
- data/app/assets/javascripts/hydra-editor/field_manager.es6 +187 -0
- data/app/assets/javascripts/hyrax/analytics_events.js +48 -24
- data/app/assets/javascripts/hyrax/collapse.js +4 -4
- data/app/assets/javascripts/hyrax/file_manager/save_manager.es6 +2 -0
- data/app/assets/javascripts/hyrax/search.js +2 -3
- data/app/assets/javascripts/hyrax/select_work_type.es6 +3 -1
- data/app/assets/javascripts/hyrax/uploader.js +20 -18
- data/app/assets/javascripts/hyrax.js +1 -0
- data/app/assets/stylesheets/_bootstrap-default-overrides.scss +4 -0
- data/app/assets/stylesheets/hyrax/_card.scss +4 -0
- data/app/assets/stylesheets/hyrax/_catalog.scss +21 -0
- data/app/assets/stylesheets/hyrax/_collections.scss +1 -1
- data/app/assets/stylesheets/hyrax/_facets.scss +15 -3
- data/app/assets/stylesheets/hyrax/_featured.scss +4 -0
- data/app/assets/stylesheets/hyrax/_form.scss +4 -0
- data/app/assets/stylesheets/hyrax/_forms.scss +2 -1
- data/app/assets/stylesheets/hyrax/_nestable.scss +9 -8
- data/app/assets/stylesheets/hyrax/_select_work_type.scss +12 -0
- data/app/assets/stylesheets/hyrax/_styles.scss +4 -0
- data/app/assets/stylesheets/hyrax/_work-show.scss +3 -0
- data/app/controllers/concerns/hyrax/singular_subresource_controller.rb +7 -2
- data/app/controllers/concerns/hyrax/valkyrie_downloads_controller_behavior.rb +11 -2
- data/app/controllers/concerns/hyrax/works_controller_behavior.rb +9 -2
- data/app/controllers/hyrax/admin/analytics/collection_reports_controller.rb +2 -2
- data/app/controllers/hyrax/admin/analytics/work_reports_controller.rb +7 -8
- data/app/controllers/hyrax/dashboard/collections_controller.rb +2 -1
- data/app/controllers/hyrax/downloads_controller.rb +24 -3
- data/app/controllers/hyrax/file_sets_controller.rb +32 -6
- data/app/controllers/hyrax/my/works_controller.rb +20 -0
- data/app/controllers/hyrax/stats_controller.rb +1 -1
- data/app/controllers/hyrax/uploads_controller.rb +28 -2
- data/app/forms/hyrax/forms/admin/appearance.rb +1 -1
- data/app/forms/hyrax/forms/admin/collection_type_form.rb +1 -7
- data/app/forms/hyrax/forms/pcdm_collection_form.rb +9 -0
- data/app/forms/hyrax/forms/work_embargo_form.rb +6 -0
- data/app/forms/hyrax/forms/work_lease_form.rb +6 -0
- data/app/indexers/concerns/hyrax/location_indexer.rb +2 -2
- data/app/indexers/hyrax/indexers/file_set_indexer.rb +4 -0
- data/app/indexers/hyrax/indexers/resource_indexer.rb +1 -0
- data/app/indexers/hyrax/valkyrie_indexer.rb +3 -5
- data/app/jobs/batch_create_job.rb +4 -3
- data/app/jobs/create_work_job.rb +4 -3
- data/app/jobs/migrate_files_to_valkyrie_job.rb +109 -0
- data/app/jobs/migrate_resources_job.rb +34 -0
- data/app/jobs/valkyrie_create_derivatives_job.rb +2 -1
- data/app/models/admin_set.rb +1 -0
- data/app/models/concerns/hyrax/ar_resource.rb +104 -0
- data/app/models/concerns/hyrax/solr_document/ordered_members.rb +2 -1
- data/app/models/concerns/hyrax/solr_document_behavior.rb +13 -2
- data/app/models/concerns/hyrax/valkyrie_lazy_migration.rb +82 -0
- data/app/models/file_download_stat.rb +1 -1
- data/app/models/file_view_stat.rb +1 -1
- data/app/models/hyrax/collection_type.rb +12 -4
- data/app/models/hyrax/file_metadata.rb +19 -0
- data/app/models/hyrax/file_set.rb +25 -0
- data/app/models/hyrax/model_registry.rb +2 -3
- data/app/models/hyrax/resource.rb +5 -0
- data/app/models/hyrax/statistic.rb +12 -37
- data/app/presenters/hyrax/file_set_presenter.rb +2 -1
- data/app/presenters/hyrax/file_usage.rb +3 -3
- data/app/presenters/hyrax/iiif_manifest_presenter.rb +2 -1
- data/app/presenters/hyrax/member_presenter_factory.rb +7 -1
- data/app/presenters/hyrax/menu_presenter.rb +1 -1
- data/app/presenters/hyrax/stats_usage_presenter.rb +2 -1
- data/app/presenters/hyrax/work_show_presenter.rb +13 -17
- data/app/presenters/hyrax/work_usage.rb +5 -2
- data/app/search_builders/hyrax/expired_embargo_search_builder.rb +7 -1
- data/app/search_builders/hyrax/expired_lease_search_builder.rb +7 -1
- data/app/search_builders/hyrax/filter_by_type.rb +1 -3
- data/app/search_builders/hyrax/valkyrie_abstract_type_relation.rb +7 -2
- data/app/services/hyrax/access_control_list.rb +1 -1
- data/app/services/hyrax/admin_set_create_service.rb +16 -5
- data/app/services/hyrax/admin_set_service.rb +2 -1
- data/app/services/hyrax/analytics/ga4/base.rb +96 -0
- data/app/services/hyrax/analytics/ga4/events.rb +25 -0
- data/app/services/hyrax/analytics/ga4/events_daily.rb +36 -0
- data/app/services/hyrax/analytics/ga4/visits.rb +33 -0
- data/app/services/hyrax/analytics/ga4/visits_daily.rb +24 -0
- data/app/services/hyrax/analytics/ga4.rb +204 -0
- data/app/services/hyrax/analytics/google.rb +16 -2
- data/app/services/hyrax/analytics/matomo.rb +16 -3
- data/app/services/hyrax/analytics/results.rb +6 -0
- data/app/services/hyrax/custom_queries/find_access_control.rb +1 -1
- data/app/services/hyrax/custom_queries/find_by_date_range.rb +6 -23
- data/app/services/hyrax/custom_queries/find_collections_by_type.rb +2 -2
- data/app/services/hyrax/custom_queries/find_count_by.rb +3 -31
- data/app/services/hyrax/custom_queries/find_file_metadata.rb +2 -2
- data/app/services/hyrax/custom_queries/find_models_by_access.rb +5 -27
- data/app/services/hyrax/embargo_manager.rb +2 -1
- data/app/services/hyrax/listeners/file_listener.rb +2 -2
- data/app/services/hyrax/lock_manager.rb +6 -6
- data/app/services/hyrax/lockable.rb +4 -3
- data/app/services/hyrax/simple_schema_loader.rb +1 -1
- data/app/services/hyrax/solr_service.rb +22 -8
- data/app/services/hyrax/statistics/query_service.rb +1 -1
- data/app/services/hyrax/statistics/works/over_time.rb +1 -1
- data/app/services/hyrax/thumbnail_path_service.rb +2 -0
- data/app/services/hyrax/user_stat_importer.rb +5 -5
- data/app/services/hyrax/valkyrie_upload.rb +9 -7
- data/app/services/hyrax/versioning_service.rb +10 -2
- data/app/services/hyrax/work_query_service.rb +2 -2
- data/app/services/migrate_resource_service.rb +55 -0
- data/app/views/_controls.html.erb +5 -5
- data/app/views/_masthead.html.erb +1 -1
- data/app/views/catalog/_search_form.html.erb +9 -16
- data/app/views/catalog/_thumbnail_list_collection.html.erb +1 -1
- data/app/views/catalog/_thumbnail_list_default.html.erb +2 -2
- data/app/views/hyrax/admin/analytics/collection_reports/index.html.erb +4 -4
- data/app/views/hyrax/admin/analytics/work_reports/index.html.erb +1 -1
- data/app/views/hyrax/admin/collection_types/_form.html.erb +4 -4
- data/app/views/hyrax/admin/collection_types/index.html.erb +1 -1
- data/app/views/hyrax/admin/features/index.html.erb +1 -1
- data/app/views/hyrax/base/_file_manager_actions.html.erb +1 -1
- data/app/views/hyrax/base/_file_manager_member.html.erb +7 -4
- data/app/views/hyrax/base/_file_manager_thumbnail.html.erb +1 -1
- data/app/views/hyrax/base/_form_files.html.erb +1 -1
- data/app/views/hyrax/base/_form_member_of_collections.html.erb +4 -0
- data/app/views/hyrax/base/_show_actions.html.erb +7 -8
- data/app/views/hyrax/base/_work_button_row.html.erb +1 -1
- data/app/views/hyrax/batch_select/_add_button.html.erb +1 -1
- data/app/views/hyrax/content_blocks/_form.html.erb +3 -3
- data/app/views/hyrax/dashboard/_sidebar.html.erb +1 -1
- data/app/views/hyrax/dashboard/_user_activity.html.erb +2 -2
- data/app/views/hyrax/dashboard/collections/_form.html.erb +4 -4
- data/app/views/hyrax/dashboard/collections/_form_share.html.erb +6 -4
- data/app/views/hyrax/dashboard/collections/_list_collections.html.erb +1 -1
- data/app/views/hyrax/dashboard/collections/_show_document_list_row.html.erb +1 -1
- data/app/views/hyrax/dashboard/show_admin.html.erb +18 -19
- data/app/views/hyrax/dashboard/sidebar/_activity.html.erb +1 -1
- data/app/views/hyrax/embargoes/_list_expired_active_embargoes.html.erb +7 -7
- data/app/views/hyrax/file_sets/_actions.html.erb +9 -1
- data/app/views/hyrax/file_sets/_permission_form.html.erb +4 -2
- data/app/views/hyrax/file_sets/_show_actions.html.erb +1 -1
- data/app/views/hyrax/homepage/_featured.html.erb +1 -1
- data/app/views/hyrax/homepage/_recent_document.html.erb +2 -2
- data/app/views/hyrax/leases/_list_expired_active_leases.html.erb +6 -6
- data/app/views/hyrax/my/collections/_list_collections.html.erb +1 -1
- data/app/views/hyrax/my/collections/_tabs.html.erb +1 -1
- data/app/views/hyrax/pages/_form.html.erb +8 -8
- data/app/views/hyrax/transfers/_received.html.erb +1 -1
- data/app/views/hyrax/uploads/create.json.jbuilder +2 -2
- data/app/views/hyrax/users/_activity_log.html.erb +15 -9
- data/app/views/hyrax/users/_user_row.html.erb +6 -3
- data/app/views/hyrax/users/_vitals.html.erb +3 -2
- data/app/views/layouts/_head_tag_content.html.erb +2 -0
- data/app/views/shared/_appearance_styles.html.erb +5 -1
- data/app/views/shared/_ga4.html.erb +11 -0
- data/app/views/shared/_select_work_type_modal.html.erb +10 -1
- data/bin/db-migrate-seed.sh +3 -3
- data/bin/dev-entrypoint.sh +7 -2
- data/bin/{db-wait.sh → service-wait.sh} +1 -1
- data/bin/worker-entrypoint.sh +8 -0
- data/chart/hyrax/templates/deployment-worker.yaml +2 -2
- data/config/locales/hyrax.en.yml +4 -2
- data/config/metadata/basic_metadata.yaml +20 -0
- data/config/metadata/hyrax_internal_metadata.yaml +1 -1
- data/docker-compose-dassie.yml +167 -0
- data/docker-compose-koppie.yml +21 -36
- data/docker-compose-sirenia.yml +50 -44
- data/docker-compose.yml +2 -183
- data/documentation/developing-your-hyrax-based-app.md +2 -2
- data/hyrax.gemspec +5 -4
- data/lib/freyja/custom_query_container.rb +5 -0
- data/lib/freyja/metadata_adapter.rb +32 -0
- data/lib/freyja/persister.rb +42 -0
- data/lib/freyja/query_service.rb +20 -0
- data/lib/freyja/resource_factory.rb +8 -0
- data/lib/freyja.rb +14 -0
- data/lib/frigg/custom_query_container.rb +5 -0
- data/lib/frigg/metadata_adapter.rb +22 -0
- data/lib/frigg/persister.rb +33 -0
- data/lib/frigg/query_service.rb +15 -0
- data/lib/frigg.rb +13 -0
- data/lib/generators/hyrax/install_generator.rb +5 -0
- data/lib/generators/hyrax/templates/config/analytics.yml +6 -1
- data/lib/generators/hyrax/templates/config/initializers/1_valkyrie.rb +6 -2
- data/lib/generators/hyrax/templates/config/valkyrie_index.yml +1 -1
- data/lib/goddess/custom_query_container.rb +71 -0
- data/lib/goddess/metadata.rb +13 -0
- data/lib/goddess/query.rb +176 -0
- data/lib/hyrax/configuration.rb +83 -0
- data/lib/hyrax/engine.rb +2 -0
- data/lib/hyrax/form_fields.rb +1 -3
- data/lib/hyrax/name.rb +5 -0
- data/lib/hyrax/rubocop/custom_cops.rb +30 -0
- data/lib/hyrax/specs/capybara.rb +10 -6
- data/lib/hyrax/specs/shared_specs/factories/admin_sets.rb +2 -0
- data/lib/hyrax/specs/shared_specs/factories/hyrax_embargo.rb +4 -0
- data/lib/hyrax/specs/shared_specs/factories/hyrax_lease.rb +4 -0
- data/lib/hyrax/specs/shared_specs/factories/hyrax_work.rb +16 -2
- data/lib/hyrax/specs/shared_specs/hydra_works.rb +1 -1
- data/lib/hyrax/transactions/admin_set_destroy.rb +2 -1
- data/lib/hyrax/transactions/collection_destroy.rb +2 -1
- data/lib/hyrax/transactions/container.rb +9 -0
- data/lib/hyrax/transactions/steps/add_file_sets.rb +2 -1
- data/lib/hyrax/transactions/steps/delete_permission_template.rb +30 -0
- data/lib/hyrax/transactions/steps/delete_resource.rb +1 -1
- data/lib/hyrax/transactions/steps/save_collection_logo.rb +2 -1
- data/lib/hyrax/valkyrie_can_can_adapter.rb +8 -1
- data/lib/hyrax/version.rb +1 -1
- data/lib/wings/active_fedora_converter.rb +13 -5
- data/lib/wings/converter_value_mapper.rb +1 -0
- data/lib/wings/services/custom_queries/find_collections_by_type.rb +2 -1
- data/lib/wings/services/custom_queries/find_file_metadata.rb +2 -2
- data/lib/wings/setup.rb +12 -3
- data/lib/wings/transformer_value_mapper.rb +5 -1
- data/lib/wings/valkyrie/persister.rb +3 -1
- data/template.rb +1 -1
- metadata +77 -19
- data/.koppie/scripts/db-migrate-seed.sh +0 -9
- data/.koppie/scripts/entrypoint.sh +0 -10
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Hyrax
|
3
|
+
module Analytics
|
4
|
+
module Ga4
|
5
|
+
class Base
|
6
|
+
attr_reader :start_date, :end_date, :dimensions, :metrics
|
7
|
+
|
8
|
+
def initialize(start_date:,
|
9
|
+
end_date:,
|
10
|
+
dimensions: [],
|
11
|
+
metrics: [])
|
12
|
+
@start_date = start_date.to_date
|
13
|
+
@end_date = end_date.to_date
|
14
|
+
@dimensions = dimensions
|
15
|
+
@metrics = metrics
|
16
|
+
end
|
17
|
+
|
18
|
+
def filters
|
19
|
+
@filters ||= {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def filters=(value)
|
23
|
+
value
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_filter(dimension:, values:)
|
27
|
+
# reset any cached results
|
28
|
+
@results = nil
|
29
|
+
filters[dimension] ||= []
|
30
|
+
filters[dimension] += values
|
31
|
+
end
|
32
|
+
|
33
|
+
def results
|
34
|
+
@results ||= Hyrax::Analytics.client.run_report(report).rows
|
35
|
+
end
|
36
|
+
|
37
|
+
def report
|
38
|
+
::Google::Analytics::Data::V1beta::RunReportRequest.new(
|
39
|
+
property: Hyrax::Analytics.property,
|
40
|
+
metrics: metrics,
|
41
|
+
date_ranges: [{ start_date: start_date.iso8601, end_date: end_date.iso8601 }],
|
42
|
+
dimensions: dimensions,
|
43
|
+
dimension_filter: dimension_filter
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def dimension_filter
|
48
|
+
return nil if filters.blank?
|
49
|
+
{
|
50
|
+
and_group: {
|
51
|
+
expressions: dimension_expressions
|
52
|
+
}
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def dimension_expressions
|
57
|
+
filters.map do |dimension, values|
|
58
|
+
{
|
59
|
+
filter: {
|
60
|
+
field_name: dimension,
|
61
|
+
in_list_filter: { values: values.uniq }
|
62
|
+
}
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def results_array(target_type = nil)
|
68
|
+
r = {}
|
69
|
+
# prefill dates so that all dates at least have 0
|
70
|
+
(start_date..end_date).each do |date|
|
71
|
+
r[date] = 0
|
72
|
+
end
|
73
|
+
results.each do |result|
|
74
|
+
date = unwrap_dimension(metric: result, dimension: 0)
|
75
|
+
type = unwrap_dimension(metric: result, dimension: 1)
|
76
|
+
next if date.nil? || type.nil?
|
77
|
+
next if target_type && type != target_type
|
78
|
+
date = date.to_date
|
79
|
+
r[date] += unwrap_metric(result)
|
80
|
+
end
|
81
|
+
Hyrax::Analytics::Results.new(r.to_a)
|
82
|
+
end
|
83
|
+
|
84
|
+
protected
|
85
|
+
|
86
|
+
def unwrap_dimension(metric:, dimension: 0)
|
87
|
+
metric.dimension_values[dimension]&.value
|
88
|
+
end
|
89
|
+
|
90
|
+
def unwrap_metric(metric)
|
91
|
+
metric.metric_values.first.value.to_i
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Hyrax
|
3
|
+
module Analytics
|
4
|
+
module Ga4
|
5
|
+
class Events < Hyrax::Analytics::Ga4::Base
|
6
|
+
def initialize(start_date:,
|
7
|
+
end_date:,
|
8
|
+
dimensions: [{ name: 'eventName' }, { name: 'contentType' }, { name: 'contentId' }],
|
9
|
+
metrics: [{ name: 'eventCount' }])
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.list(start_date, end_date, action)
|
14
|
+
events = Events.new(start_date: start_date, end_date: end_date)
|
15
|
+
events.add_filter(dimension: 'eventName', values: [action])
|
16
|
+
events.top_result_array
|
17
|
+
end
|
18
|
+
|
19
|
+
def top_result_array
|
20
|
+
results.map { |r| [unwrap_dimension(metric: r, dimension: 2), unwrap_metric(r)] }.sort_by { |r| r[1] }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Hyrax
|
3
|
+
module Analytics
|
4
|
+
module Ga4
|
5
|
+
class EventsDaily < Hyrax::Analytics::Ga4::Base
|
6
|
+
def initialize(start_date:,
|
7
|
+
end_date:,
|
8
|
+
dimensions: [{ name: 'date' }, { name: 'eventName' }, { name: 'contentType' }, { name: 'contentId' }],
|
9
|
+
metrics: [{ name: 'eventCount' }])
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
# returns a daily number of events for a specific action
|
14
|
+
def self.summary(start_date, end_date, action)
|
15
|
+
events_daily = EventsDaily.new(
|
16
|
+
start_date: start_date,
|
17
|
+
end_date: end_date
|
18
|
+
)
|
19
|
+
events_daily.add_filter(dimension: 'eventName', values: [action])
|
20
|
+
events_daily.results_array
|
21
|
+
end
|
22
|
+
|
23
|
+
# returns a daily number of events for a specific action
|
24
|
+
def self.by_id(start_date, end_date, id, action)
|
25
|
+
events_daily = EventsDaily.new(
|
26
|
+
start_date: start_date,
|
27
|
+
end_date: end_date
|
28
|
+
)
|
29
|
+
events_daily.add_filter(dimension: 'contentId', values: [id])
|
30
|
+
events_daily.add_filter(dimension: 'eventName', values: [action])
|
31
|
+
events_daily.results_array
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Hyrax
|
3
|
+
module Analytics
|
4
|
+
module Ga4
|
5
|
+
class Visits < Hyrax::Analytics::Ga4::Base
|
6
|
+
def initialize(start_date:, end_date:, dimensions: [{ name: 'newVsReturning' }], metrics: [{ name: 'sessions' }])
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def new_visits
|
11
|
+
unwrap_metric(results.detect { |r| unwrap_dimension(metric: r) == 'new' })
|
12
|
+
end
|
13
|
+
|
14
|
+
def return_visits
|
15
|
+
unwrap_metric(results.detect { |r| unwrap_dimension(metric: r) == 'returning' })
|
16
|
+
end
|
17
|
+
|
18
|
+
def unknown_visits
|
19
|
+
empty_metrics = results.detect { |r| unwrap_dimension(metric: r) == '' }
|
20
|
+
not_set_metrics = results.detect { |r| unwrap_dimension(metric: r) == '(not set)' }
|
21
|
+
unknown = 0
|
22
|
+
unknown += unwrap_metric(empty_metrics) if empty_metrics.present?
|
23
|
+
unknown += unwrap_metric(not_set_metrics) if not_set_metrics.present?
|
24
|
+
unknown
|
25
|
+
end
|
26
|
+
|
27
|
+
def total_visits
|
28
|
+
new_visits + return_visits + unknown_visits
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Hyrax
|
3
|
+
module Analytics
|
4
|
+
module Ga4
|
5
|
+
class VisitsDaily < Hyrax::Analytics::Ga4::Base
|
6
|
+
def initialize(start_date:, end_date:, dimensions: [{ name: 'date' }, { name: 'newVsReturning' }], metrics: [{ name: 'sessions' }])
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def new_visits
|
11
|
+
results_array('new')
|
12
|
+
end
|
13
|
+
|
14
|
+
def return_visits
|
15
|
+
results_array('returning')
|
16
|
+
end
|
17
|
+
|
18
|
+
def total_visits
|
19
|
+
results_array
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'oauth2'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require "google/analytics/data/v1beta"
|
7
|
+
rescue LoadError
|
8
|
+
$stderr.puts "Unable to load 'google/analytics/data/v1beta'; this is okay unless you are trying to do analytics reporting."
|
9
|
+
end
|
10
|
+
|
11
|
+
module Hyrax
|
12
|
+
module Analytics
|
13
|
+
# rubocop:disable Metrics/ModuleLength
|
14
|
+
module Ga4
|
15
|
+
extend ActiveSupport::Concern
|
16
|
+
# rubocop:disable Metrics/BlockLength
|
17
|
+
class_methods do
|
18
|
+
# Loads configuration options from config/analytics.yml. You only need PRIVATE_KEY_PATH or
|
19
|
+
# PRIVATE_KEY_VALUE. VALUE takes precedence.
|
20
|
+
# Expected structure:
|
21
|
+
# `analytics:`
|
22
|
+
# ` ga4:`
|
23
|
+
# analytics_id: <%= ENV['GOOGLE_ANALYTICS_ID'] %>
|
24
|
+
# property_id: <%= ENV['GOOGLE_ANALYTICS_PROPERTY_ID'] %>
|
25
|
+
# account_json: <%= ENV['GOOGLE_ACCOUNT_JSON'] %>
|
26
|
+
# account_json_path: <%= ENV['GOOGLE_ACCOUNT_JSON_PATH'] %>
|
27
|
+
# @return [Config]
|
28
|
+
def config
|
29
|
+
@config ||= Config.load_from_yaml
|
30
|
+
end
|
31
|
+
|
32
|
+
class Config
|
33
|
+
def self.load_from_yaml
|
34
|
+
filename = Rails.root.join('config', 'analytics.yml')
|
35
|
+
yaml = YAML.safe_load(ERB.new(File.read(filename)).result)
|
36
|
+
unless yaml
|
37
|
+
Hyrax.logger.error("Unable to fetch any keys from #{filename}.")
|
38
|
+
return new({})
|
39
|
+
end
|
40
|
+
config = yaml.fetch('analytics')&.fetch('ga4', nil)
|
41
|
+
unless config
|
42
|
+
Deprecation.warn("Deprecated analytics configuration format found. Please update config/analytics.yml.")
|
43
|
+
config = yaml.fetch('analytics')
|
44
|
+
# this has to exist here with a placeholder so it can be set in the Hyrax initializer
|
45
|
+
# it is only for backward compatibility
|
46
|
+
config['analytics_id'] = '-'
|
47
|
+
end
|
48
|
+
new config
|
49
|
+
end
|
50
|
+
|
51
|
+
KEYS = %w[analytics_id property_id account_json account_json_path].freeze
|
52
|
+
REQUIRED_KEYS = %w[analytics_id property_id].freeze
|
53
|
+
|
54
|
+
def initialize(config)
|
55
|
+
@config = config
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [Boolean] are all the required values present?
|
59
|
+
def valid?
|
60
|
+
return false unless @config['account_json'].present? || @config['account_json_path'].present?
|
61
|
+
|
62
|
+
REQUIRED_KEYS.all? { |required| @config[required].present? }
|
63
|
+
end
|
64
|
+
|
65
|
+
def base64?(value)
|
66
|
+
value.is_a?(String) && Base64.strict_encode64(Base64.decode64(value)) == value
|
67
|
+
end
|
68
|
+
|
69
|
+
def account_json_string
|
70
|
+
return @account_json_string if @account_json_string
|
71
|
+
@account_json_string = if @config['account_json']
|
72
|
+
base64?(@config['account_json']) ? Base64.decode64(@config['account_json']) : @config['account_json']
|
73
|
+
else
|
74
|
+
File.read(@config['account_json_path'])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def account_info
|
79
|
+
@account_info ||= JSON.parse(account_json_string)
|
80
|
+
end
|
81
|
+
|
82
|
+
KEYS.each do |key|
|
83
|
+
# rubocop:disable Style/EvalWithLocation
|
84
|
+
class_eval %{ def #{key}; @config.fetch('#{key}'); end }
|
85
|
+
class_eval %{ def #{key}=(value); @config['#{key}'] = value; end }
|
86
|
+
# rubocop:enable Style/EvalWithLocation
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def client
|
91
|
+
@client ||= ::Google::Analytics::Data::V1beta::AnalyticsData::Client.new do |conf|
|
92
|
+
conf.credentials = config.account_info
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def property
|
97
|
+
"properties/#{config.property_id}"
|
98
|
+
end
|
99
|
+
|
100
|
+
# rubocop:disable Metrics/MethodLength
|
101
|
+
def to_date_range(period)
|
102
|
+
case period
|
103
|
+
when "day"
|
104
|
+
start_date = Time.zone.today
|
105
|
+
end_date = Time.zone.today
|
106
|
+
when "week"
|
107
|
+
start_date = Time.zone.today - 7.days
|
108
|
+
end_date = Time.zone.today
|
109
|
+
when "month"
|
110
|
+
start_date = Time.zone.today - 1.month
|
111
|
+
end_date = Time.zone.today
|
112
|
+
when "year"
|
113
|
+
start_date = Time.zone.today - 1.year
|
114
|
+
end_date = Time.zone.today
|
115
|
+
end
|
116
|
+
|
117
|
+
[start_date, end_date]
|
118
|
+
end
|
119
|
+
# rubocop:enable Metrics/MethodLength
|
120
|
+
|
121
|
+
def keyword_conversion(date)
|
122
|
+
case date
|
123
|
+
when "last12"
|
124
|
+
start_date = Time.zone.today - 11.months
|
125
|
+
end_date = Time.zone.today
|
126
|
+
|
127
|
+
[start_date, end_date]
|
128
|
+
else
|
129
|
+
date.split(",")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def date_period(period, date)
|
134
|
+
if period == "range"
|
135
|
+
date.split(",")
|
136
|
+
else
|
137
|
+
to_date_range(period)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Configure analytics_start_date in ENV file
|
142
|
+
def default_date_range
|
143
|
+
"#{Hyrax.config.analytics_start_date},#{Time.zone.today + 1.day}"
|
144
|
+
end
|
145
|
+
|
146
|
+
# The number of events by day for an action
|
147
|
+
def daily_events(action, date = default_date_range)
|
148
|
+
date = date.split(",")
|
149
|
+
EventsDaily.summary(date[0], date[1], action)
|
150
|
+
end
|
151
|
+
|
152
|
+
# The number of events by day for an action and ID
|
153
|
+
def daily_events_for_id(id, action, date = default_date_range)
|
154
|
+
date = date.split(",")
|
155
|
+
EventsDaily.by_id(date[0], date[1], id, action)
|
156
|
+
end
|
157
|
+
|
158
|
+
# A list of events sorted by highest event count
|
159
|
+
def top_events(action, date = default_date_range)
|
160
|
+
date = date.split(",")
|
161
|
+
Events.list(date[0], date[1], action)
|
162
|
+
end
|
163
|
+
|
164
|
+
def unique_visitors(date = default_date_range); end
|
165
|
+
|
166
|
+
def unique_visitors_for_id(id, date = default_date_range); end
|
167
|
+
|
168
|
+
def new_visitors(period = 'month', date = default_date_range)
|
169
|
+
start_date, end_date = date_period(period, date)
|
170
|
+
Visits.new(start_date: start_date, end_date: end_date).new_visits
|
171
|
+
end
|
172
|
+
|
173
|
+
def new_visits_by_day(date = default_date_range, period = 'range')
|
174
|
+
start_date, end_date = date_period(period, date)
|
175
|
+
VisitsDaily.new(start_date: start_date, end_date: end_date).new_visits
|
176
|
+
end
|
177
|
+
|
178
|
+
def returning_visitors(period = 'month', date = default_date_range)
|
179
|
+
start_date, end_date = date_period(period, date)
|
180
|
+
Visits.new(start_date: start_date, end_date: end_date).return_visits
|
181
|
+
end
|
182
|
+
|
183
|
+
def returning_visits_by_day(date = default_date_range, period = 'range')
|
184
|
+
start_date, end_date = date_period(period, date)
|
185
|
+
VisitsDaily.new(start_date: start_date, end_date: end_date).return_visits
|
186
|
+
end
|
187
|
+
|
188
|
+
def total_visitors(period = 'month', date = default_date_range)
|
189
|
+
start_date, end_date = date_period(period, date)
|
190
|
+
Visits.new(start_date: start_date, end_date: end_date).total_visits
|
191
|
+
end
|
192
|
+
|
193
|
+
def page_statistics(start_date, object)
|
194
|
+
visits = VisitsDaily.new(start_date: start_date, end_date: Date.yesterday)
|
195
|
+
visits.add_filter(dimension: 'contentId', values: [object.id.to_s])
|
196
|
+
visits.total_visits
|
197
|
+
end
|
198
|
+
end
|
199
|
+
# rubocop:enable Metrics/BlockLength
|
200
|
+
end
|
201
|
+
# rubocop:enable Metrics/ModuleLength
|
202
|
+
end
|
203
|
+
end
|
204
|
+
# rubocop:enable Metrics/ModuleLength
|
@@ -55,7 +55,6 @@ module Hyrax
|
|
55
55
|
# @return [Boolean] are all the required values present?
|
56
56
|
def valid?
|
57
57
|
return false unless @config['privkey_value'].present? || @config['privkey_path'].present?
|
58
|
-
|
59
58
|
REQUIRED_KEYS.all? { |required| @config[required].present? }
|
60
59
|
end
|
61
60
|
|
@@ -203,10 +202,25 @@ module Hyrax
|
|
203
202
|
date = date_period(period, date)
|
204
203
|
Visits.total_visits(profile, date[0], date[1])
|
205
204
|
end
|
205
|
+
|
206
|
+
# Hyrax::Download is sent to Hyrax::Analytics.profile as #hyrax__download
|
207
|
+
# see Legato::ProfileMethods.method_name_from_klass
|
208
|
+
def page_statistics(start_date, object)
|
209
|
+
path = Rails.application.routes.url_helpers.polymorphic_path(object)
|
210
|
+
profile = Hyrax::Analytics.profile
|
211
|
+
unless profile
|
212
|
+
Hyrax.logger.error("Google Analytics profile has not been established. Unable to fetch statistics.")
|
213
|
+
return []
|
214
|
+
end
|
215
|
+
profile.hyrax__pageview(sort: 'date',
|
216
|
+
start_date: start_date,
|
217
|
+
end_date: Date.yesterday,
|
218
|
+
limit: 10_000)
|
219
|
+
.for_path(path)
|
220
|
+
end
|
206
221
|
end
|
207
222
|
# rubocop:enable Metrics/BlockLength
|
208
223
|
end
|
209
224
|
# rubocop:enable Metrics/ModuleLength
|
210
225
|
end
|
211
226
|
end
|
212
|
-
# rubocop:enable Metrics/ModuleLength
|
@@ -154,24 +154,37 @@ module Hyrax
|
|
154
154
|
response["nb_visits_returning"].to_i + response["nb_visits_new"].to_i
|
155
155
|
end
|
156
156
|
|
157
|
+
# TODO: implement
|
158
|
+
def page_statistics(_start_date, _object)
|
159
|
+
[]
|
160
|
+
end
|
161
|
+
|
157
162
|
def results_array(response, metric)
|
158
163
|
results = []
|
159
164
|
response.each do |result|
|
160
165
|
if result[1].empty?
|
161
166
|
results.push([result[0].to_date, 0])
|
162
167
|
elsif result[1].is_a?(Array)
|
163
|
-
results.push([result[0].to_date, result[1].first[metric]])
|
168
|
+
results.push([result[0].to_date, result[1].first[metric].to_i])
|
164
169
|
else
|
165
|
-
results.push([result[0].to_date, result[1][metric].presence
|
170
|
+
results.push([result[0].to_date, result[1][metric].presence.to_i])
|
166
171
|
end
|
167
172
|
end
|
168
173
|
Hyrax::Analytics::Results.new(results)
|
169
174
|
end
|
170
175
|
|
176
|
+
# If Matomo detects an error it will return a reponse with the key {"result":"error"}
|
177
|
+
# instead of returning an error status code. This method checks for that key.
|
178
|
+
def contains_matomo_error?(response)
|
179
|
+
response.is_a?(Hash) && response["result"] == "error"
|
180
|
+
end
|
181
|
+
|
171
182
|
def get(params)
|
172
183
|
response = Faraday.get(config.base_url, params)
|
173
184
|
return [] if response.status != 200
|
174
|
-
JSON.parse(response.body)
|
185
|
+
api_response = JSON.parse(response.body)
|
186
|
+
return [] if contains_matomo_error?(api_response)
|
187
|
+
api_response
|
175
188
|
end
|
176
189
|
|
177
190
|
def api_params(method, period, date, additional_params = {})
|
@@ -20,7 +20,7 @@ module Hyrax
|
|
20
20
|
.find_inverse_references_by(resource: resource, property: :access_to)
|
21
21
|
.find { |r| r.is_a?(Hyrax::AccessControl) } ||
|
22
22
|
raise(Valkyrie::Persistence::ObjectNotFoundError)
|
23
|
-
rescue ArgumentError # some adapters raise ArgumentError for missing resources
|
23
|
+
rescue ArgumentError, Ldp::Gone, Ldp::NotFound # some adapters raise ArgumentError for missing resources
|
24
24
|
raise(Valkyrie::Persistence::ObjectNotFoundError)
|
25
25
|
end
|
26
26
|
end
|
@@ -25,30 +25,13 @@ module Hyrax
|
|
25
25
|
# @param models [Array]
|
26
26
|
# @param start_datetime [DateTime]
|
27
27
|
# @param end_datetime [DateTime]
|
28
|
+
# @return [Array<Hyrax::Resource>]
|
28
29
|
def find_by_date_range(start_datetime:, end_datetime: nil, models: nil)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def find_models_by_date_range_query
|
38
|
-
<<-SQL
|
39
|
-
SELECT * FROM orm_resources
|
40
|
-
WHERE created_at >= ?
|
41
|
-
AND created_at <= ?
|
42
|
-
AND internal_resource IN (?);
|
43
|
-
SQL
|
44
|
-
end
|
45
|
-
|
46
|
-
def find_by_date_range_query
|
47
|
-
<<-SQL
|
48
|
-
SELECT * FROM orm_resources
|
49
|
-
WHERE created_at >= ?
|
50
|
-
AND created_at <= ?;
|
51
|
-
SQL
|
30
|
+
end_range = end_datetime.blank? ? '*' : end_datetime.utc.xmlschema
|
31
|
+
query = "system_create_dtsi:[#{start_datetime.utc.xmlschema} TO #{end_range}]"
|
32
|
+
query += " AND has_model_ssim: (#{models.map { |m| "\"#{m}\"" }.join(' OR ')})" unless models.empty?
|
33
|
+
ids = Hyrax::SolrService.query_result(query, fl: 'id')['response']['docs'].map { |doc| doc['id'] }
|
34
|
+
Hyrax.query_service.find_many_by_ids(ids: ids)
|
52
35
|
end
|
53
36
|
end
|
54
37
|
end
|
@@ -28,9 +28,9 @@ module Hyrax
|
|
28
28
|
# @param global_id [GlobalID] global id for a Hyrax::CollectionType
|
29
29
|
#
|
30
30
|
# @return [Enumerable<PcdmCollection>]
|
31
|
-
def find_collections_by_type(global_id:)
|
31
|
+
def find_collections_by_type(global_id:, model: Hyrax.config.collection_class)
|
32
32
|
query_service
|
33
|
-
.find_all_of_model(model:
|
33
|
+
.find_all_of_model(model:)
|
34
34
|
.select { |collection| collection.collection_type_gid == global_id }
|
35
35
|
end
|
36
36
|
end
|
@@ -25,37 +25,9 @@ module Hyrax
|
|
25
25
|
# @param hash [Hash] the hash representation of the query
|
26
26
|
def find_count_by(hash = {}, models: nil)
|
27
27
|
return nil if models.empty? && hash.blank?
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
query_service.orm_class.count_by_sql(([find_count_by_properties_query] + internal_array))
|
32
|
-
elsif hash.blank?
|
33
|
-
query_service.orm_class.count_by_sql([find_count_by_models_query] + [models])
|
34
|
-
else
|
35
|
-
query_service.orm_class.count_by_sql(([find_count_by_properties_and_models_query] + internal_array + [models]))
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def find_count_by_properties_and_models_query
|
40
|
-
<<-SQL
|
41
|
-
SELECT count(*) FROM orm_resources
|
42
|
-
WHERE metadata @> ?
|
43
|
-
AND internal_resource IN (?);
|
44
|
-
SQL
|
45
|
-
end
|
46
|
-
|
47
|
-
def find_count_by_models_query
|
48
|
-
<<-SQL
|
49
|
-
SELECT count(*) FROM orm_resources
|
50
|
-
WHERE internal_resource IN (?);
|
51
|
-
SQL
|
52
|
-
end
|
53
|
-
|
54
|
-
def find_count_by_properties_query
|
55
|
-
<<-SQL
|
56
|
-
SELECT count(*) FROM orm_resources
|
57
|
-
WHERE metadata @> ?;
|
58
|
-
SQL
|
28
|
+
flat_hash = hash.map { |k, v| "#{k}: \"#{v}\"" }.join(' ')
|
29
|
+
flat_hash += " has_model_ssim: (#{models.map { |m| "\"#{m}\"" }.join(' OR ')})" unless models.empty?
|
30
|
+
Hyrax::SolrService.count(flat_hash)
|
59
31
|
end
|
60
32
|
end
|
61
33
|
end
|
@@ -31,7 +31,7 @@ module Hyrax
|
|
31
31
|
result = query_service.find_by(id: id)
|
32
32
|
unless result.is_a? Hyrax::FileMetadata
|
33
33
|
raise ::Valkyrie::Persistence::ObjectNotFoundError,
|
34
|
-
"Result type #{result
|
34
|
+
"Result type #{result&.internal_resource} for id #{id} is not a `Hyrax::FileMetadata`"
|
35
35
|
end
|
36
36
|
result
|
37
37
|
end
|
@@ -44,7 +44,7 @@ module Hyrax
|
|
44
44
|
result = query_service.find_by_alternate_identifier(alternate_identifier: alternate_identifier)
|
45
45
|
unless result.is_a? Hyrax::FileMetadata
|
46
46
|
raise ::Valkyrie::Persistence::ObjectNotFoundError,
|
47
|
-
"Result type #{result
|
47
|
+
"Result type #{result&.internal_resource} for alternate_identifier #{alternate_identifier} is not a `Hyrax::FileMetadata`"
|
48
48
|
end
|
49
49
|
result
|
50
50
|
end
|
@@ -24,35 +24,13 @@ module Hyrax
|
|
24
24
|
#
|
25
25
|
# @param model [Class]
|
26
26
|
# @param ids [Enumerable<#to_s>, Symbol]
|
27
|
+
# @return [Array<Hyrax::Resource>]
|
27
28
|
#
|
28
29
|
def find_models_by_access(mode:, models: nil, agent:, group: nil)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
else
|
34
|
-
query_service.run_query(find_by_access_query, internal_array)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def find_models_by_access_query
|
39
|
-
<<-SQL
|
40
|
-
SELECT * FROM orm_resources
|
41
|
-
WHERE id IN (
|
42
|
-
SELECT uuid(metadata::json#>'{access_to,0}'->>'id') FROM orm_resources
|
43
|
-
WHERE metadata @> ?
|
44
|
-
) AND internal_resource IN (?);
|
45
|
-
SQL
|
46
|
-
end
|
47
|
-
|
48
|
-
def find_by_access_query
|
49
|
-
<<-SQL
|
50
|
-
SELECT * FROM orm_resources
|
51
|
-
WHERE id IN (
|
52
|
-
SELECT uuid(metadata::json#>'{access_to,0}'->>'id') FROM orm_resources
|
53
|
-
WHERE metadata @> ?
|
54
|
-
);
|
55
|
-
SQL
|
30
|
+
query = "#{Hydra.config.permissions[mode.to_sym][(group ? 'group' : 'individual').to_sym]}:#{agent}"
|
31
|
+
query += " AND has_model_ssim: (#{models.map { |m| "\"#{m}\"" }.join(' OR ')})" unless models.empty?
|
32
|
+
ids = Hyrax::SolrService.query_result(query, fl: 'id')['response']['docs'].map { |doc| doc['id'] }
|
33
|
+
Hyrax.query_service.find_many_by_ids(ids: ids)
|
56
34
|
end
|
57
35
|
end
|
58
36
|
end
|