hyrax 3.5.0 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.dassie/config/analytics.yml +4 -2
- data/.regen +2 -1
- data/app/assets/javascripts/hyrax/analytics_events.js +88 -38
- data/app/controllers/hyrax/admin/analytics/collection_reports_controller.rb +2 -2
- data/app/controllers/hyrax/admin/analytics/work_reports_controller.rb +1 -1
- data/app/controllers/hyrax/admin/strategies_controller.rb +1 -1
- data/app/controllers/hyrax/admin/workflows_controller.rb +48 -3
- data/app/services/hyrax/analytics/ga4.rb +204 -0
- data/app/services/hyrax/analytics/google.rb +23 -15
- data/app/services/hyrax/analytics/matomo.rb +4 -3
- data/app/services/hyrax/solr_query_service.rb +4 -4
- data/app/services/hyrax/workflow/actionable_objects.rb +28 -3
- data/app/services/hyrax/workflow/permission_query.rb +23 -2
- data/app/views/hyrax/admin/analytics/collection_reports/index.html.erb +1 -1
- data/app/views/hyrax/admin/analytics/work_reports/index.html.erb +1 -1
- data/app/views/hyrax/admin/workflows/_tabs.html.erb +9 -0
- data/app/views/hyrax/admin/workflows/index.html.erb +53 -78
- data/app/views/hyrax/base/_show_actions.html.erb +1 -1
- data/app/views/hyrax/base/_work_button_row.html.erb +1 -1
- data/app/views/hyrax/dashboard/_user_activity.html.erb +1 -1
- data/app/views/hyrax/dashboard/show_admin.html.erb +1 -1
- data/app/views/hyrax/dashboard/sidebar/_activity.html.erb +1 -1
- data/app/views/hyrax/file_sets/_show_actions.html.erb +1 -1
- data/app/views/layouts/_head_tag_content.html.erb +5 -2
- data/app/views/shared/_ga4.html.erb +10 -0
- data/config/locales/hyrax.en.yml +8 -0
- data/documentation/developing-your-hyrax-based-app.md +1 -1
- data/documentation/legacyREADME.md +2 -2
- data/hyrax.gemspec +1 -1
- data/lib/generators/hyrax/templates/config/analytics.yml +3 -0
- data/lib/hyrax/version.rb +1 -1
- data/template.rb +1 -1
- metadata +12 -5
- data/.github/workflows/main.yml +0 -17
- data/.github/workflows/release.yml +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48404af16c40c5a6144f84f4747f391276dbbf03901c49947658904296ad1510
|
4
|
+
data.tar.gz: 6836a04cd80c63455b5670c591c102d5ac20224cd3746051eb6df0fc17daaaf5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 10235f122db54b7d649c42208982e93eee952d16ee921c4db12ab8e42fb3a30d4834e1a9ae509bec3aa8de9427d05bb78725f06ec9ce0455093ea58cc02c2cd8
|
7
|
+
data.tar.gz: 5e8df023cfdff3daedba28bdd012ddb7dbf26d8ef12dab69b6e648d4aca742db5948830510a5890a12f1220ca8f5350991ce95156bbab6f559c2669b6e1f10be
|
@@ -1,13 +1,15 @@
|
|
1
1
|
analytics:
|
2
|
+
ga4:
|
3
|
+
analytics_id: <%= ENV['GOOGLE_ANALYTICS_ID'] %>
|
2
4
|
google:
|
3
5
|
analytics_id: <%= ENV['GOOGLE_ANALYTICS_ID'] %>
|
4
6
|
app_name: <%= ENV['GOOGLE_OAUTH_APP_NAME'] %>
|
5
7
|
app_version: <%= ENV['GOOGLE_OAUTH_APP_VERSION'] %>
|
8
|
+
privkey_value: <%= ENV['GOOGLE_OAUTH_PRIVATE_KEY_VALUE'] %>
|
6
9
|
privkey_path: <%= ENV['GOOGLE_OAUTH_PRIVATE_KEY_PATH'] %>
|
7
10
|
privkey_secret: <%= ENV['GOOGLE_OAUTH_PRIVATE_KEY_SECRET'] %>
|
8
11
|
client_email: <%= ENV['GOOGLE_OAUTH_CLIENT_EMAIL'] %>
|
9
|
-
matomo:
|
12
|
+
matomo:
|
10
13
|
base_url: <%= ENV['MATOMO_BASE_URL'] %>
|
11
14
|
site_id: <%= ENV['MATOMO_SITE_ID'] %>
|
12
15
|
auth_token: <%= ENV['MATOMO_AUTH_TOKEN'] %>
|
13
|
-
|
data/.regen
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
|
1
|
+
# When updating CI regen seed, set to current date.
|
2
|
+
2023-05-24T10:43:07
|
@@ -1,75 +1,125 @@
|
|
1
1
|
class TrackingTags {
|
2
2
|
constructor(provider) {
|
3
3
|
this.provider = provider
|
4
|
+
switch(this.provider) {
|
5
|
+
case 'matomo':
|
6
|
+
this.tracker = new MatomoTagTracker();
|
7
|
+
break;
|
8
|
+
case 'google':
|
9
|
+
this.tracker = new UATagTracker();
|
10
|
+
break;
|
11
|
+
case 'ga4':
|
12
|
+
this.tracker = new GA4TagTracker();
|
13
|
+
break;
|
14
|
+
default:
|
15
|
+
console.error('Unsupport analytics provider ' + this.provider + ', supported values are: matomo, google, ga4');
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
// Track an event with the configured provider
|
20
|
+
trackTagEvent(category, action, name) {
|
21
|
+
this.tracker.trackEvent(category, action, name);
|
4
22
|
}
|
5
23
|
|
24
|
+
// Track a page view with the configured provider
|
25
|
+
trackPageView() {
|
26
|
+
this.tracker.trackPageView();
|
27
|
+
}
|
28
|
+
|
29
|
+
// Deprecated: use trackTagEvent and trackPageView instead.
|
6
30
|
analytics() {
|
7
|
-
|
8
|
-
return _paq;
|
9
|
-
}
|
10
|
-
else {
|
11
|
-
return _gaq;
|
12
|
-
}
|
31
|
+
return this;
|
13
32
|
}
|
14
33
|
|
15
|
-
|
16
|
-
|
17
|
-
|
34
|
+
// Deprecated: use trackTagEvent and trackPageView instead.
|
35
|
+
push(params) {
|
36
|
+
if (params[0] == 'trackPageView' || params[0] == '_trackPageView') {
|
37
|
+
this.tracker.trackPageView();
|
18
38
|
} else {
|
19
|
-
|
39
|
+
this.tracker.trackTagEvent(params[1], params[2], params[3]);
|
20
40
|
}
|
21
41
|
}
|
22
42
|
|
43
|
+
// Deprecated
|
44
|
+
pageView() {
|
45
|
+
return 'trackPageView';
|
46
|
+
}
|
47
|
+
|
48
|
+
// Deprecated
|
23
49
|
trackEvent() {
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
50
|
+
return 'trackEvent';
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
class GA4TagTracker {
|
55
|
+
trackEvent(category, action, name) {
|
56
|
+
gtag('event', action, {
|
57
|
+
'category': category,
|
58
|
+
'label': name
|
59
|
+
});
|
60
|
+
}
|
61
|
+
|
62
|
+
trackPageView() {
|
63
|
+
// No operation necessary, this event is automatically collected
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
class UATagTracker {
|
68
|
+
trackEvent(category, action, name) {
|
69
|
+
_gaq.push(['_trackEvent', category, action, name]);
|
70
|
+
}
|
71
|
+
|
72
|
+
trackPageView() {
|
73
|
+
_gaq.push(['_trackPageView']);
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
class MatomoTagTracker {
|
78
|
+
trackEvent(category, action, name) {
|
79
|
+
_paq.push(['trackEvent', category, action, name]);
|
80
|
+
}
|
81
|
+
|
82
|
+
trackPageView() {
|
83
|
+
_paq.push(['trackPageView']);
|
29
84
|
}
|
30
85
|
}
|
31
86
|
|
32
87
|
function trackPageView() {
|
33
|
-
window.trackingTags.
|
88
|
+
window.trackingTags.trackPageView();
|
34
89
|
}
|
35
90
|
|
36
91
|
function trackAnalyticsEvents() {
|
37
92
|
$('span.analytics-event').each(function(){
|
38
|
-
var eventSpan = $(this)
|
39
|
-
window.trackingTags.
|
93
|
+
var eventSpan = $(this);
|
94
|
+
window.trackingTags.trackTagEvent(eventSpan.data('category'), eventSpan.data('action'), eventSpan.data('name'));
|
40
95
|
})
|
41
96
|
}
|
42
97
|
|
43
98
|
function setupTracking() {
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
99
|
+
var provider = $('meta[name="analytics-provider"]').prop('content')
|
100
|
+
if (provider === undefined) {
|
101
|
+
return;
|
102
|
+
}
|
103
|
+
window.trackingTags = new TrackingTags(provider);
|
104
|
+
trackPageView();
|
105
|
+
trackAnalyticsEvents();
|
51
106
|
}
|
52
107
|
|
53
108
|
if (typeof Turbolinks !== 'undefined') {
|
54
109
|
$(document).on('turbolinks:load', function() {
|
55
|
-
setupTracking()
|
56
|
-
})
|
110
|
+
setupTracking();
|
111
|
+
});
|
57
112
|
} else {
|
58
113
|
$(document).on('ready', function() {
|
59
|
-
setupTracking()
|
60
|
-
})
|
114
|
+
setupTracking();
|
115
|
+
});
|
61
116
|
}
|
62
117
|
|
63
118
|
$(document).on('click', '#file_download', function(e) {
|
64
|
-
|
65
|
-
|
66
|
-
return;
|
67
|
-
}
|
68
|
-
window.trackingTags = new TrackingTags(provider)
|
69
|
-
window.trackingTags.analytics().push([trackingTags.trackEvent(), 'file-set', 'file-set-download', $(this).data('label')]);
|
70
|
-
window.trackingTags.analytics().push([trackingTags.trackEvent(), 'file-set-in-work', 'file-set-in-work-download', $(this).data('work-id')]);
|
119
|
+
window.trackingTags.trackTagEvent('file-set', 'file-set-download', $(this).data('label'));
|
120
|
+
window.trackingTags.trackTagEvent('file-set-in-work', 'file-set-in-work-download', $(this).data('work-id'));
|
71
121
|
$(this).data('collection-ids').forEach(function (collection) {
|
72
|
-
window.trackingTags.
|
73
|
-
window.trackingTags.
|
122
|
+
window.trackingTags.trackTagEvent('file-set-in-collection', 'file-set-in-collection-download', collection);
|
123
|
+
window.trackingTags.trackTagEvent('work-in-collection', 'work-in-collection-download', collection);
|
74
124
|
});
|
75
125
|
});
|
@@ -5,7 +5,7 @@ module Hyrax
|
|
5
5
|
class CollectionReportsController < AnalyticsController
|
6
6
|
include Hyrax::BreadcrumbsForCollectionAnalytics
|
7
7
|
def index
|
8
|
-
return unless Hyrax.config.analytics?
|
8
|
+
return unless Hyrax.config.analytics? && Hyrax.config.analytics_provider != 'ga4'
|
9
9
|
|
10
10
|
@pageviews = Hyrax::Analytics.daily_events('collection-page-view')
|
11
11
|
@work_page_views = Hyrax::Analytics.daily_events('work-in-collection-view')
|
@@ -21,7 +21,7 @@ module Hyrax
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def show
|
24
|
-
return unless Hyrax.config.analytics?
|
24
|
+
return unless Hyrax.config.analytics? && Hyrax.config.analytics_provider != 'ga4'
|
25
25
|
@document = ::SolrDocument.find(params[:id])
|
26
26
|
@pageviews = Hyrax::Analytics.daily_events_for_id(@document.id, 'collection-page-view')
|
27
27
|
@work_page_views = Hyrax::Analytics.daily_events_for_id(@document.id, 'work-in-collection-view')
|
@@ -6,7 +6,7 @@ module Hyrax
|
|
6
6
|
include Hyrax::BreadcrumbsForWorksAnalytics
|
7
7
|
|
8
8
|
def index
|
9
|
-
return unless Hyrax.config.analytics?
|
9
|
+
return unless Hyrax.config.analytics? && Hyrax.config.analytics_provider != 'ga4'
|
10
10
|
|
11
11
|
@accessible_works ||= accessible_works
|
12
12
|
@accessible_file_sets ||= accessible_file_sets
|
@@ -15,9 +15,8 @@ module Hyrax
|
|
15
15
|
add_breadcrumb t(:'hyrax.dashboard.breadcrumbs.admin'), hyrax.dashboard_path
|
16
16
|
add_breadcrumb t(:'hyrax.admin.sidebar.tasks'), '#'
|
17
17
|
add_breadcrumb t(:'hyrax.admin.sidebar.workflow_review'), request.path
|
18
|
-
|
19
|
-
@
|
20
|
-
@published_list = actionable_objects.select(&:published?)
|
18
|
+
assign_action_objects_params
|
19
|
+
@response = WorkflowResponse.new(actionable_objects.to_a, actionable_objects.total_count, current_page, per_page, under_review?)
|
21
20
|
end
|
22
21
|
|
23
22
|
private
|
@@ -30,5 +29,51 @@ module Hyrax
|
|
30
29
|
@actionable_objects ||=
|
31
30
|
Hyrax::Workflow::ActionableObjects.new(user: current_user)
|
32
31
|
end
|
32
|
+
|
33
|
+
def current_page
|
34
|
+
@page ||= params.fetch('page', 1).to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
def per_page
|
38
|
+
@per_page ||= params.fetch('per_page', 10).to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
def assign_action_objects_params
|
42
|
+
actionable_objects.page = current_page
|
43
|
+
actionable_objects.per_page = per_page
|
44
|
+
actionable_objects.workflow_state_filter = (under_review? ? '!' : '') + deposited_workflow_state_name
|
45
|
+
end
|
46
|
+
|
47
|
+
def under_review?
|
48
|
+
@under_review = params['state'] != 'published'
|
49
|
+
end
|
50
|
+
|
51
|
+
class WorkflowResponse
|
52
|
+
attr_reader :total_count
|
53
|
+
attr_reader :current_page
|
54
|
+
attr_reader :per_page
|
55
|
+
attr_reader :docs
|
56
|
+
attr_reader :under_review
|
57
|
+
|
58
|
+
def initialize(docs, total_count, page, per_page, under_review)
|
59
|
+
@docs = docs
|
60
|
+
@total_count = total_count
|
61
|
+
@per_page = per_page.to_i
|
62
|
+
@current_page = page.to_i
|
63
|
+
@under_review = under_review
|
64
|
+
end
|
65
|
+
|
66
|
+
def total_pages
|
67
|
+
(total_count.to_f / per_page).ceil
|
68
|
+
end
|
69
|
+
|
70
|
+
def limit_value
|
71
|
+
docs.length
|
72
|
+
end
|
73
|
+
|
74
|
+
def viewing_under_review?
|
75
|
+
under_review
|
76
|
+
end
|
77
|
+
end
|
33
78
|
end
|
34
79
|
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 Ga4
|
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
|
+
# ` ga4:`
|
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
|
+
Hyrax.logger.error("Unable to fetch any keys from #{filename}.")
|
31
|
+
return new({})
|
32
|
+
end
|
33
|
+
config = yaml.fetch('analytics')&.fetch('ga4', 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
|
@@ -1,20 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'oauth2'
|
3
4
|
require 'signet/oauth_2/client'
|
4
5
|
|
5
|
-
# rubocop:disable Metrics/ModuleLength
|
6
6
|
module Hyrax
|
7
7
|
module Analytics
|
8
|
+
# rubocop:disable Metrics/ModuleLength
|
8
9
|
module Google
|
9
10
|
extend ActiveSupport::Concern
|
10
11
|
# rubocop:disable Metrics/BlockLength
|
11
12
|
class_methods do
|
12
|
-
# Loads configuration options from config/analytics.yml.
|
13
|
+
# Loads configuration options from config/analytics.yml. You only need PRIVATE_KEY_PATH or
|
14
|
+
# PRIVATE_KEY_VALUE. VALUE takes precedence.
|
15
|
+
# Expected structure:
|
13
16
|
# `analytics:`
|
14
17
|
# ` google:`
|
15
18
|
# ` app_name: <%= ENV['GOOGLE_OAUTH_APP_NAME']`
|
16
19
|
# ` app_version: <%= ENV['GOOGLE_OAUTH_APP_VERSION']`
|
17
20
|
# ` privkey_path: <%= ENV['GOOGLE_OAUTH_PRIVATE_KEY_PATH']`
|
21
|
+
# ` privkey_value: <%= ENV['GOOGLE_OAUTH_PRIVATE_KEY_VALUE']`
|
18
22
|
# ` privkey_secret: <%= ENV['GOOGLE_OAUTH_PRIVATE_KEY_SECRET']`
|
19
23
|
# ` client_email: <%= ENV['GOOGLE_OAUTH_CLIENT_EMAIL']`
|
20
24
|
# @return [Config]
|
@@ -41,7 +45,8 @@ module Hyrax
|
|
41
45
|
new config
|
42
46
|
end
|
43
47
|
|
44
|
-
|
48
|
+
KEYS = %w[analytics_id app_name app_version privkey_path privkey_value privkey_secret client_email].freeze
|
49
|
+
REQUIRED_KEYS = %w[analytics_id app_name app_version privkey_secret client_email].freeze
|
45
50
|
|
46
51
|
def initialize(config)
|
47
52
|
@config = config
|
@@ -49,18 +54,16 @@ module Hyrax
|
|
49
54
|
|
50
55
|
# @return [Boolean] are all the required values present?
|
51
56
|
def valid?
|
52
|
-
|
53
|
-
REQUIRED_KEYS.all? { |required| config_keys.include?(required) }
|
54
|
-
end
|
57
|
+
return false unless @config['privkey_value'].present? || @config['privkey_path'].present?
|
55
58
|
|
56
|
-
|
57
|
-
class_eval %{ def #{key}; @config.fetch('#{key}'); end }
|
59
|
+
REQUIRED_KEYS.all? { |required| @config[required].present? }
|
58
60
|
end
|
59
61
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
@config['
|
62
|
+
KEYS.each do |key|
|
63
|
+
# rubocop:disable Style/EvalWithLocation
|
64
|
+
class_eval %{ def #{key}; @config.fetch('#{key}'); end }
|
65
|
+
class_eval %{ def #{key}=(value); @config['#{key}'] = value; end }
|
66
|
+
# rubocop:enable Style/EvalWithLocation
|
64
67
|
end
|
65
68
|
end
|
66
69
|
|
@@ -77,8 +80,12 @@ module Hyrax
|
|
77
80
|
end
|
78
81
|
|
79
82
|
def auth_client(scope)
|
80
|
-
|
81
|
-
private_key
|
83
|
+
private_key = Base64.decode64(config.privkey_value) if config.privkey_value.present?
|
84
|
+
if private_key.blank?
|
85
|
+
raise "Private key file for Google analytics was expected at '#{config.privkey_path}', but no file was found." unless File.exist?(config.privkey_path)
|
86
|
+
|
87
|
+
private_key = File.read(config.privkey_path)
|
88
|
+
end
|
82
89
|
Signet::OAuth2::Client.new token_credential_uri: 'https://accounts.google.com/o/oauth2/token',
|
83
90
|
audience: 'https://accounts.google.com/o/oauth2/token',
|
84
91
|
scope: scope,
|
@@ -123,7 +130,7 @@ module Hyrax
|
|
123
130
|
|
124
131
|
[start_date, end_date]
|
125
132
|
end
|
126
|
-
# rubocop:
|
133
|
+
# rubocop:enable Metrics/MethodLength
|
127
134
|
|
128
135
|
def keyword_conversion(date)
|
129
136
|
case date
|
@@ -199,6 +206,7 @@ module Hyrax
|
|
199
206
|
end
|
200
207
|
# rubocop:enable Metrics/BlockLength
|
201
208
|
end
|
209
|
+
# rubocop:enable Metrics/ModuleLength
|
202
210
|
end
|
203
211
|
end
|
204
212
|
# rubocop:enable Metrics/ModuleLength
|
@@ -11,15 +11,16 @@ module Hyrax
|
|
11
11
|
# Loads configuration options from config/analytics.yml. Expected structure:
|
12
12
|
# `analytics:`
|
13
13
|
# ` matomo:`
|
14
|
-
# ` base_url: <%= ENV['
|
15
|
-
# ` site_id: <%= ENV['
|
16
|
-
# ` auth_token: <%= ENV['
|
14
|
+
# ` base_url: <%= ENV['MATOMO_BASE_URL']`
|
15
|
+
# ` site_id: <%= ENV['MATOMO_SITE_ID']`
|
16
|
+
# ` auth_token: <%= ENV['MATOMO_AUTH_TOKEN']`
|
17
17
|
# @return [Config]
|
18
18
|
def config
|
19
19
|
@config ||= Config.load_from_yaml
|
20
20
|
end
|
21
21
|
|
22
22
|
class Config
|
23
|
+
# TODO: test matomo and see if it needs any of the updates from https://github.com/samvera/hyrax/pull/6063
|
23
24
|
def self.load_from_yaml
|
24
25
|
filename = Rails.root.join('config', 'analytics.yml')
|
25
26
|
yaml = YAML.safe_load(ERB.new(File.read(filename)).result)
|
@@ -27,14 +27,14 @@ module Hyrax
|
|
27
27
|
|
28
28
|
##
|
29
29
|
# @return [Hash] the results returned from solr for the current query
|
30
|
-
def get
|
31
|
-
solr_service.get(build)
|
30
|
+
def get(*args)
|
31
|
+
solr_service.get(build, *args)
|
32
32
|
end
|
33
33
|
|
34
34
|
##
|
35
35
|
# @return [Enumerable<SolrDocument>]
|
36
|
-
def solr_documents
|
37
|
-
get['response']['docs'].map { |doc| self.class.document_model.new(doc) }
|
36
|
+
def solr_documents(*args)
|
37
|
+
get(*args)['response']['docs'].map { |doc| self.class.document_model.new(doc) }
|
38
38
|
end
|
39
39
|
|
40
40
|
##
|
@@ -20,11 +20,27 @@ module Hyrax
|
|
20
20
|
# @!attribute [rw] user
|
21
21
|
# @return [::User]
|
22
22
|
attr_accessor :user
|
23
|
+
##
|
24
|
+
# @!attribute [rw] workflow_state_filter
|
25
|
+
# @return [String]
|
26
|
+
attr_accessor :workflow_state_filter
|
27
|
+
##
|
28
|
+
# @!attribute [rw] page of results to return, 1 based
|
29
|
+
# @return [Integer]
|
30
|
+
attr_accessor :page
|
31
|
+
##
|
32
|
+
# @!attribute [rw] per_page number of results in the page
|
33
|
+
# @return [Integer]
|
34
|
+
attr_accessor :per_page
|
23
35
|
|
24
36
|
##
|
25
37
|
# @param [::User] user the user whose
|
26
|
-
|
38
|
+
# @param [String] optional filter by workstate name
|
39
|
+
def initialize(user:, workflow_state_filter: nil)
|
27
40
|
@user = user
|
41
|
+
@workflow_state_filter = workflow_state_filter
|
42
|
+
@page = 1
|
43
|
+
@per_page = 10
|
28
44
|
end
|
29
45
|
|
30
46
|
##
|
@@ -34,7 +50,8 @@ module Hyrax
|
|
34
50
|
ids_and_states = id_state_pairs
|
35
51
|
return if ids_and_states.none?
|
36
52
|
|
37
|
-
docs = Hyrax::SolrQueryService.new.with_ids(ids: ids_and_states.map(&:first))
|
53
|
+
docs = Hyrax::SolrQueryService.new.with_ids(ids: ids_and_states.map(&:first))
|
54
|
+
.solr_documents(page: @page, rows: @per_page, sort: 'system_create_dtsi ASC')
|
38
55
|
|
39
56
|
docs.each do |solr_doc|
|
40
57
|
object = ObjectInWorkflowDecorator.new(solr_doc)
|
@@ -46,6 +63,13 @@ module Hyrax
|
|
46
63
|
end
|
47
64
|
end
|
48
65
|
|
66
|
+
##
|
67
|
+
# @return [Integer] total number of entities selected
|
68
|
+
def total_count
|
69
|
+
PermissionQuery.scope_entities_for_the_user(user: user, workflow_state_filter: workflow_state_filter)
|
70
|
+
.count
|
71
|
+
end
|
72
|
+
|
49
73
|
private
|
50
74
|
|
51
75
|
##
|
@@ -53,7 +77,8 @@ module Hyrax
|
|
53
77
|
# @return [Array[String, Sipity::WorkflowState]]
|
54
78
|
def id_state_pairs
|
55
79
|
gids_and_states = PermissionQuery
|
56
|
-
.scope_entities_for_the_user(user: user)
|
80
|
+
.scope_entities_for_the_user(user: user, page: page, per_page: per_page, workflow_state_filter: workflow_state_filter)
|
81
|
+
.order(created_at: :asc)
|
57
82
|
.pluck(:proxy_for_global_id, :workflow_state_id)
|
58
83
|
|
59
84
|
return [] if gids_and_states.none?
|
@@ -194,7 +194,7 @@ module Hyrax
|
|
194
194
|
# @return [ActiveRecord::Relation<Sipity::Entity>]
|
195
195
|
#
|
196
196
|
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
197
|
-
def scope_entities_for_the_user(user:)
|
197
|
+
def scope_entities_for_the_user(user:, page: 1, per_page: nil, workflow_state_filter: nil)
|
198
198
|
entities = Sipity::Entity.arel_table
|
199
199
|
workflow_state_actions = Sipity::WorkflowStateAction.arel_table
|
200
200
|
workflow_states = Sipity::WorkflowState.arel_table
|
@@ -227,13 +227,34 @@ module Hyrax
|
|
227
227
|
entity_specific_where = where_builder.call(entity_responsibilities).and(
|
228
228
|
entities[:id].eq(entity_responsibilities[:entity_id])
|
229
229
|
)
|
230
|
+
entity_specific_where = filter_by_workflow_state(entity_specific_where, workflow_states, workflow_state_filter) if workflow_state_filter
|
230
231
|
workflow_specific_where = where_builder.call(workflow_responsibilities)
|
232
|
+
workflow_specific_where = filter_by_workflow_state(workflow_specific_where, workflow_states, workflow_state_filter) if workflow_state_filter
|
231
233
|
|
232
|
-
Sipity::Entity.where(
|
234
|
+
result = Sipity::Entity.where(
|
233
235
|
entities[:id].in(entity_specific_joins.where(entity_specific_where))
|
234
236
|
.or(entities[:id].in(workflow_specific_joins.where(workflow_specific_where)))
|
235
237
|
)
|
238
|
+
# Apply paging if provided
|
239
|
+
if per_page.nil?
|
240
|
+
result
|
241
|
+
else
|
242
|
+
result.page(page).per(per_page)
|
243
|
+
end
|
236
244
|
end
|
245
|
+
|
246
|
+
# @api private
|
247
|
+
#
|
248
|
+
# Append a filter by workflow state name to the provided where builder.
|
249
|
+
# If the filter begins with a !, it will filter to states not equal to the filter.
|
250
|
+
def filter_by_workflow_state(where_builder, workflow_states, filter)
|
251
|
+
if filter.start_with?('!')
|
252
|
+
where_builder.and(workflow_states[:name].not_eq(filter[1..-1]))
|
253
|
+
else
|
254
|
+
where_builder.and(workflow_states[:name].eq(filter))
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
237
258
|
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
238
259
|
|
239
260
|
# @api public
|
@@ -8,7 +8,7 @@
|
|
8
8
|
<div class="col-sm-12">
|
9
9
|
<div class="collection-reports">
|
10
10
|
|
11
|
-
<% if Hyrax.config.analytics? %>
|
11
|
+
<% if Hyrax.config.analytics? && Hyrax.config.analytics_provider != 'ga4' %>
|
12
12
|
<div class="panel panel-default">
|
13
13
|
<div class="panel-heading"><%= t('.repo_summary') %> <b><%= pluralize(Collection.count, "collection") %></b>, <%= t('.repo_summary_2') %> <b><%= @pageviews.all %> <%= t('.views') %></b> <%= t('.and') %> <b><%= @downloads.all %> <%= t('.downloads') %></b>.</div>
|
14
14
|
<div class="panel-body">
|
@@ -9,7 +9,7 @@
|
|
9
9
|
<div class="work-reports">
|
10
10
|
|
11
11
|
<div class="panel panel-default">
|
12
|
-
<% if Hyrax.config.analytics? %>
|
12
|
+
<% if Hyrax.config.analytics? && Hyrax.config.analytics_provider != 'ga4' %>
|
13
13
|
<% if current_user.ability.admin? %>
|
14
14
|
<div class="panel-heading"><b><%= @works_count %> <%= t('.works') %></b> <%= t('.repo_summary') %> <b><%= @pageviews.all if @pageviews %> <%= t('.views') %></b> <%= t('.and') %> <b><%= @downloads.all if @downloads %> <%= t('.downloads') %></b>.</div>
|
15
15
|
<% else %>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<ul class="nav nav-tabs" id="my_nav" role="list">
|
2
|
+
<li<%= ' class="active"'.html_safe if @response.viewing_under_review? %>>
|
3
|
+
<%= link_to t('hyrax.admin.workflows.index.tabs.under_review'),
|
4
|
+
hyrax.admin_workflows_path(state: 'under-review'), class: "nav-link#{' active' if @response.viewing_under_review?}" %>
|
5
|
+
</li>
|
6
|
+
<li<%= ' class="active"'.html_safe if !@response.viewing_under_review? %>>
|
7
|
+
<%= link_to t('hyrax.admin.workflows.index.tabs.published'), hyrax.admin_workflows_path(state: 'published'), class: "nav-link#{' active' if !@response.viewing_under_review?}" %>
|
8
|
+
</li>
|
9
|
+
</ul>
|
@@ -5,87 +5,62 @@
|
|
5
5
|
<div class="row">
|
6
6
|
<div class="col-md-12">
|
7
7
|
<div class="panel panel-default tabs">
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
<% @status_list.each do |document| %>
|
32
|
-
<tr>
|
33
|
-
<td>
|
34
|
-
<%= link_to document, [main_app, document] %>
|
35
|
-
</td>
|
36
|
-
<td>
|
37
|
-
<%= safe_join(document.creator, tag(:br)) %>
|
38
|
-
</td>
|
39
|
-
<td>
|
40
|
-
<%= document.date_modified %>
|
41
|
-
</td>
|
42
|
-
<td>
|
43
|
-
<span class="state state-pending"><%= document.workflow_state %></span>
|
44
|
-
</td>
|
45
|
-
</tr>
|
46
|
-
<% end %>
|
47
|
-
</tbody>
|
48
|
-
</table>
|
49
|
-
</div>
|
50
|
-
</div>
|
51
|
-
</div>
|
52
|
-
</div>
|
53
|
-
<div id="published" class="tab-pane">
|
54
|
-
<div class="panel panel-default labels">
|
55
|
-
<div class="panel-body">
|
56
|
-
<div class="table-responsive">
|
57
|
-
<table class="table table-condensed table-striped datatable">
|
58
|
-
<thead>
|
59
|
-
<tr>
|
60
|
-
<th width="40%">Work</th>
|
61
|
-
<th width="20%">Depositor</th>
|
62
|
-
<th width="20%">Submission Date</th>
|
63
|
-
<th width="20%">Status</th>
|
64
|
-
</tr>
|
65
|
-
</thead>
|
66
|
-
<tbody>
|
67
|
-
<% @published_list.each do |document| %>
|
68
|
-
<tr>
|
69
|
-
<td>
|
70
|
-
<%= link_to document, [main_app, document] %>
|
71
|
-
</td>
|
72
|
-
<td>
|
73
|
-
<%= safe_join(document.creator, tag(:br)) %>
|
74
|
-
</td>
|
75
|
-
<td>
|
76
|
-
<%= document.date_modified %>
|
77
|
-
</td>
|
78
|
-
<td>
|
79
|
-
<span class="state state-pending"><%= document.workflow_state %></span>
|
80
|
-
</td>
|
81
|
-
</tr>
|
82
|
-
<% end %>
|
83
|
-
</tbody>
|
84
|
-
</table>
|
85
|
-
</div>
|
8
|
+
<%= render 'tabs' %>
|
9
|
+
<div class="panel-heading">
|
10
|
+
<span class="count-display">
|
11
|
+
<% if @response.viewing_under_review? %>
|
12
|
+
<%= I18n.t('hyrax.admin.workflows.index.works_under_review', total_count: @response.total_count).html_safe %>
|
13
|
+
<% else %>
|
14
|
+
<%= I18n.t('hyrax.admin.workflows.index.works_published', total_count: @response.total_count).html_safe %>
|
15
|
+
<% end %>
|
16
|
+
</span>
|
17
|
+
</div>
|
18
|
+
<div class="panel-body">
|
19
|
+
<div class="row">
|
20
|
+
<div class="col-sm-12">
|
21
|
+
<div class="sort-toggle mt-2">
|
22
|
+
<%= form_tag hyrax.admin_workflows_path, method: :get, class: 'per_page' do %>
|
23
|
+
<fieldset class="col-12">
|
24
|
+
<legend class="sr-only"><%= t('hyrax.dashboard.my.sr.results_per_page') %></legend>
|
25
|
+
<%= label_tag :per_page do %>
|
26
|
+
Show <%= select_tag :per_page, options_for_select(Hyrax.config.range_for_number_of_results_to_display_per_page.map(&:to_s), h(params[:per_page])), title: "entries" %> per page
|
27
|
+
<% end %>
|
28
|
+
<%= render_hash_as_hidden_fields(search_state.params_for_search.except(:per_page, :sort, :utf8)) %>
|
29
|
+
</fieldset>
|
30
|
+
<% end %>
|
86
31
|
</div>
|
87
32
|
</div>
|
88
33
|
</div>
|
34
|
+
<h2 class="sr-only"><%= t('.works_listing') %></h2>
|
35
|
+
<table class="table table-sm table-striped works-list">
|
36
|
+
<thead>
|
37
|
+
<tr>
|
38
|
+
<th width="40%"><%= t(".heading.work") %></th>
|
39
|
+
<th width="20%"><%= t(".heading.depositor") %></th>
|
40
|
+
<th width="20%"><%= t(".heading.submission_date") %></th>
|
41
|
+
<th width="20%"><%= t(".heading.status") %></th>
|
42
|
+
</tr>
|
43
|
+
</thead>
|
44
|
+
<tbody>
|
45
|
+
<% @response.docs.each do |document| %>
|
46
|
+
<tr>
|
47
|
+
<td>
|
48
|
+
<%= link_to document, [main_app, document] %>
|
49
|
+
</td>
|
50
|
+
<td>
|
51
|
+
<%= safe_join(document.creator, tag(:br)) %>
|
52
|
+
</td>
|
53
|
+
<td>
|
54
|
+
<%= document.date_modified %>
|
55
|
+
</td>
|
56
|
+
<td>
|
57
|
+
<span class="state state-pending"><%= document.workflow_state %></span>
|
58
|
+
</td>
|
59
|
+
</tr>
|
60
|
+
<% end %>
|
61
|
+
</tbody>
|
62
|
+
</table>
|
63
|
+
<%= render 'hyrax/my/results_pagination' %>
|
89
64
|
</div>
|
90
65
|
</div>
|
91
66
|
</div>
|
@@ -17,7 +17,7 @@
|
|
17
17
|
class: presenter.display_unfeature_link? ? 'btn btn-default' : 'btn btn-default collapse' %>
|
18
18
|
<% end %>
|
19
19
|
<% end %>
|
20
|
-
<% if Hyrax.config.analytics? %>
|
20
|
+
<% if Hyrax.config.analytics? && Hyrax.config.analytics_provider != 'ga4' %>
|
21
21
|
<% # turbolinks needs to be turned off or the page will use the cache and the %>
|
22
22
|
<% # analytics graph will not show unless the page is refreshed. %>
|
23
23
|
<%= link_to t('.analytics'), presenter.stats_path, id: 'stats', class: 'btn btn-default', data: { turbolinks: false } %>
|
@@ -37,7 +37,7 @@
|
|
37
37
|
data: { behavior: 'unfeature' },
|
38
38
|
class: presenter.display_feature_link? ? 'btn btn-default collapse' : 'btn btn-default' %>
|
39
39
|
<% end %>
|
40
|
-
<% if Hyrax.config.analytics? %>
|
40
|
+
<% if Hyrax.config.analytics? && Hyrax.config.analytics_provider != 'ga4' %>
|
41
41
|
<%= link_to t(".analytics"), presenter.stats_path, id: 'stats', class: 'btn btn-default' %>
|
42
42
|
<% end %>
|
43
43
|
</div>
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<h3 class="panel-title"><%= t('.title') %></h3>
|
3
3
|
</div>
|
4
4
|
<div class="panel-body text-center">
|
5
|
-
<% if Hyrax.config.analytics? %>
|
5
|
+
<% if Hyrax.config.analytics? && Hyrax.config.analytics_provider != 'ga4' %>
|
6
6
|
<%= render 'user_activity_graph' %>
|
7
7
|
|
8
8
|
<div class="col-md-3">
|
@@ -32,7 +32,7 @@
|
|
32
32
|
<% end %>
|
33
33
|
<% end %>
|
34
34
|
|
35
|
-
<% if current_ability.can_create_any_work? && Hyrax.config.analytics? %>
|
35
|
+
<% if current_ability.can_create_any_work? && Hyrax.config.analytics? && Hyrax.config.analytics_provider != 'ga4' %>
|
36
36
|
<li>
|
37
37
|
<%= menu.collapsable_section t('hyrax.admin.sidebar.analytics'),
|
38
38
|
icon_class: "fa fa-pie-chart",
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<div class="form-actions">
|
2
|
-
<% if Hyrax.config.analytics? %>
|
2
|
+
<% if Hyrax.config.analytics? && Hyrax.config.analytics_provider != 'ga4' %>
|
3
3
|
<% # turbolinks needs to be turned off or the page will use the cache and the %>
|
4
4
|
<% # analytics graph will not show unless the page is refreshed. %>
|
5
5
|
<%= link_to t('.analytics'), @presenter.stats_path, id: 'stats', class: 'btn btn-default', data: { turbolinks: false } %>
|
@@ -26,9 +26,12 @@ signed in %>
|
|
26
26
|
<%= render 'shared/appearance_styles' %>
|
27
27
|
|
28
28
|
<% if Hyrax.config.analytics? %>
|
29
|
-
<%
|
29
|
+
<% case Hyrax.config.analytics_provider %>
|
30
|
+
<% when 'ga4' %>
|
31
|
+
<%= render partial: 'shared/ga4', formats: [:html] %>
|
32
|
+
<% when 'google' %>
|
30
33
|
<%= render partial: 'shared/ga', formats: [:html] %>
|
31
|
-
<%
|
34
|
+
<% when 'matomo' %>
|
32
35
|
<%= render partial: 'shared/matomo', formats: [:html] %>
|
33
36
|
<% end %>
|
34
37
|
<% end %>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<!-- Google tag (gtag.js) -->
|
2
|
+
<script async src="https://www.googletagmanager.com/gtag/js?id=<%= Hyrax::Analytics.config.analytics_id %>"></script>
|
3
|
+
<script>
|
4
|
+
window.dataLayer = window.dataLayer || [];
|
5
|
+
function gtag(){dataLayer.push(arguments);}
|
6
|
+
gtag('js', new Date());
|
7
|
+
|
8
|
+
gtag('config', '<%= Hyrax::Analytics.config.analytics_id %>');
|
9
|
+
</script>
|
10
|
+
<meta name="analytics-provider" content="ga4">
|
data/config/locales/hyrax.en.yml
CHANGED
@@ -459,6 +459,14 @@ en:
|
|
459
459
|
workflows:
|
460
460
|
index:
|
461
461
|
header: Review Submissions
|
462
|
+
heading:
|
463
|
+
work: Work
|
464
|
+
depositor: Depositor
|
465
|
+
submission_date: Submission Date
|
466
|
+
status: status
|
467
|
+
works_published: "<strong>%{total_count} works</strong> published"
|
468
|
+
works_under_review: "<strong>%{total_count} works</strong> under review"
|
469
|
+
works_listing: Works listing
|
462
470
|
tabs:
|
463
471
|
published: Published
|
464
472
|
under_review: Under Review
|
@@ -125,7 +125,7 @@ NOTE: The steps need to be done in order to create a new Hyrax based app.
|
|
125
125
|
Generate a new Rails application using the template.
|
126
126
|
|
127
127
|
```
|
128
|
-
rails _5.2.8.1_ new my_app -m https://raw.githubusercontent.com/samvera/hyrax/v3.
|
128
|
+
rails _5.2.8.1_ new my_app -m https://raw.githubusercontent.com/samvera/hyrax/v3.6.0/template.rb
|
129
129
|
```
|
130
130
|
|
131
131
|
Generating a new Rails application using Hyrax's template above takes cares of a number of steps for you, including:
|
@@ -50,7 +50,7 @@ The Samvera community is here to help. Please see our [support guide](./.github/
|
|
50
50
|
# Getting started
|
51
51
|
|
52
52
|
This document contains instructions specific to setting up an app with __Hyrax
|
53
|
-
v3.
|
53
|
+
v3.6.0__. If you are looking for instructions on installing a different
|
54
54
|
version, be sure to select the appropriate branch or tag from the drop-down
|
55
55
|
menu above.
|
56
56
|
|
@@ -159,7 +159,7 @@ NOTE: The steps need to be done in order to create a new Hyrax based app.
|
|
159
159
|
Generate a new Rails application using the template.
|
160
160
|
|
161
161
|
```
|
162
|
-
rails _5.2.8.1_ new my_app -m https://raw.githubusercontent.com/samvera/hyrax/v3.
|
162
|
+
rails _5.2.8.1_ new my_app -m https://raw.githubusercontent.com/samvera/hyrax/v3.6.0/template.rb
|
163
163
|
```
|
164
164
|
|
165
165
|
Generating a new Rails application using Hyrax's template above takes cares of a number of steps for you, including:
|
data/hyrax.gemspec
CHANGED
@@ -79,7 +79,7 @@ SUMMARY
|
|
79
79
|
spec.add_dependency 'rdf-vocab', '~> 3.0'
|
80
80
|
spec.add_dependency 'redis', '~> 4.0'
|
81
81
|
spec.add_dependency 'redis-namespace', '~> 1.5'
|
82
|
-
spec.add_dependency 'redlock', '>= 0.1.2'
|
82
|
+
spec.add_dependency 'redlock', '>= 0.1.2', '< 2.0'
|
83
83
|
spec.add_dependency 'reform', '~> 2.3'
|
84
84
|
spec.add_dependency 'reform-rails', '~> 0.2.0'
|
85
85
|
spec.add_dependency 'retriable', '>= 2.9', '< 4.0'
|
@@ -2,10 +2,13 @@
|
|
2
2
|
# You can manually fill in these values or use the ENV variables.
|
3
3
|
#
|
4
4
|
analytics:
|
5
|
+
ga4:
|
6
|
+
analytics_id: <%= ENV['GOOGLE_ANALYTICS_ID'] %>
|
5
7
|
google:
|
6
8
|
analytics_id: <%= ENV['GOOGLE_ANALYTICS_ID'] %>
|
7
9
|
app_name: <%= ENV['GOOGLE_OAUTH_APP_NAME'] %>
|
8
10
|
app_version: <%= ENV['GOOGLE_OAUTH_APP_VERSION'] %>
|
11
|
+
privkey_value: <%= ENV['GOOGLE_OAUTH_PRIVATE_KEY_VALUE'] %>
|
9
12
|
privkey_path: <%= ENV['GOOGLE_OAUTH_PRIVATE_KEY_PATH'] %>
|
10
13
|
privkey_secret: <%= ENV['GOOGLE_OAUTH_PRIVATE_KEY_SECRET'] %>
|
11
14
|
client_email: <%= ENV['GOOGLE_OAUTH_CLIENT_EMAIL'] %>
|
data/lib/hyrax/version.rb
CHANGED
data/template.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hyrax
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Coyne
|
@@ -14,7 +14,7 @@ authors:
|
|
14
14
|
autorequire:
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
|
-
date:
|
17
|
+
date: 2023-06-27 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: rails
|
@@ -723,6 +723,9 @@ dependencies:
|
|
723
723
|
- - ">="
|
724
724
|
- !ruby/object:Gem::Version
|
725
725
|
version: 0.1.2
|
726
|
+
- - "<"
|
727
|
+
- !ruby/object:Gem::Version
|
728
|
+
version: '2.0'
|
726
729
|
type: :runtime
|
727
730
|
prerelease: false
|
728
731
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -730,6 +733,9 @@ dependencies:
|
|
730
733
|
- - ">="
|
731
734
|
- !ruby/object:Gem::Version
|
732
735
|
version: 0.1.2
|
736
|
+
- - "<"
|
737
|
+
- !ruby/object:Gem::Version
|
738
|
+
version: '2.0'
|
733
739
|
- !ruby/object:Gem::Dependency
|
734
740
|
name: reform
|
735
741
|
requirement: !ruby/object:Gem::Requirement
|
@@ -1527,8 +1533,6 @@ files:
|
|
1527
1533
|
- ".github/SUPPORT.md"
|
1528
1534
|
- ".github/release.yml"
|
1529
1535
|
- ".github/stale.yml"
|
1530
|
-
- ".github/workflows/main.yml"
|
1531
|
-
- ".github/workflows/release.yml"
|
1532
1536
|
- ".gitignore"
|
1533
1537
|
- ".hound.yml"
|
1534
1538
|
- ".regen"
|
@@ -2162,6 +2166,7 @@ files:
|
|
2162
2166
|
- app/services/hyrax/admin_set_member_service.rb
|
2163
2167
|
- app/services/hyrax/admin_set_service.rb
|
2164
2168
|
- app/services/hyrax/analytics.rb
|
2169
|
+
- app/services/hyrax/analytics/ga4.rb
|
2165
2170
|
- app/services/hyrax/analytics/google.rb
|
2166
2171
|
- app/services/hyrax/analytics/google/events.rb
|
2167
2172
|
- app/services/hyrax/analytics/google/events_daily.rb
|
@@ -2435,6 +2440,7 @@ files:
|
|
2435
2440
|
- app/views/hyrax/admin/stats/show.html.erb
|
2436
2441
|
- app/views/hyrax/admin/users/index.html.erb
|
2437
2442
|
- app/views/hyrax/admin/workflow_roles/index.html.erb
|
2443
|
+
- app/views/hyrax/admin/workflows/_tabs.html.erb
|
2438
2444
|
- app/views/hyrax/admin/workflows/index.html.erb
|
2439
2445
|
- app/views/hyrax/base/_actions.html.erb
|
2440
2446
|
- app/views/hyrax/base/_attribute_rows.html.erb
|
@@ -2779,6 +2785,7 @@ files:
|
|
2779
2785
|
- app/views/shared/_citations.html.erb
|
2780
2786
|
- app/views/shared/_footer.html.erb
|
2781
2787
|
- app/views/shared/_ga.html.erb
|
2788
|
+
- app/views/shared/_ga4.html.erb
|
2782
2789
|
- app/views/shared/_locale_picker.html.erb
|
2783
2790
|
- app/views/shared/_matomo.html.erb
|
2784
2791
|
- app/views/shared/_nav_safety_modal.html.erb
|
@@ -3184,7 +3191,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
3184
3191
|
- !ruby/object:Gem::Version
|
3185
3192
|
version: '0'
|
3186
3193
|
requirements: []
|
3187
|
-
rubygems_version: 3.
|
3194
|
+
rubygems_version: 3.4.12
|
3188
3195
|
signing_key:
|
3189
3196
|
specification_version: 4
|
3190
3197
|
summary: Hyrax is a front-end based on the robust Samvera framework, providing a user
|
data/.github/workflows/main.yml
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
name: Trigger Nurax build
|
2
|
-
on:
|
3
|
-
workflow_dispatch:
|
4
|
-
push:
|
5
|
-
|
6
|
-
jobs:
|
7
|
-
trigger:
|
8
|
-
runs-on: ubuntu-latest
|
9
|
-
steps:
|
10
|
-
- uses: peter-evans/repository-dispatch@v1
|
11
|
-
with:
|
12
|
-
token: ${{ secrets.NURAX_ACCESS_TOKEN }}
|
13
|
-
event-type: push
|
14
|
-
repository: curationexperts/nurax
|
15
|
-
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}'
|
16
|
-
|
17
|
-
|
@@ -1,17 +0,0 @@
|
|
1
|
-
name: Trigger Nurax build
|
2
|
-
on:
|
3
|
-
workflow_dispatch:
|
4
|
-
release:
|
5
|
-
|
6
|
-
jobs:
|
7
|
-
trigger:
|
8
|
-
runs-on: ubuntu-latest
|
9
|
-
steps:
|
10
|
-
- uses: peter-evans/repository-dispatch@v1
|
11
|
-
with:
|
12
|
-
token: ${{ secrets.NURAX_ACCESS_TOKEN }}
|
13
|
-
event-type: release
|
14
|
-
repository: curationexperts/nurax
|
15
|
-
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}'
|
16
|
-
|
17
|
-
|