hyrax 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +3 -6
- data/.dassie/.env +1 -2
- data/.dassie/Gemfile +7 -3
- data/.dassie/app/models/user.rb +0 -2
- data/.dassie/config/analytics.yml +12 -5
- data/.dassie/config/environments/development.rb +2 -0
- data/.dassie/config/initializers/hyrax.rb +2 -0
- data/.dassie/db/migrate/20210921150120_enable_uuid_extension.valkyrie_engine.rb +7 -0
- data/.dassie/db/migrate/20210921150121_create_orm_resources.valkyrie_engine.rb +19 -0
- data/.dassie/db/migrate/20210921150122_add_model_type_to_orm_resources.valkyrie_engine.rb +7 -0
- data/.dassie/db/migrate/20210921150123_change_model_type_to_internal_model.valkyrie_engine.rb +7 -0
- data/.dassie/db/migrate/20210921150124_create_path_gin_index.valkyrie_engine.rb +7 -0
- data/.dassie/db/migrate/20210921150125_create_internal_resource_index.valkyrie_engine.rb +7 -0
- data/.dassie/db/migrate/20210921150126_create_updated_at_index.valkyrie_engine.rb +7 -0
- data/.dassie/db/migrate/20210921150127_add_optimistic_locking_to_orm_resources.valkyrie_engine.rb +7 -0
- data/.dassie/db/migrate/20211130181150_create_default_administrative_set.rb +8 -0
- data/.dassie/db/schema.rb +20 -1
- data/.env +7 -4
- data/.github/workflows/main.yml +17 -0
- data/.github/workflows/release.yml +17 -0
- data/.gitignore +1 -0
- data/.regen +1 -1
- data/CONTAINERS.md +13 -10
- data/README.md +37 -0
- data/app/assets/javascripts/hyrax/admin/graphs.es6 +34 -37
- data/app/assets/javascripts/hyrax/analytics_events.js +69 -0
- data/app/assets/javascripts/hyrax/collapse.js +24 -0
- data/app/assets/javascripts/hyrax/collections.js +1 -2
- data/app/assets/javascripts/hyrax/ga_events.js +2 -8
- data/app/assets/javascripts/hyrax/reports-buttons.js +33 -0
- data/app/assets/javascripts/hyrax.js +2 -1
- data/app/assets/stylesheets/_bootstrap-default-overrides.scss +9 -0
- data/app/authorities/qa/authorities/collections.rb +4 -5
- data/app/authorities/qa/authorities/find_works.rb +1 -1
- data/app/controllers/concerns/hyrax/breadcrumbs_for_collection_analytics.rb +26 -0
- data/app/controllers/concerns/hyrax/breadcrumbs_for_works_analytics.rb +26 -0
- data/app/controllers/concerns/hyrax/controller.rb +22 -0
- data/app/controllers/hyrax/admin/analytics/analytics_controller.rb +40 -0
- data/app/controllers/hyrax/admin/analytics/collection_reports_controller.rb +61 -0
- data/app/controllers/hyrax/admin/analytics/work_reports_controller.rb +122 -0
- data/app/controllers/hyrax/collections_controller.rb +4 -1
- data/app/controllers/hyrax/dashboard/collections_controller.rb +15 -6
- data/app/controllers/hyrax/dashboard_controller.rb +8 -0
- data/app/controllers/hyrax/stats_controller.rb +3 -1
- data/app/forms/hyrax/forms/pcdm_collection_form.rb +3 -0
- data/app/indexers/hyrax/valkyrie_file_set_indexer.rb +1 -1
- data/app/jobs/characterize_job.rb +28 -1
- data/app/jobs/valkyrie_ingest_job.rb +56 -0
- data/app/models/concerns/hyrax/ability.rb +26 -5
- data/app/models/concerns/hyrax/solr_document/metadata.rb +1 -0
- data/app/models/file_download_stat.rb +4 -4
- data/app/models/hyrax/default_administrative_set.rb +42 -0
- data/app/models/hyrax/statistic.rb +31 -4
- data/app/presenters/hyrax/admin/dashboard_presenter.rb +8 -6
- data/app/presenters/hyrax/admin/repository_growth_presenter.rb +10 -5
- data/app/presenters/hyrax/admin/user_activity_presenter.rb +8 -12
- data/app/presenters/hyrax/file_set_presenter.rb +2 -0
- data/app/presenters/hyrax/menu_presenter.rb +4 -0
- data/app/presenters/hyrax/pcdm_member_presenter_factory.rb +1 -1
- data/app/presenters/hyrax/work_show_presenter.rb +5 -2
- data/app/presenters/hyrax/work_usage.rb +1 -0
- data/app/search_builders/hyrax/README.md +1 -1
- data/app/search_builders/hyrax/dashboard/collections_search_builder.rb +1 -1
- data/app/search_builders/hyrax/my/collections_search_builder.rb +1 -1
- data/app/services/hyrax/admin_set_create_service.rb +76 -14
- data/app/services/hyrax/analytics/google/events.rb +37 -0
- data/app/services/hyrax/analytics/google/events_daily.rb +72 -0
- data/app/services/hyrax/analytics/google/visits.rb +44 -0
- data/app/services/hyrax/analytics/google/visits_daily.rb +49 -0
- data/app/services/hyrax/analytics/google.rb +204 -0
- data/app/services/hyrax/analytics/matomo.rb +193 -0
- data/app/services/hyrax/analytics/results.rb +79 -0
- data/app/services/hyrax/analytics.rb +12 -82
- data/app/services/hyrax/characterization/valkyrie_characterization_service.rb +134 -0
- data/app/services/hyrax/collections/nested_collection_query_service.rb +8 -3
- data/app/services/hyrax/listeners/acl_index_listener.rb +3 -1
- data/app/services/hyrax/listeners/active_fedora_acl_index_listener.rb +3 -1
- data/app/services/hyrax/listeners/batch_notification_listener.rb +3 -1
- data/app/services/hyrax/listeners/file_metadata_listener.rb +19 -0
- data/app/services/hyrax/listeners/file_set_lifecycle_listener.rb +6 -2
- data/app/services/hyrax/listeners/file_set_lifecycle_notification_listener.rb +6 -2
- data/app/services/hyrax/listeners/member_cleanup_listener.rb +3 -0
- data/app/services/hyrax/listeners/metadata_index_listener.rb +9 -3
- data/app/services/hyrax/listeners/object_lifecycle_listener.rb +9 -3
- data/app/services/hyrax/listeners/proxy_deposit_listener.rb +3 -1
- data/app/services/hyrax/listeners/trophy_cleanup_listener.rb +3 -0
- data/app/services/hyrax/listeners/workflow_listener.rb +3 -1
- data/app/services/hyrax/listeners.rb +8 -0
- data/app/services/hyrax/restriction_service.rb +4 -0
- data/app/services/hyrax/statistics/users/over_time.rb +8 -5
- data/app/services/hyrax/statistics/works/over_time.rb +10 -0
- data/app/services/hyrax/work_uploads_handler.rb +4 -1
- data/app/views/hyrax/admin/analytics/_date_range_form.html.erb +11 -0
- data/app/views/hyrax/admin/analytics/collection_reports/_custom_range.html.erb +39 -0
- data/app/views/hyrax/admin/analytics/collection_reports/_monthly_summary.html.erb +48 -0
- data/app/views/hyrax/admin/analytics/collection_reports/_summary.html.erb +55 -0
- data/app/views/hyrax/admin/analytics/collection_reports/_top_collections.html.erb +55 -0
- data/app/views/hyrax/admin/analytics/collection_reports/index.html.erb +70 -0
- data/app/views/hyrax/admin/analytics/collection_reports/show.html.erb +94 -0
- data/app/views/hyrax/admin/analytics/work_reports/_custom_range.html.erb +43 -0
- data/app/views/hyrax/admin/analytics/work_reports/_monthly_summary.html.erb +35 -0
- data/app/views/hyrax/admin/analytics/work_reports/_summary.html.erb +60 -0
- data/app/views/hyrax/admin/analytics/work_reports/_top_file_set_downloads.html.erb +33 -0
- data/app/views/hyrax/admin/analytics/work_reports/_top_works.html.erb +40 -0
- data/app/views/hyrax/admin/analytics/work_reports/_work_counts.html.erb +18 -0
- data/app/views/hyrax/admin/analytics/work_reports/_work_files.html.erb +41 -0
- data/app/views/hyrax/admin/analytics/work_reports/index.html.erb +77 -0
- data/app/views/hyrax/admin/analytics/work_reports/show.html.erb +90 -0
- data/app/views/hyrax/admin/stats/show.html.erb +1 -1
- data/app/views/hyrax/base/_relationships_parent_row.html.erb +0 -1
- data/app/views/hyrax/base/show.html.erb +6 -0
- data/app/views/hyrax/collections/show.html.erb +4 -0
- data/app/views/hyrax/dashboard/_repository_growth.html.erb +5 -5
- data/app/views/hyrax/dashboard/_resource_type_graph.html.erb +41 -0
- data/app/views/hyrax/dashboard/_sidebar.html.erb +4 -1
- data/app/views/hyrax/dashboard/_tabs.html.erb +11 -0
- data/app/views/hyrax/dashboard/_user_activity.html.erb +17 -23
- data/app/views/hyrax/dashboard/_user_activity_graph.html.erb +55 -0
- data/app/views/hyrax/dashboard/_visibility_graph.html.erb +31 -0
- data/app/views/hyrax/dashboard/_work_type_graph.html.erb +41 -0
- data/app/views/hyrax/dashboard/collections/_form.html.erb +2 -1
- data/app/views/hyrax/dashboard/show_admin.html.erb +24 -45
- data/app/views/hyrax/dashboard/sidebar/_activity.html.erb +22 -0
- data/app/views/hyrax/file_sets/_actions.html.erb +4 -3
- data/app/views/hyrax/file_sets/show.html.erb +6 -0
- data/app/views/hyrax/my/collections/index.html.erb +1 -1
- data/app/views/hyrax/stats/_downloads.html.erb +18 -0
- data/app/views/hyrax/stats/_pageviews.html.erb +18 -0
- data/app/views/hyrax/stats/work.html.erb +17 -9
- data/app/views/layouts/_head_tag_content.html.erb +7 -2
- data/app/views/{_ga.html.erb → shared/_ga.html.erb} +3 -7
- data/app/views/shared/_matomo.html.erb +15 -0
- data/chart/hyrax/Chart.yaml +1 -1
- data/chart/hyrax/values.yaml +1 -1
- data/config/i18n-tasks.yml +2 -2
- data/config/initializers/listeners.rb +5 -5
- data/config/locales/hyrax.de.yml +194 -0
- data/config/locales/hyrax.en.yml +190 -12
- data/config/locales/hyrax.es.yml +194 -0
- data/config/locales/hyrax.fr.yml +194 -0
- data/config/locales/hyrax.it.yml +194 -0
- data/config/locales/hyrax.pt-BR.yml +194 -0
- data/config/locales/hyrax.zh.yml +194 -0
- data/config/routes.rb +4 -0
- data/docker-compose.yml +3 -1
- data/documentation/developing-your-hyrax-based-app.md +2 -2
- data/documentation/legacyREADME.md +1 -1
- data/hyrax.gemspec +3 -1
- data/lib/generators/hyrax/templates/config/analytics.yml +13 -7
- data/lib/generators/hyrax/templates/config/initializers/hyrax.rb +0 -13
- data/lib/generators/hyrax/templates/db/migrate/20211130181150_create_default_administrative_set.rb.erb +8 -0
- data/lib/generators/hyrax/work/templates/feature_spec.rb.erb +3 -1
- data/lib/hyrax/configuration.rb +67 -5
- data/lib/hyrax/engine.rb +7 -6
- data/lib/hyrax/publisher.rb +4 -0
- data/lib/hyrax/transactions/admin_set_create.rb +22 -0
- data/lib/hyrax/transactions/container.rb +11 -0
- data/lib/hyrax/version.rb +1 -1
- data/lib/tasks/regenerate_derivatives.rake +1 -1
- data/lib/wings/setup.rb +15 -0
- data/lib/wings/valkyrie/persister.rb +16 -0
- data/template.rb +1 -1
- data/vendor/assets/javascripts/morris/morris.min.js +1 -7
- data/vendor/assets/stylesheets/morris.js/0.5.1/morris.css +1 -1
- metadata +87 -11
- data/app/views/hyrax/dashboard/_repository_objects.html.erb +0 -28
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Hyrax
|
|
3
|
+
module Analytics
|
|
4
|
+
module Google
|
|
5
|
+
module Visits
|
|
6
|
+
extend Legato::Model
|
|
7
|
+
|
|
8
|
+
dimensions :user_type
|
|
9
|
+
metrics :sessions
|
|
10
|
+
|
|
11
|
+
def self.results_array(response)
|
|
12
|
+
results = []
|
|
13
|
+
response.to_a.each do |result|
|
|
14
|
+
results.push([result.date.to_date, result.result.sessions.to_i])
|
|
15
|
+
end
|
|
16
|
+
Hyrax::Analytics::Results.new(results)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.new_visits(profile, start_date, end_date)
|
|
20
|
+
x = Visits.results(profile,
|
|
21
|
+
start_date: start_date,
|
|
22
|
+
end_date: end_date).to_a
|
|
23
|
+
x.first.sessions.to_i
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.return_visits(profile, start_date, end_date)
|
|
27
|
+
x = Visits.results(profile,
|
|
28
|
+
start_date: start_date,
|
|
29
|
+
end_date: end_date).to_a
|
|
30
|
+
x.last.sessions.to_i
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.total_visits(profile, start_date, end_date)
|
|
34
|
+
x = Visits.results(profile,
|
|
35
|
+
start_date: start_date,
|
|
36
|
+
end_date: end_date).to_a
|
|
37
|
+
new_visits = x.first.sessions.to_i
|
|
38
|
+
returning_visits = x.last.sessions.to_i
|
|
39
|
+
new_visits + returning_visits
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Hyrax
|
|
3
|
+
module Analytics
|
|
4
|
+
module Google
|
|
5
|
+
module VisitsDaily
|
|
6
|
+
extend Legato::Model
|
|
7
|
+
|
|
8
|
+
dimensions :date, :user_type
|
|
9
|
+
metrics :sessions
|
|
10
|
+
|
|
11
|
+
filter(:returning) { |_user_type| matches(:userType, 'Returning Visitor') }
|
|
12
|
+
filter(:new_visit) { |_user_type| matches(:userType, 'New Visitor') }
|
|
13
|
+
|
|
14
|
+
def self.new_visits(profile, start_date, end_date)
|
|
15
|
+
response = VisitsDaily.results(profile,
|
|
16
|
+
start_date: start_date,
|
|
17
|
+
end_date: end_date).new_visit.to_a
|
|
18
|
+
dates = (start_date.to_date...end_date.to_date)
|
|
19
|
+
results_array(response, dates)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.return_visits(profile, start_date, end_date)
|
|
23
|
+
response = VisitsDaily.results(profile,
|
|
24
|
+
start_date: start_date,
|
|
25
|
+
end_date: end_date).returning.to_a
|
|
26
|
+
dates = (start_date.to_date...end_date.to_date)
|
|
27
|
+
results_array(response, dates)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.results_array(response, dates)
|
|
31
|
+
results = []
|
|
32
|
+
response.to_a.each do |result|
|
|
33
|
+
results.push([result.date.to_date, result.sessions.to_i])
|
|
34
|
+
end
|
|
35
|
+
new_results = []
|
|
36
|
+
dates.each do |date|
|
|
37
|
+
match = results.detect { |a, _b| a == date }
|
|
38
|
+
if match
|
|
39
|
+
new_results.push(match)
|
|
40
|
+
else
|
|
41
|
+
new_results.push([date, 0])
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
Hyrax::Analytics::Results.new(new_results)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'oauth2'
|
|
3
|
+
require 'signet/oauth_2/client'
|
|
4
|
+
|
|
5
|
+
# rubocop:disable Metrics/ModuleLength
|
|
6
|
+
module Hyrax
|
|
7
|
+
module Analytics
|
|
8
|
+
module Google
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
# rubocop:disable Metrics/BlockLength
|
|
11
|
+
class_methods do
|
|
12
|
+
# Loads configuration options from config/analytics.yml. Expected structure:
|
|
13
|
+
# `analytics:`
|
|
14
|
+
# ` google:`
|
|
15
|
+
# ` app_name: <%= ENV['GOOGLE_OAUTH_APP_NAME']`
|
|
16
|
+
# ` app_version: <%= ENV['GOOGLE_OAUTH_APP_VERSION']`
|
|
17
|
+
# ` privkey_path: <%= ENV['GOOGLE_OAUTH_PRIVATE_KEY_PATH']`
|
|
18
|
+
# ` privkey_secret: <%= ENV['GOOGLE_OAUTH_PRIVATE_KEY_SECRET']`
|
|
19
|
+
# ` client_email: <%= ENV['GOOGLE_OAUTH_CLIENT_EMAIL']`
|
|
20
|
+
# @return [Config]
|
|
21
|
+
def config
|
|
22
|
+
@config ||= Config.load_from_yaml
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class Config
|
|
26
|
+
def self.load_from_yaml
|
|
27
|
+
filename = Rails.root.join('config', 'analytics.yml')
|
|
28
|
+
yaml = YAML.safe_load(ERB.new(File.read(filename)).result)
|
|
29
|
+
unless yaml
|
|
30
|
+
Rails.logger.error("Unable to fetch any keys from #{filename}.")
|
|
31
|
+
return new({})
|
|
32
|
+
end
|
|
33
|
+
config = yaml.fetch('analytics')&.fetch('google', nil)
|
|
34
|
+
unless config
|
|
35
|
+
Deprecation.warn("Deprecated analytics configuration format found. Please update config/analytics.yml.")
|
|
36
|
+
config = yaml.fetch('analytics')
|
|
37
|
+
# this has to exist here with a placeholder so it can be set in the Hyrax initializer
|
|
38
|
+
# it is only for backward compatibility
|
|
39
|
+
config['analytics_id'] = '-'
|
|
40
|
+
end
|
|
41
|
+
new config
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
REQUIRED_KEYS = %w[analytics_id app_name app_version privkey_path privkey_secret client_email].freeze
|
|
45
|
+
|
|
46
|
+
def initialize(config)
|
|
47
|
+
@config = config
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [Boolean] are all the required values present?
|
|
51
|
+
def valid?
|
|
52
|
+
config_keys = @config.keys
|
|
53
|
+
REQUIRED_KEYS.all? { |required| config_keys.include?(required) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
REQUIRED_KEYS.each do |key|
|
|
57
|
+
class_eval %{ def #{key}; @config.fetch('#{key}'); end }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# This method allows setting the analytics id in the initializer
|
|
61
|
+
# @deprecated set the analytics id in either ENV['GOOGLE_ANALYTICS_ID'] or config/analytics.yaml
|
|
62
|
+
def analytics_id=(value)
|
|
63
|
+
@config['analytics_id'] = value
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Generate an OAuth2 token for Google Analytics
|
|
68
|
+
# @return [OAuth2::AccessToken] An OAuth2 access token for GA
|
|
69
|
+
def token(scope = 'https://www.googleapis.com/auth/analytics.readonly')
|
|
70
|
+
access_token = auth_client(scope).fetch_access_token!
|
|
71
|
+
OAuth2::AccessToken.new(oauth_client, access_token['access_token'], expires_in: access_token['expires_in'])
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def oauth_client
|
|
75
|
+
OAuth2::Client.new('', '', authorize_url: 'https://accounts.google.com/o/oauth2/auth',
|
|
76
|
+
token_url: 'https://accounts.google.com/o/oauth2/token')
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def auth_client(scope)
|
|
80
|
+
raise "Private key file for Google analytics was expected at '#{config.privkey_path}', but no file was found." unless File.exist?(config.privkey_path)
|
|
81
|
+
private_key = File.read(config.privkey_path)
|
|
82
|
+
Signet::OAuth2::Client.new token_credential_uri: 'https://accounts.google.com/o/oauth2/token',
|
|
83
|
+
audience: 'https://accounts.google.com/o/oauth2/token',
|
|
84
|
+
scope: scope,
|
|
85
|
+
issuer: config.client_email,
|
|
86
|
+
signing_key: OpenSSL::PKCS12.new(private_key, config.privkey_secret).key,
|
|
87
|
+
sub: config.client_email
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Return a user object linked to a Google Analytics account
|
|
91
|
+
# @return [Legato::User] A user account with GA access
|
|
92
|
+
def user
|
|
93
|
+
Legato::User.new(token)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Return a Google Analytics profile matching specified ID
|
|
97
|
+
# @ return [Legato::Management::Profile] A user profile associated with GA
|
|
98
|
+
def profile
|
|
99
|
+
return unless config.valid?
|
|
100
|
+
@profile = user.profiles.detect do |profile|
|
|
101
|
+
profile.web_property_id == config.analytics_id
|
|
102
|
+
end
|
|
103
|
+
raise 'User does not have access to this property' unless @profile
|
|
104
|
+
@profile
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# rubocop:disable Metrics/MethodLength
|
|
108
|
+
def to_date_range(period)
|
|
109
|
+
case period
|
|
110
|
+
when "day"
|
|
111
|
+
start_date = Time.zone.today
|
|
112
|
+
end_date = Time.zone.today
|
|
113
|
+
when "week"
|
|
114
|
+
start_date = Time.zone.today - 7.days
|
|
115
|
+
end_date = Time.zone.today
|
|
116
|
+
when "month"
|
|
117
|
+
start_date = Time.zone.today - 1.month
|
|
118
|
+
end_date = Time.zone.today
|
|
119
|
+
when "year"
|
|
120
|
+
start_date = Time.zone.today - 1.year
|
|
121
|
+
end_date = Time.zone.today
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
[start_date, end_date]
|
|
125
|
+
end
|
|
126
|
+
# rubocop:enabl e Metrics/MethodLength
|
|
127
|
+
|
|
128
|
+
def keyword_conversion(date)
|
|
129
|
+
case date
|
|
130
|
+
when "last12"
|
|
131
|
+
start_date = Time.zone.today - 11.months
|
|
132
|
+
end_date = Time.zone.today
|
|
133
|
+
|
|
134
|
+
[start_date, end_date]
|
|
135
|
+
else
|
|
136
|
+
date.split(",")
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def date_period(period, date)
|
|
141
|
+
if period == "range"
|
|
142
|
+
date.split(",")
|
|
143
|
+
else
|
|
144
|
+
to_date_range(period)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Configure analytics_start_date in ENV file
|
|
149
|
+
def default_date_range
|
|
150
|
+
"#{Hyrax.config.analytics_start_date},#{Time.zone.today + 1.day}"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# The number of events by day for an action
|
|
154
|
+
def daily_events(action, date = default_date_range)
|
|
155
|
+
date = date.split(",")
|
|
156
|
+
EventsDaily.summary(profile, date[0], date[1], action)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# The number of events by day for an action and ID
|
|
160
|
+
def daily_events_for_id(id, action, date = default_date_range)
|
|
161
|
+
date = date.split(",")
|
|
162
|
+
EventsDaily.by_id(profile, date[0], date[1], id, action)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# A list of events sorted by highest event count
|
|
166
|
+
def top_events(action, date = default_date_range)
|
|
167
|
+
date = date.split(",")
|
|
168
|
+
Events.send('list', profile, date[0], date[1], action)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def unique_visitors(date = default_date_range); end
|
|
172
|
+
|
|
173
|
+
def unique_visitors_for_id(id, date = default_date_range); end
|
|
174
|
+
|
|
175
|
+
def new_visitors(period = 'month', date = default_date_range)
|
|
176
|
+
date = date_period(period, date)
|
|
177
|
+
Visits.new_visits(profile, date[0], date[1])
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def new_visits_by_day(date = default_date_range, _period = 'day')
|
|
181
|
+
date = date.split(",")
|
|
182
|
+
VisitsDaily.new_visits(profile, date[0], date[1])
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def returning_visitors(period = 'month', date = default_date_range)
|
|
186
|
+
date = date_period(period, date)
|
|
187
|
+
Visits.return_visits(profile, date[0], date[1])
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def returning_visits_by_day(date = default_date_range, _period = 'day')
|
|
191
|
+
date = date.split(",")
|
|
192
|
+
VisitsDaily.return_visits(profile, date[0], date[1])
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def total_visitors(period = 'month', date = default_date_range)
|
|
196
|
+
date = date_period(period, date)
|
|
197
|
+
Visits.total_visits(profile, date[0], date[1])
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
# rubocop:enable Metrics/BlockLength
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
# rubocop:enable Metrics/ModuleLength
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Metrics/ModuleLength
|
|
4
|
+
module Hyrax
|
|
5
|
+
module Analytics
|
|
6
|
+
module Matomo
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
# rubocop:disable Metrics/BlockLength
|
|
10
|
+
class_methods do
|
|
11
|
+
# Loads configuration options from config/analytics.yml. Expected structure:
|
|
12
|
+
# `analytics:`
|
|
13
|
+
# ` matomo:`
|
|
14
|
+
# ` base_url: <%= ENV['MATOMOT_BASE_URL']`
|
|
15
|
+
# ` site_id: <%= ENV['MATOMOT_SITE_ID']`
|
|
16
|
+
# ` auth_token: <%= ENV['MATOMOT_AUTH_TOKEN']`
|
|
17
|
+
# @return [Config]
|
|
18
|
+
def config
|
|
19
|
+
@config ||= Config.load_from_yaml
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class Config
|
|
23
|
+
def self.load_from_yaml
|
|
24
|
+
filename = Rails.root.join('config', 'analytics.yml')
|
|
25
|
+
yaml = YAML.safe_load(ERB.new(File.read(filename)).result)
|
|
26
|
+
unless yaml
|
|
27
|
+
Rails.logger.error("Unable to fetch any keys from #{filename}.")
|
|
28
|
+
return new({})
|
|
29
|
+
end
|
|
30
|
+
new yaml.fetch('analytics')&.fetch('matomo')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
REQUIRED_KEYS = %w[base_url site_id auth_token].freeze
|
|
34
|
+
|
|
35
|
+
def initialize(config)
|
|
36
|
+
@config = config
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [Boolean] are all the required values present?
|
|
40
|
+
def valid?
|
|
41
|
+
config_keys = @config.keys
|
|
42
|
+
REQUIRED_KEYS.all? { |required| config_keys.include?(required) }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
REQUIRED_KEYS.each do |key|
|
|
46
|
+
class_eval %{ def #{key}; @config.fetch('#{key}'); end }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Period Options = "day, week, month, year, range"
|
|
51
|
+
# Date Format = "2021-01-01,2021-01-31"
|
|
52
|
+
|
|
53
|
+
def default_date_range
|
|
54
|
+
"#{Hyrax.config.analytics_start_date},#{Time.zone.today}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns a total count of an event action over a date range
|
|
58
|
+
def total_events(action, date = default_date_range)
|
|
59
|
+
additional_params = { label: action }
|
|
60
|
+
response = api_params('Events.getAction', 'range', date, additional_params)
|
|
61
|
+
response&.first ? response.first["nb_events"] : 0
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns a total count of an event action for an id over a date range
|
|
65
|
+
def total_events_for_id(id, action, date = default_date_range)
|
|
66
|
+
additional_params = {
|
|
67
|
+
flat: 1,
|
|
68
|
+
label: "#{id} - #{action}"
|
|
69
|
+
}
|
|
70
|
+
response = api_params('Events.getName', 'range', date, additional_params)
|
|
71
|
+
response&.first ? response.first["nb_events"] : 0
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def daily_events(action, date = default_date_range)
|
|
75
|
+
additional_params = { label: action }
|
|
76
|
+
response = api_params('Events.getAction', 'day', date, additional_params)
|
|
77
|
+
results_array(response, 'nb_events')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Pass in an action name and an id and get back the daily count of events for that id. [date, event_count]
|
|
81
|
+
def daily_events_for_id(id, action, date = default_date_range)
|
|
82
|
+
additional_params = {
|
|
83
|
+
flat: 1,
|
|
84
|
+
label: "#{id} - #{action}"
|
|
85
|
+
}
|
|
86
|
+
response = api_params('Events.getName', 'day', date, additional_params)
|
|
87
|
+
results_array(response, 'nb_events')
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Returns a list of the total of events by id in the format of [["id", event_count]]
|
|
91
|
+
def top_events(action, date = default_date_range)
|
|
92
|
+
additional_params = {
|
|
93
|
+
flat: '1',
|
|
94
|
+
filter_column: 'Events_EventAction',
|
|
95
|
+
filter_pattern: action.to_s,
|
|
96
|
+
filter_limit: '-1',
|
|
97
|
+
filter_sort_column: 'nb_events',
|
|
98
|
+
filter_sort_order: 'desc'
|
|
99
|
+
}
|
|
100
|
+
response = api_params('Events.getName', 'range', date, additional_params)
|
|
101
|
+
response.map { |res| [res['Events_EventName'], res['nb_events']] }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Filter the daily events by a specific action and get back the daily count of number of events.
|
|
105
|
+
# TODO(geezy): NOT IN USE BUT SAVING FOR POTENTIAL REFACTOR
|
|
106
|
+
def filter_by_action(action, response)
|
|
107
|
+
results = []
|
|
108
|
+
response.each do |result|
|
|
109
|
+
if result[1].empty?
|
|
110
|
+
results.push([result[0].to_date, 0])
|
|
111
|
+
elsif result[1].is_a?(Array)
|
|
112
|
+
result[1].each do |subtable|
|
|
113
|
+
results.push([result[0].to_date, subtable["nb_events"].to_i]) if subtable["label"] == action
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
Hyrax::Analytics::Results.new(results)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def unique_visitors(date = default_date_range)
|
|
121
|
+
response = api_params('Actions.get', 'day', date)
|
|
122
|
+
results_array(response, 'nb_uniq_pageviews')
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def unique_visitors_for_id(url, date = default_date_range)
|
|
126
|
+
# additional_params = { pageUrl: url }
|
|
127
|
+
# response = api_params('Actions.getPageUrl', 'day', date, additional_params)
|
|
128
|
+
# results_array(response, 'nb_uniq_visitors')
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def new_visitors(period = 'month', date = 'today')
|
|
132
|
+
response = api_params('VisitFrequency.get', period, date)
|
|
133
|
+
response["nb_visits_new"]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def new_visits_by_day(date = default_date_range, period = 'day')
|
|
137
|
+
result = api_params('VisitFrequency.get', period, date)
|
|
138
|
+
results_array(result, 'nb_visits_new')
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def returning_visitors(period = 'month', date = 'today')
|
|
142
|
+
response = api_params('VisitFrequency.get', period, date)
|
|
143
|
+
response["nb_visits_returning"]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def returning_visits_by_day(date = default_date_range, period = 'day')
|
|
147
|
+
result = api_params('VisitFrequency.get', period, date)
|
|
148
|
+
results_array(result, 'nb_visits_returning')
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def total_visitors(period = 'month', date = 'today')
|
|
152
|
+
response = api_params('VisitFrequency.get', period, date)
|
|
153
|
+
response["nb_visits_returning"].to_i + response["nb_visits_new"].to_i
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def results_array(response, metric)
|
|
157
|
+
results = []
|
|
158
|
+
response.each do |result|
|
|
159
|
+
if result[1].empty?
|
|
160
|
+
results.push([result[0].to_date, 0])
|
|
161
|
+
elsif result[1].is_a?(Array)
|
|
162
|
+
results.push([result[0].to_date, result[1].first[metric]])
|
|
163
|
+
else
|
|
164
|
+
results.push([result[0].to_date, result[1][metric].presence || 0])
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
Hyrax::Analytics::Results.new(results)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def get(params)
|
|
171
|
+
response = Faraday.get(config.base_url, params)
|
|
172
|
+
return [] if response.status != 200
|
|
173
|
+
JSON.parse(response.body)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def api_params(method, period, date, additional_params = {})
|
|
177
|
+
params = {
|
|
178
|
+
module: "API",
|
|
179
|
+
idSite: config.site_id,
|
|
180
|
+
method: method,
|
|
181
|
+
period: period,
|
|
182
|
+
date: date,
|
|
183
|
+
format: "JSON",
|
|
184
|
+
token_auth: config.auth_token
|
|
185
|
+
}
|
|
186
|
+
params.merge!(additional_params)
|
|
187
|
+
get(params)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
# rubocop:enable Metrics/ModuleLength
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Hyrax
|
|
3
|
+
module Analytics
|
|
4
|
+
class Results
|
|
5
|
+
require 'csv'
|
|
6
|
+
|
|
7
|
+
attr_accessor :results
|
|
8
|
+
|
|
9
|
+
def initialize(results)
|
|
10
|
+
@results ||= results
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def all
|
|
14
|
+
results.inject(0) { |sum, a| sum + a[1] }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def day(date = Time.zone.today)
|
|
18
|
+
start_date = date.at_beginning_of_day
|
|
19
|
+
end_date = date.at_end_of_day
|
|
20
|
+
range_results = []
|
|
21
|
+
results.each do |result|
|
|
22
|
+
range_results.push(result) if (start_date..end_date).cover?(result[0])
|
|
23
|
+
end
|
|
24
|
+
range_results.inject(0) { |sum, a| sum + a[1] }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def week(date = Time.zone.today)
|
|
28
|
+
start_date = date.at_beginning_of_week
|
|
29
|
+
end_date = date.at_end_of_week
|
|
30
|
+
range_results = []
|
|
31
|
+
results.each do |result|
|
|
32
|
+
range_results.push(result) if (start_date..end_date).cover?(result[0])
|
|
33
|
+
end
|
|
34
|
+
range_results.inject(0) { |sum, a| sum + a[1] }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def month(date = Time.zone.today)
|
|
38
|
+
start_date = date.at_beginning_of_month
|
|
39
|
+
end_date = date.at_end_of_month
|
|
40
|
+
range_results = []
|
|
41
|
+
results.each do |result|
|
|
42
|
+
range_results.push(result) if (start_date..end_date).cover?(result[0])
|
|
43
|
+
end
|
|
44
|
+
range_results.inject(0) { |sum, a| sum + a[1] }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def year(date = Time.zone.today)
|
|
48
|
+
start_date = date.at_beginning_of_year
|
|
49
|
+
end_date = date.at_end_of_year
|
|
50
|
+
range_results = []
|
|
51
|
+
results.each do |result|
|
|
52
|
+
range_results.push(result) if (start_date..end_date).cover?(result[0])
|
|
53
|
+
end
|
|
54
|
+
range_results.inject(0) { |sum, a| sum + a[1] }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def range(start_date = Time.zone.today - 1.month, end_date = Time.zone.today)
|
|
58
|
+
range_results = []
|
|
59
|
+
results.each do |result|
|
|
60
|
+
range_results.push(result) if (start_date..end_date).cover?(result[0])
|
|
61
|
+
end
|
|
62
|
+
range_results.inject(0) { |sum, a| sum + a[1] }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def to_csv
|
|
66
|
+
results.inject([]) { |csv, row| csv << CSV.generate_line(row) }.join("")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def list
|
|
70
|
+
results.inject([]) { |line, row| line << row }.reverse
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def to_flot
|
|
74
|
+
fields = [:date, :pageviews]
|
|
75
|
+
results.map { |row| fields.zip(row).to_h }
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -1,89 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require 'oauth2'
|
|
3
|
-
require 'signet/oauth_2/client'
|
|
4
|
-
|
|
5
2
|
module Hyrax
|
|
6
3
|
module Analytics
|
|
7
|
-
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
private_class_method :config
|
|
19
|
-
|
|
20
|
-
class Config
|
|
21
|
-
def self.load_from_yaml
|
|
22
|
-
filename = Rails.root.join('config', 'analytics.yml')
|
|
23
|
-
yaml = YAML.safe_load(ERB.new(File.read(filename)).result)
|
|
24
|
-
unless yaml
|
|
25
|
-
Rails.logger.error("Unable to fetch any keys from #{filename}.")
|
|
26
|
-
return new({})
|
|
27
|
-
end
|
|
28
|
-
new yaml.fetch('analytics')
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
REQUIRED_KEYS = %w[app_name app_version privkey_path privkey_secret client_email].freeze
|
|
32
|
-
|
|
33
|
-
def initialize(config)
|
|
34
|
-
@config = config
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# @return [Boolean] are all the required values present?
|
|
38
|
-
def valid?
|
|
39
|
-
config_keys = @config.keys
|
|
40
|
-
REQUIRED_KEYS.all? { |required| config_keys.include?(required) }
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
REQUIRED_KEYS.each do |key|
|
|
44
|
-
class_eval %{ def #{key}; @config.fetch('#{key}'); end }
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Generate an OAuth2 token for Google Analytics
|
|
49
|
-
# @return [OAuth2::AccessToken] An OAuth2 access token for GA
|
|
50
|
-
def self.token(scope = 'https://www.googleapis.com/auth/analytics.readonly')
|
|
51
|
-
access_token = auth_client(scope).fetch_access_token!
|
|
52
|
-
OAuth2::AccessToken.new(oauth_client, access_token['access_token'], expires_in: access_token['expires_in'])
|
|
4
|
+
##
|
|
5
|
+
# a completely empty module to include if no parser is configured
|
|
6
|
+
module NullAnalyticsParser; end
|
|
7
|
+
|
|
8
|
+
def self.provider_parser
|
|
9
|
+
"Hyrax::Analytics::#{Hyrax.config.analytics_provider.to_s.capitalize}".constantize
|
|
10
|
+
rescue NameError => err
|
|
11
|
+
Hyrax.logger.warn("Couldn't find an Analytics provider matching "\
|
|
12
|
+
" #{Hyrax.config.analytics_provider}. Loading " \
|
|
13
|
+
" NullAnalyticsProvider.\n#{err.message}")
|
|
14
|
+
NullAnalyticsParser
|
|
53
15
|
end
|
|
54
16
|
|
|
55
|
-
|
|
56
|
-
OAuth2::Client.new('', '', authorize_url: 'https://accounts.google.com/o/oauth2/auth',
|
|
57
|
-
token_url: 'https://accounts.google.com/o/oauth2/token')
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def self.auth_client(scope)
|
|
61
|
-
raise "Private key file for Google analytics was expected at '#{config.privkey_path}', but no file was found." unless File.exist?(config.privkey_path)
|
|
62
|
-
private_key = File.read(config.privkey_path)
|
|
63
|
-
Signet::OAuth2::Client.new token_credential_uri: 'https://accounts.google.com/o/oauth2/token',
|
|
64
|
-
audience: 'https://accounts.google.com/o/oauth2/token',
|
|
65
|
-
scope: scope,
|
|
66
|
-
issuer: config.client_email,
|
|
67
|
-
signing_key: OpenSSL::PKCS12.new(private_key, config.privkey_secret).key,
|
|
68
|
-
sub: config.client_email
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
private_class_method :token
|
|
72
|
-
|
|
73
|
-
# Return a user object linked to a Google Analytics account
|
|
74
|
-
# @return [Legato::User] A user account wit GA access
|
|
75
|
-
def self.user
|
|
76
|
-
Legato::User.new(token)
|
|
77
|
-
end
|
|
78
|
-
private_class_method :user
|
|
79
|
-
|
|
80
|
-
# Return a Google Analytics profile matching specified ID
|
|
81
|
-
# @ return [Legato::Management::Profile] A user profile associated with GA
|
|
82
|
-
def self.profile
|
|
83
|
-
return unless config.valid?
|
|
84
|
-
user.profiles.detect do |profile|
|
|
85
|
-
profile.web_property_id == Hyrax.config.google_analytics_id
|
|
86
|
-
end
|
|
87
|
-
end
|
|
17
|
+
include provider_parser
|
|
88
18
|
end
|
|
89
19
|
end
|