sufia 4.1.0 → 4.2.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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +14 -3
  3. data/SUFIA_VERSION +1 -1
  4. data/app/assets/javascripts/sufia/batch_edit.js +50 -39
  5. data/app/jobs/content_depositor_change_event_job.rb +7 -14
  6. data/app/views/_head_tag_extras.html.erb +1 -0
  7. data/app/views/homepage/_featured.html.erb +1 -1
  8. data/app/views/homepage/_featured_fields.html.erb +1 -1
  9. data/app/views/homepage/_recent_document.html.erb +1 -1
  10. data/app/views/homepage/_tagcloud.html.erb +1 -2
  11. data/app/views/layouts/_head_tag_content.html.erb +11 -3
  12. data/lib/sufia/version.rb +1 -1
  13. data/spec/controllers/generic_files_controller_spec.rb +1 -1
  14. data/spec/jobs/event_jobs_spec.rb +16 -1
  15. data/spec/models/file_download_stat_spec.rb +86 -0
  16. data/spec/models/file_usage_spec.rb +41 -21
  17. data/spec/models/file_view_stat_spec.rb +84 -0
  18. data/spec/support/features.rb +1 -0
  19. data/spec/support/statistic_helper.rb +9 -0
  20. data/sufia-models/app/models/concerns/sufia/file_stat_utils.rb +35 -0
  21. data/sufia-models/app/models/file_download_stat.rb +18 -0
  22. data/sufia-models/app/models/file_usage.rb +6 -35
  23. data/sufia-models/app/models/file_view_stat.rb +18 -0
  24. data/sufia-models/lib/generators/sufia/models/cached_stats_generator.rb +53 -0
  25. data/sufia-models/lib/generators/sufia/models/install_generator.rb +8 -4
  26. data/sufia-models/lib/generators/sufia/models/templates/migrations/create_file_download_stats.rb +12 -0
  27. data/sufia-models/lib/generators/sufia/models/templates/migrations/create_file_view_stats.rb +12 -0
  28. data/sufia-models/lib/sufia/models/version.rb +1 -1
  29. data/sufia-models/sufia-models.gemspec +5 -0
  30. metadata +18 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9889dfdd0876c8615e02e6c0bf89b9a228fd0241
4
- data.tar.gz: 63518b2cae3e6434a98d1688ed1d7ec29cc0fbf5
3
+ metadata.gz: f0becb3e785f413597d7424ec1ed417c1712a740
4
+ data.tar.gz: a7c8c1607bb853f638cf2be54eaf105feeb80cea
5
5
  SHA512:
6
- metadata.gz: 07db0b9e832d7cdfd1bf4b51775f932b29537f3bb32651d06ef1a1eeecc68716350f4d1618ee098d8ca5c43c28c5a1e34759d1fd724e824a88a9ec4732dcf276
7
- data.tar.gz: 46b8e8b75e586c01108c5e5a2456273916dffbaab674f3255735e97c6c4764fbe6b46b90cf37230d5c453fb4e9c422fffab147395f33aedc1be41d297c47e886
6
+ metadata.gz: afe990f4ee1a1b7cef86f77919fcf79808399553de7627057f34bae0f54f3782f89d4a5b9f07267d57222269f5451918493063b43bbc299a39e8d8b929c29739
7
+ data.tar.gz: b708364b4863882be9f7376c83a99d7202d1d9b9784844e550ebac24db019f9228b24fbaf4492e7fd884813ea07fac81b975118940a8b733f5d0da6905798a5b
data/History.md CHANGED
@@ -1,10 +1,21 @@
1
1
  # History of Sufia releases
2
2
 
3
+ ## 4.2.0
4
+
5
+ * Caches google analytics data in the database so we do not have to retrieve them each time the page is loaded [Carolyn Cole]
6
+ * Allows adopters to inject content into the layout's head block, needed by ScholarSphere to add a favicon [Mike Giarlo]
7
+ * Removes redundant title attributes for featured and recent works, fixes orphaned labels [Michael Tribone]
8
+ * Pins mini_magick for rubies < 2.1 [Carolyn Cole]
9
+ * Changes the way we log depositor change events [Mike Giarlo]
10
+ * Breaks cached stats migrations into dedicated generator [Mike Giarlo]
11
+ * Fixes bug with proxy setup in the install generator [Mike Giarlo]
12
+ * Fixes bug in batch editing javascript [Carolyn Cole]
13
+
3
14
  ## 4.1.0
4
15
 
5
- * Adds proxy deposit, "sticky" proxies, and transfers of ownership (from ScholarSphere) [mjgiarlo]
6
- * Fixes bug with form fields attached to single-valued terms [cam156]
7
- * Converts specs to use RSpec 3 style [mjgiarlo]
16
+ * Adds proxy deposit, "sticky" proxies, and transfers of ownership (from ScholarSphere) [Mike Giarlo]
17
+ * Fixes bug with form fields attached to single-valued terms [Carolyn Cole]
18
+ * Converts specs to use RSpec 3 style [Mike Giarlo]
8
19
 
9
20
  ## 4.0.1
10
21
 
@@ -1 +1 @@
1
- 4.1.0
1
+ 4.2.0
@@ -20,9 +20,13 @@ function batch_edit_init () {
20
20
  var Result = {};
21
21
  while (i--) {
22
22
  var Pair = decodeURIComponent(Data[i]).split("=");
23
- var Key = Pair[0];
24
- var Val = Pair[1];
25
- Result[Key] = Val;
23
+ var key = Pair[0];
24
+ var val = Pair[1];
25
+ if (Result[key] != null) {
26
+ if(!$.isArray(Result[key])) Result[key] = [Result[key]];
27
+ Result[key].push(val);
28
+ } else
29
+ Result[key] = val;
26
30
  }
27
31
  return Result;
28
32
  }
@@ -46,54 +50,65 @@ function batch_edit_init () {
46
50
  },
47
51
  run: function () {
48
52
  running = true;
49
- var self = this,
50
- orgSuc;
53
+ var self = this;
51
54
 
52
55
  if (requests.length) {
53
- oriSuc = requests[0].complete;
54
56
 
55
57
  // combine data from multiple requests
56
58
  if (requests.length > 1) {
57
- var data = deserialize(requests[0].data.replace(/\+/g, " "));
58
- form = [requests[0].form]
59
- for (var i = requests.length - 1; i > 0; i--) {
60
- req = requests.pop();
61
- adata = deserialize(req.data.replace(/\+/g, " "));
62
- for (key in Object.keys(adata)) {
63
- curKey = Object.keys(adata)[key];
64
- if (curKey.slice(0, 12) == "generic_file") {
65
- data[curKey] = adata[curKey];
66
- form.push(req.form);
67
- }
68
- }
69
- }
70
- requests[0].data = $.param(data);
71
- requests[0].form = form;
59
+ requests = this.combine_requests(requests);
72
60
  }
73
61
 
74
- requests[0].complete = function () {
75
- if (typeof oriSuc === 'function') oriSuc();
76
- if (typeof requests[0].form === 'object') {
77
- for (f in form) {
78
- form_id = form[f];
79
- after_ajax(form_id);
80
- }
81
- }
82
- requests.shift();
83
- self.run.apply(self, []);
84
- };
85
-
62
+ requests = this.setup_request_complete(requests);
86
63
  $.ajax(requests[0]);
87
64
  } else {
88
65
  self.tid = setTimeout(function () {
89
66
  self.run.apply(self, []);
90
67
  }, 500);
68
+ running = false;
91
69
  }
92
- running = false;
93
70
  },
94
71
  stop: function () {
95
72
  requests = [];
96
73
  clearTimeout(this.tid);
74
+ },
75
+ setup_request_complete: function (requests) {
76
+ oriComp = requests[0].complete;
77
+
78
+ requests[0].complete = [ function (e) {
79
+ req = requests.shift();
80
+ if (typeof req.form === 'object') {
81
+ for (f in req.form) {
82
+ form_id = form[f];
83
+ after_ajax(form_id);
84
+ }
85
+ }
86
+ this.tid = setTimeout(function () {
87
+ ajaxManager.run.apply(ajaxManager, []);
88
+ }, 50);
89
+ return true;
90
+ }];
91
+ if (typeof oriComp === 'function') requests[0].complete.push(oriComp);
92
+ return requests;
93
+ },
94
+ combine_requests: function (requests) {
95
+ var data = deserialize(requests[0].data.replace(/\+/g, " "));
96
+ form = [requests[0].form]
97
+ for (var i = requests.length - 1; i > 0; i--) {
98
+ req = requests.pop();
99
+ adata = deserialize(req.data.replace(/\+/g, " "));
100
+
101
+ for (key in Object.keys(adata)) {
102
+ curKey = Object.keys(adata)[key];
103
+ if (curKey.slice(0, 12) == "generic_file") {
104
+ data[curKey] = adata[curKey];
105
+ form.push(req.form);
106
+ }
107
+ }
108
+ }
109
+ requests[0].data = $.param(data);
110
+ requests[0].form = form;
111
+ return requests;
97
112
  }
98
113
  };
99
114
  }());
@@ -135,11 +150,7 @@ function batch_edit_init () {
135
150
  dataType: "json",
136
151
  type: form.attr("method").toUpperCase(),
137
152
  data: form.serialize(),
138
- success: function (e) {
139
- eval(e.responseText);
140
- after_ajax(form_id);
141
- },
142
- error: function (e) {
153
+ complete: function (e) {
143
154
  after_ajax(form_id);
144
155
  if (e.status == 200) {
145
156
  eval(e.responseText);
@@ -23,7 +23,7 @@ class ContentDepositorChangeEventJob < EventJob
23
23
  file.apply_depositor_metadata(login)
24
24
  file.save!
25
25
 
26
- action = "User #{link_to_profile file.proxy_depositor} has transferred #{link_to file.title.first, Sufia::Engine.routes.url_helpers.generic_file_path(file.noid)} to User #{link_to_profile login}"
26
+ action = "User #{link_to_profile file.proxy_depositor} has transferred #{link_to file.title.first, Sufia::Engine.routes.url_helpers.generic_file_path(file.noid)} to user #{link_to_profile login}"
27
27
  timestamp = Time.now.to_i
28
28
  depositor = ::User.find_by_user_key(file.depositor)
29
29
  proxy_depositor = ::User.find_by_user_key(file.proxy_depositor)
@@ -31,19 +31,12 @@ class ContentDepositorChangeEventJob < EventJob
31
31
  event = proxy_depositor.create_event(action, timestamp)
32
32
  # Log the event to the GF's stream
33
33
  file.log_event(event)
34
-
35
- #log the event to the depositor
36
- log_depositor_event(event, depositor, file)
37
-
38
- #log the event to the proxy_depositor
39
- log_depositor_event(event, proxy_depositor, file)
40
- end
41
-
42
- def log_depositor_event(event, depositor, gf)
43
- # Log the event to the depositor's profile stream
44
- depositor.log_profile_event(event)
45
- # Fan out the event to all followers who have access
46
- depositor.followers.select { |user| user.can? :read, gf }.each do |follower|
34
+ # log the event to the proxy depositor's profile
35
+ proxy_depositor.log_profile_event(event)
36
+ # log the event to the depositor's dashboard
37
+ depositor.log_event(event)
38
+ # Fan out the event to the depositor's followers who have access
39
+ depositor.followers.select { |user| user.can? :read, file }.each do |follower|
47
40
  follower.log_event(event)
48
41
  end
49
42
  end
@@ -0,0 +1 @@
1
+ <%# Override this partial in your application to insert extra HEAD content into every page %>
@@ -2,7 +2,7 @@
2
2
  <li class="featured-item" data-id="<%= solr_document.noid %>">
3
3
  <div class="main row">
4
4
  <div class="col-sm-3">
5
- <%= link_to sufia.generic_file_path(solr_document) do %>
5
+ <%= link_to sufia.generic_file_path(solr_document), "aria-hidden" => true do %>
6
6
  <%= render_thumbnail_tag solr_document, {width: 90} %>
7
7
  <% end %>
8
8
  </div>
@@ -1,6 +1,6 @@
1
1
  <h3>
2
2
  <span class="sr-only">Title</span>
3
- <%= link_to truncate(featured.title_or_label, length: 28, separator: ' '), sufia.generic_file_path(featured), title: featured.title_or_label %>
3
+ <%= link_to truncate(featured.title_or_label, length: 28, separator: ' '), sufia.generic_file_path(featured) %>
4
4
  </h3>
5
5
  <div>
6
6
  <span class="sr-only">Depositor</span>
@@ -9,7 +9,7 @@
9
9
  <% end %>
10
10
  <td>
11
11
  <h3>
12
- <span class="sr-only">Title</span><%= link_to truncate(recent_document.title_or_label, length: 28, separator: ' '), sufia.generic_file_path(recent_document.noid), title: recent_document.title_or_label %>
12
+ <span class="sr-only">Title</span><%= link_to truncate(recent_document.title_or_label, length: 28, separator: ' '), sufia.generic_file_path(recent_document.noid) %>
13
13
  <% if display_access %>
14
14
  <% if recent_document.registered? %>
15
15
  <span class="label label-info" title="<%=t('sufia.institution_name') %>"><%=t('sufia.institution_name') %></span>
@@ -1,6 +1,5 @@
1
1
  <a role="button" aria-hidden="true" class="btn btn-default tag-toggle-list">List</a>
2
- <label for="tag-cloud" class="sr-only">Sort By</label>
3
- <div id="tag-cloud" class="btn-group btn-group-justified tag-sort">
2
+ <div id="tag-cloud" class="btn-group btn-group-justified tag-sort" aria-label="Sort By">
4
3
  <a role="button" class="btn btn-default tag-sort-za">Z-A</a>
5
4
  <a role="button" class="btn btn-default tag-sort-az">A-Z</a>
6
5
  <a role="button" class="btn btn-default tag-sort-numerical">Rank</a>
@@ -2,18 +2,26 @@
2
2
  <link href='https://fonts.googleapis.com/css?family=Lato:300,400' rel='stylesheet' type='text/css'>
3
3
  <meta charset="utf-8" />
4
4
 
5
- <!-- added for use on small devices like phones" -->
5
+ <!-- added for use on small devices like phones -->
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
7
7
 
8
- <%= yield :twitter_meta if content_for?(:twitter_meta) %>
9
- <%= yield :gscholar_meta if content_for?(:gscholar_meta) %>
8
+ <!-- Twitter card metadata -->
9
+ <%= yield :twitter_meta %>
10
+ <!-- Google Scholar metadata -->
11
+ <%= yield :gscholar_meta %>
10
12
 
11
13
  <title><%= h(@page_title || application_name) %></title>
14
+
12
15
  <!-- application css -->
13
16
  <%= stylesheet_link_tag 'application' %>
14
17
  <%= yield(:css_head) %>
15
18
 
16
19
  <!-- application js -->
17
20
  <%= javascript_include_tag 'application' %>
21
+ <%= yield(:js_head) %>
18
22
 
23
+ <!-- Google Analytics -->
19
24
  <%= render partial: '/ga', formats: [:html] %>
25
+
26
+ <!-- for extras, e.g., a favicon -->
27
+ <%= render partial: '/head_tag_extras', formats: [:html] %>
@@ -1,3 +1,3 @@
1
1
  module Sufia
2
- VERSION = "4.1.0"
2
+ VERSION = "4.2.0"
3
3
  end
@@ -304,7 +304,7 @@ describe GenericFilesController, :type => :controller do
304
304
 
305
305
  download_query = double('query')
306
306
  allow(download_query).to receive(:for_file).and_return([
307
- OpenStruct.new(eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:123456789", totalEvents: "3")
307
+ OpenStruct.new(date: Date.today.strftime("%Y%m%d"), eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:123456789", totalEvents: "3")
308
308
  ])
309
309
  allow(download_query).to receive(:map).and_return(download_query.for_file.map(&:marshal_dump))
310
310
  allow(profile).to receive(:sufia__download).and_return(download_query)
@@ -86,6 +86,22 @@ describe 'event jobs' do
86
86
  expect(@gf.events.length).to eq(1)
87
87
  expect(@gf.events.first).to eq(event)
88
88
  end
89
+ it "logs content depositor change events" do
90
+ # ContentDepositorChange should log the event to the proxy depositor's profile, the depositor's dashboard, followers' dashboards, and the GF
91
+ @third_user.follow(@another_user)
92
+ allow_any_instance_of(User).to receive(:can?).and_return(true)
93
+ allow(Time).to receive(:now).at_least(:once).and_return(1)
94
+ event = {action: 'User <a href="/users/jilluser@example-dot-com">jilluser@example.com</a> has transferred <a href="/files/123">Hamlet</a> to user <a href="/users/archivist1@example-dot-com">archivist1@example.com</a>', timestamp: '1' }
95
+ ContentDepositorChangeEventJob.new('test:123', @another_user.user_key).run
96
+ expect(@user.profile_events.length).to eq(1)
97
+ expect(@user.profile_events.first).to eq(event)
98
+ expect(@another_user.events.length).to eq(1)
99
+ expect(@another_user.events.first).to eq(event)
100
+ expect(@third_user.events.length).to eq(1)
101
+ expect(@third_user.events.first).to eq(event)
102
+ expect(@gf.events.length).to eq(1)
103
+ expect(@gf.events.first).to eq(event)
104
+ end
89
105
  it "should log content update events" do
90
106
  # ContentUpdate should log the event to the depositor's profile, followers' dashboards, and the GF
91
107
  @another_user.follow(@user)
@@ -188,4 +204,3 @@ describe 'event jobs' do
188
204
  expect(@gf.events.first).to eq(event)
189
205
  end
190
206
  end
191
-
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe FileDownloadStat, :type => :model do
4
+ let (:file_id) {"99"}
5
+ let (:date) {DateTime.new}
6
+ let (:file_stat) {FileDownloadStat.create(downloads:"2", date: date, file_id: file_id)}
7
+
8
+ it "has attributes" do
9
+ expect(file_stat).to respond_to(:downloads)
10
+ expect(file_stat).to respond_to(:date)
11
+ expect(file_stat).to respond_to(:file_id)
12
+ expect(file_stat.file_id).to eq("99")
13
+ expect(file_stat.date).to eq(date)
14
+ expect(file_stat.downloads).to eq(2)
15
+ end
16
+
17
+
18
+ describe "#get_float_statistics" do
19
+
20
+ let(:dates) {
21
+ ldates = []
22
+ 4.downto(0) {|idx| ldates << (Date.today-idx.day) }
23
+ ldates
24
+ }
25
+ let(:date_strs) {
26
+ dates.map {|date| date.strftime("%Y%m%d") }
27
+ }
28
+
29
+ let(:download_output) {
30
+ [[statistic_date(dates[0]), 1], [statistic_date(dates[1]), 1], [statistic_date(dates[2]), 2], [statistic_date(dates[3]), 3]]
31
+ }
32
+
33
+ # This is what the data looks like that's returned from Google Analytics (GA) via the Legato gem
34
+ # Due to the nature of querying GA, testing this data in an automated fashion is problematc.
35
+ # Sample data structures were created by sending real events to GA from a test instance of
36
+ # Scholarsphere. The data below are essentially a "cut and paste" from the output of query
37
+ # results from the Legato gem.
38
+ let(:sample_download_statistics) {
39
+ [
40
+ OpenStruct.new(eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:x920fw85p", date: date_strs[0], totalEvents: "1"),
41
+ OpenStruct.new(eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:x920fw85p", date: date_strs[1], totalEvents: "1"),
42
+ OpenStruct.new(eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:x920fw85p", date: date_strs[2], totalEvents: "2"),
43
+ OpenStruct.new(eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:x920fw85p", date: date_strs[3], totalEvents: "3"),
44
+ #OpenStruct.new(eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:x920fw85p", date: date_strs[4], totalEvents: "5"),
45
+ ]
46
+ }
47
+
48
+ describe "cache empty" do
49
+ let (:stats) {
50
+ expect(FileDownloadStat).to receive(:ga_statistics).and_return(sample_download_statistics)
51
+ FileDownloadStat.statistics(file_id,Date.today-4.day)
52
+ }
53
+
54
+ it "includes cached ga data" do
55
+ expect(FileDownloadStat.to_flots stats).to include(*download_output)
56
+ end
57
+
58
+ it "caches data" do
59
+ expect(FileDownloadStat.to_flots stats).to include(*download_output)
60
+
61
+ # at this point all data should be cached
62
+ allow(FileDownloadStat).to receive(:ga_statistics).with(Date.today, file_id).and_raise("We should not call Google Analytics All data should be cached!")
63
+
64
+ stats2 = FileDownloadStat.statistics(file_id,Date.today-4.day)
65
+ expect(FileDownloadStat.to_flots stats2).to include(*download_output)
66
+ end
67
+
68
+ end
69
+
70
+ describe "cache loaded" do
71
+
72
+ let!(:file_download_stat) { FileDownloadStat.create(date: (Date.today-5.day).to_datetime, file_id: file_id, downloads:"25")}
73
+
74
+ let (:stats) {
75
+ expect(FileDownloadStat).to receive(:ga_statistics).and_return(sample_download_statistics)
76
+ FileDownloadStat.statistics(file_id,Date.today-5.day)
77
+ }
78
+
79
+ it "includes cached data" do
80
+ expect(FileDownloadStat.to_flots stats).to include([file_download_stat.date.to_i*1000,file_download_stat.downloads],*download_output)
81
+ end
82
+
83
+ end
84
+ end
85
+
86
+ end
@@ -12,6 +12,25 @@ describe FileUsage, :type => :model do
12
12
  @file.delete
13
13
  end
14
14
 
15
+ let(:dates) {
16
+ ldates = []
17
+ 4.downto(0) {|idx| ldates << (Date.today-idx.day) }
18
+ ldates
19
+ }
20
+ let(:date_strs) {
21
+ ldate_strs = []
22
+ dates.each {|date| ldate_strs << date.strftime("%Y%m%d") }
23
+ ldate_strs
24
+ }
25
+
26
+ let(:view_output) {
27
+ [[statistic_date(dates[0]), 4], [statistic_date(dates[1]), 8], [statistic_date(dates[2]), 6], [statistic_date(dates[3]), 10], [statistic_date(dates[4]), 2]]
28
+ }
29
+
30
+ let(:download_output) {
31
+ [[statistic_date(dates[0]), 1], [statistic_date(dates[1]), 1], [statistic_date(dates[2]), 2], [statistic_date(dates[3]), 3], [statistic_date(dates[4]), 5]]
32
+ }
33
+
15
34
  # This is what the data looks like that's returned from Google Analytics (GA) via the Legato gem
16
35
  # Due to the nature of querying GA, testing this data in an automated fashion is problematc.
17
36
  # Sample data structures were created by sending real events to GA from a test instance of
@@ -20,27 +39,28 @@ describe FileUsage, :type => :model do
20
39
 
21
40
  let(:sample_download_statistics) {
22
41
  [
23
- OpenStruct.new(eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:x920fw85p", date: "20140101", totalEvents: "1"),
24
- OpenStruct.new(eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:x920fw85p", date: "20140102", totalEvents: "1"),
25
- OpenStruct.new(eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:x920fw85p", date: "20140103", totalEvents: "2"),
26
- OpenStruct.new(eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:x920fw85p", date: "20140104", totalEvents: "3"),
27
- OpenStruct.new(eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:x920fw85p", date: "20140105", totalEvents: "5"),
42
+ OpenStruct.new(eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:x920fw85p", date: date_strs[0], totalEvents: "1"),
43
+ OpenStruct.new(eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:x920fw85p", date: date_strs[1], totalEvents: "1"),
44
+ OpenStruct.new(eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:x920fw85p", date: date_strs[2], totalEvents: "2"),
45
+ OpenStruct.new(eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:x920fw85p", date: date_strs[3], totalEvents: "3"),
46
+ OpenStruct.new(eventCategory: "Files", eventAction: "Downloaded", eventLabel: "sufia:x920fw85p", date: date_strs[4], totalEvents: "5"),
28
47
  ]
29
48
  }
30
49
 
31
50
  let(:sample_pageview_statistics) {
32
51
  [
33
- OpenStruct.new(date: '20140101', pageviews: 4),
34
- OpenStruct.new(date: '20140102', pageviews: 8),
35
- OpenStruct.new(date: '20140103', pageviews: 6),
36
- OpenStruct.new(date: '20140104', pageviews: 10),
37
- OpenStruct.new(date: '20140105', pageviews: 2)
52
+ OpenStruct.new(date: date_strs[0], pageviews: 4),
53
+ OpenStruct.new(date: date_strs[1], pageviews: 8),
54
+ OpenStruct.new(date: date_strs[2], pageviews: 6),
55
+ OpenStruct.new(date: date_strs[3], pageviews: 10),
56
+ OpenStruct.new(date: date_strs[4], pageviews: 2)
38
57
  ]
39
58
  }
40
59
 
41
60
  let(:usage) {
42
- allow_any_instance_of(FileUsage).to receive(:download_statistics).and_return(sample_download_statistics)
43
- allow_any_instance_of(FileUsage).to receive(:pageview_statistics).and_return(sample_pageview_statistics)
61
+ allow_any_instance_of(GenericFile).to receive(:create_date).and_return((Date.today-4.day).to_s)
62
+ expect(FileDownloadStat).to receive(:ga_statistics).and_return(sample_download_statistics)
63
+ expect(FileViewStat).to receive(:ga_statistics).and_return(sample_pageview_statistics)
44
64
  FileUsage.new(@file.id)
45
65
  }
46
66
 
@@ -82,8 +102,8 @@ describe FileUsage, :type => :model do
82
102
  it "should return an array of hashes for use with JQuery Flot" do
83
103
  expect(usage.to_flot[0][:label]).to eq("Pageviews")
84
104
  expect(usage.to_flot[1][:label]).to eq("Downloads")
85
- expect(usage.to_flot[0][:data]).to include([1388534400000, 4], [1388620800000, 8], [1388707200000, 6], [1388793600000, 10], [1388880000000, 2])
86
- expect(usage.to_flot[1][:data]).to include([1388534400000, 1], [1388620800000, 1], [1388707200000, 2], [1388793600000, 3], [1388880000000, 5])
105
+ expect(usage.to_flot[0][:data]).to include(*view_output)
106
+ expect(usage.to_flot[1][:data]).to include(*download_output)
87
107
  end
88
108
 
89
109
  let(:create_date) {DateTime.new(2014, 01, 01)}
@@ -100,8 +120,8 @@ describe FileUsage, :type => :model do
100
120
  describe "create date before earliest date set" do
101
121
  let(:usage) {
102
122
  allow_any_instance_of(GenericFile).to receive(:create_date).and_return(create_date.to_s)
103
- allow_any_instance_of(FileUsage).to receive(:download_statistics).and_return(sample_download_statistics)
104
- allow_any_instance_of(FileUsage).to receive(:pageview_statistics).and_return(sample_pageview_statistics)
123
+ expect(FileDownloadStat).to receive(:ga_statistics).and_return(sample_download_statistics)
124
+ expect(FileViewStat).to receive(:ga_statistics).and_return(sample_pageview_statistics)
105
125
  FileUsage.new(@file.id)
106
126
  }
107
127
  it "should set the created date to the earliest date not the created date" do
@@ -112,8 +132,9 @@ describe FileUsage, :type => :model do
112
132
 
113
133
  describe "create date after earliest" do
114
134
  let(:usage) {
115
- allow_any_instance_of(FileUsage).to receive(:download_statistics).and_return(sample_download_statistics)
116
- allow_any_instance_of(FileUsage).to receive(:pageview_statistics).and_return(sample_pageview_statistics)
135
+ allow_any_instance_of(GenericFile).to receive(:create_date).and_return((Date.today-4.day).to_s)
136
+ expect(FileDownloadStat).to receive(:ga_statistics).and_return(sample_download_statistics)
137
+ expect(FileViewStat).to receive(:ga_statistics).and_return(sample_pageview_statistics)
117
138
  Sufia.config.analytic_start_date = earliest
118
139
  FileUsage.new(@file.id)
119
140
  }
@@ -129,8 +150,8 @@ describe FileUsage, :type => :model do
129
150
 
130
151
  let(:usage) {
131
152
  allow_any_instance_of(GenericFile).to receive(:create_date).and_return(create_date.to_s)
132
- allow_any_instance_of(FileUsage).to receive(:download_statistics).and_return(sample_download_statistics)
133
- allow_any_instance_of(FileUsage).to receive(:pageview_statistics).and_return(sample_pageview_statistics)
153
+ expect(FileDownloadStat).to receive(:ga_statistics).and_return(sample_download_statistics)
154
+ expect(FileViewStat).to receive(:ga_statistics).and_return(sample_pageview_statistics)
134
155
  FileUsage.new(@file.id)
135
156
  }
136
157
  it "should set the created date to the earliest date not the created date" do
@@ -139,5 +160,4 @@ describe FileUsage, :type => :model do
139
160
 
140
161
  end
141
162
  end
142
-
143
163
  end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe FileViewStat, :type => :model do
4
+ let (:file_id) {"99"}
5
+ let (:date) {DateTime.new}
6
+ let (:file_stat) {FileViewStat.create(views:"25", date: date, file_id: file_id)}
7
+
8
+ it "has attributes" do
9
+ expect(file_stat).to respond_to(:views)
10
+ expect(file_stat).to respond_to(:date)
11
+ expect(file_stat).to respond_to(:file_id)
12
+ expect(file_stat.file_id).to eq("99")
13
+ expect(file_stat.date).to eq(date)
14
+ expect(file_stat.views).to eq(25)
15
+ end
16
+
17
+ describe "#get_float_statistics" do
18
+
19
+ let(:dates) {
20
+ ldates = []
21
+ 4.downto(0) {|idx| ldates << (Date.today-idx.day) }
22
+ ldates
23
+ }
24
+ let(:date_strs) {
25
+ dates.map {|date| date.strftime("%Y%m%d") }
26
+ }
27
+
28
+ let(:view_output) {
29
+ [[statistic_date(dates[0]), 4], [statistic_date(dates[1]), 8], [statistic_date(dates[2]), 6], [statistic_date(dates[3]), 10]]
30
+ }
31
+
32
+ # This is what the data looks like that's returned from Google Analytics (GA) via the Legato gem
33
+ # Due to the nature of querying GA, testing this data in an automated fashion is problematc.
34
+ # Sample data structures were created by sending real events to GA from a test instance of
35
+ # Scholarsphere. The data below are essentially a "cut and paste" from the output of query
36
+ # results from the Legato gem.
37
+ let(:sample_pageview_statistics) {
38
+ [
39
+ OpenStruct.new(date: date_strs[0], pageviews: 4),
40
+ OpenStruct.new(date: date_strs[1], pageviews: 8),
41
+ OpenStruct.new(date: date_strs[2], pageviews: 6),
42
+ OpenStruct.new(date: date_strs[3], pageviews: 10),
43
+ #OpenStruct.new(date: date_strs[4], pageviews: 2)
44
+ ]
45
+ }
46
+ describe "cache empty" do
47
+ let (:stats) {
48
+ expect(FileViewStat).to receive(:ga_statistics).and_return(sample_pageview_statistics)
49
+ FileViewStat.statistics(file_id,Date.today-4.day)
50
+ }
51
+
52
+ it "includes cached ga data" do
53
+ expect(FileViewStat.to_flots stats).to include(*view_output)
54
+ end
55
+
56
+ it "caches data" do
57
+ expect(FileViewStat.to_flots stats).to include(*view_output)
58
+
59
+ # at this point all data should be cached
60
+ allow(FileViewStat).to receive(:ga_statistics).with(Date.today, file_id).and_raise("We should not call Google Analytics All data should be cached!")
61
+
62
+ stats2 = FileViewStat.statistics(file_id,Date.today-5.day)
63
+ expect(FileViewStat.to_flots stats2).to include(*view_output)
64
+ end
65
+
66
+ end
67
+
68
+ describe "cache loaded" do
69
+
70
+ let!(:file_view_stat) { FileViewStat.create(date: (Date.today-5.day).to_datetime, file_id: file_id, views:"25")}
71
+
72
+ let (:stats) {
73
+ expect(FileViewStat).to receive(:ga_statistics).and_return(sample_pageview_statistics)
74
+ FileViewStat.statistics(file_id,Date.today-5.day)
75
+ }
76
+
77
+ it "includes cached data" do
78
+ expect(FileViewStat.to_flots stats).to include([file_view_stat.date.to_i*1000,file_view_stat.views],*view_output)
79
+ end
80
+
81
+ end
82
+ end
83
+ end
84
+
@@ -6,6 +6,7 @@ require File.expand_path('../proxies', __FILE__)
6
6
  require File.expand_path('../locations', __FILE__)
7
7
  require File.expand_path('../poltergeist', __FILE__)
8
8
  require File.expand_path('../cleaner', __FILE__)
9
+ require File.expand_path('../statistic_helper', __FILE__)
9
10
 
10
11
  RSpec.configure do |config|
11
12
  config.include Features::SessionHelpers, type: :feature
@@ -0,0 +1,9 @@
1
+ module StatisticHelper
2
+ def statistic_date(date)
3
+ date.to_datetime.to_i*1000
4
+ end
5
+
6
+ RSpec.configure do |config|
7
+ config.include StatisticHelper
8
+ end
9
+ end
@@ -0,0 +1,35 @@
1
+ module Sufia
2
+ module FileStatUtils
3
+
4
+ def to_flots stats
5
+ stats.map {|stat| stat.to_flot}
6
+ end
7
+
8
+ def convert_date date_time
9
+ date_time.to_datetime.to_i * 1000
10
+ end
11
+
12
+ private
13
+
14
+ def cached_stats(file_id, start_date, method)
15
+ stats = self.where(file_id:file_id).order(date: :asc)
16
+ ga_start_date = stats.size > 0 ? stats[stats.size-1].date + 1.day : start_date.to_date
17
+ {ga_start_date: ga_start_date, cached_stats: stats.to_a }
18
+ end
19
+
20
+ def combined_stats file_id, start_date, object_method, ga_key
21
+ stat_cache_info = cached_stats( file_id, start_date, object_method)
22
+ stats = stat_cache_info[:cached_stats]
23
+ if stat_cache_info[:ga_start_date] < Date.today
24
+ ga_stats = ga_statistics(stat_cache_info[:ga_start_date], file_id)
25
+ ga_stats.each do |stat|
26
+ lstat = self.new file_id:file_id, date: stat[:date], object_method => stat[ga_key]
27
+ lstat.save unless Date.parse(stat[:date]) == Date.today
28
+ stats << lstat
29
+ end
30
+ end
31
+ stats
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ class FileDownloadStat < ActiveRecord::Base
2
+ extend Sufia::FileStatUtils
3
+
4
+ def to_flot
5
+ [ self.class.convert_date(date), downloads ]
6
+ end
7
+
8
+ def self.statistics file_id, start_date
9
+ combined_stats file_id, start_date, :downloads, :totalEvents
10
+ end
11
+
12
+ # Sufia::Download is sent to Sufia::Analytics.profile as #sufia__download
13
+ # see Legato::ProfileMethods.method_name_from_klass
14
+ def self.ga_statistics start_date, file_id
15
+ Sufia::Analytics.profile.sufia__download(sort: 'date', start_date: start_date, end_date: Date.yesterday).for_file(file_id)
16
+ end
17
+
18
+ end
@@ -8,52 +8,23 @@ class FileUsage
8
8
  earliest = Sufia.config.analytic_start_date
9
9
  self.created = DateTime.parse(::GenericFile.find(id).create_date)
10
10
  self.created = earliest > created ? earliest : created unless earliest.blank?
11
- self.downloads = download_statistics
12
- self.pageviews = pageview_statistics
11
+ self.downloads = FileDownloadStat.to_flots FileDownloadStat.statistics(id, created)
12
+ self.pageviews = FileViewStat.to_flots FileViewStat.statistics(id, created)
13
13
  end
14
14
 
15
15
  def total_downloads
16
- self.downloads.map(&:marshal_dump).reduce(0) { |total, result| total + result[:totalEvents].to_i }
16
+ self.downloads.reduce(0) { |total, result| total + result[1].to_i }
17
17
  end
18
18
 
19
19
  def total_pageviews
20
- self.pageviews.map(&:marshal_dump).reduce(0) { |total, result| total + result[:pageviews].to_i }
20
+ self.pageviews.reduce(0) { |total, result| total + result[1].to_i }
21
21
  end
22
22
 
23
23
  # Package data for visualization using JQuery Flot
24
24
  def to_flot
25
25
  [
26
- { label: "Pageviews", data: pageviews_to_flot },
27
- { label: "Downloads", data: downloads_to_flot }
26
+ { label: "Pageviews", data: pageviews },
27
+ { label: "Downloads", data: downloads }
28
28
  ]
29
29
  end
30
-
31
- private
32
-
33
- # Sufia::Download is sent to Sufia::Analytics.profile as #sufia__download
34
- # see Legato::ProfileMethods.method_name_from_klass
35
- def download_statistics
36
- Sufia::Analytics.profile.sufia__download(sort: 'date', start_date: created).for_file(self.id)
37
- end
38
-
39
- # Sufia::Pageview is sent to Sufia::Analytics.profile as #sufia__pageview
40
- # see Legato::ProfileMethods.method_name_from_klass
41
- def pageview_statistics
42
- Sufia::Analytics.profile.sufia__pageview(sort: 'date', start_date: created).for_path(self.path)
43
- end
44
-
45
- def pageviews_to_flot values = Array.new
46
- self.pageviews.map(&:marshal_dump).map do |result_hash|
47
- values << [ (Date.parse(result_hash[:date]).to_time.to_i * 1000), result_hash[:pageviews].to_i ]
48
- end
49
- return values
50
- end
51
-
52
- def downloads_to_flot values = Array.new
53
- self.downloads.map(&:marshal_dump).map do |result_hash|
54
- values << [ (Date.parse(result_hash[:date]).to_time.to_i * 1000), result_hash[:totalEvents].to_i ]
55
- end
56
- return values
57
- end
58
-
59
30
  end
@@ -0,0 +1,18 @@
1
+ class FileViewStat < ActiveRecord::Base
2
+ extend Sufia::FileStatUtils
3
+
4
+ def to_flot
5
+ [ self.class.convert_date(date), views ]
6
+ end
7
+
8
+ def self.statistics file_id, start_date
9
+ combined_stats file_id, start_date, :views, :pageviews
10
+ end
11
+
12
+ # Sufia::Download is sent to Sufia::Analytics.profile as #sufia__download
13
+ # see Legato::ProfileMethods.method_name_from_klass
14
+ def self.ga_statistics start_date, file_id
15
+ path = Sufia::Engine.routes.url_helpers.generic_file_path(Sufia::Noid.noidify(file_id))
16
+ Sufia::Analytics.profile.sufia__pageview(sort: 'date', start_date: start_date).for_path(path)
17
+ end
18
+ end
@@ -0,0 +1,53 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'rails/generators'
3
+ require 'rails/generators/migration'
4
+
5
+ class Sufia::Models::CachedStatsGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ source_root File.expand_path('../templates', __FILE__)
9
+
10
+ desc """
11
+ This generator adds the ability to cache usage stats to your application:
12
+ 1. Creates several database migrations if they do not exist in /db/migrate
13
+ """
14
+ # Implement the required interface for Rails::Generators::Migration.
15
+ # taken from http://github.com/rails/rails/blob/master/activerecord/lib/generators/active_record.rb
16
+ def self.next_migration_number(path)
17
+ if @prev_migration_nr
18
+ @prev_migration_nr += 1
19
+ else
20
+ if last_migration = Dir[File.join(path, '*.rb')].sort.last
21
+ @prev_migration_nr = last_migration.sub(File.join(path, '/'), '').to_i + 1
22
+ else
23
+ @prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
24
+ end
25
+ end
26
+ @prev_migration_nr.to_s
27
+ end
28
+
29
+ def banner
30
+ say_status("warning", "ADDING STATS CACHING-RELATED SUFIA MODELS", :yellow)
31
+ end
32
+
33
+ # Setup the database migrations
34
+ def copy_migrations
35
+ # Can't get this any more DRY, because we need this order.
36
+ [
37
+ 'create_file_view_stats.rb',
38
+ 'create_file_download_stats.rb'
39
+ ].each do |file|
40
+ better_migration_template file
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def better_migration_template(file)
47
+ begin
48
+ migration_template "migrations/#{file}", "db/migrate/#{file}"
49
+ rescue Rails::Generators::Error => e
50
+ say_status("warning", e.message, :yellow)
51
+ end
52
+ end
53
+ end
@@ -18,6 +18,7 @@ This generator makes the following changes to your application:
18
18
  6. Installs Blacklight gallery
19
19
  7. Runs full-text generator
20
20
  8. Runs proxies generator
21
+ 9. Runs cached stats generator
21
22
  """
22
23
 
23
24
  # Implement the required interface for Rails::Generators::Migration.
@@ -56,9 +57,7 @@ This generator makes the following changes to your application:
56
57
  'add_linkedin_to_users.rb',
57
58
  'create_tinymce_assets.rb',
58
59
  'create_content_blocks.rb',
59
- 'create_featured_works.rb',
60
- 'create_proxy_deposit_requests.rb',
61
- 'create_proxy_deposit_rights.rb'
60
+ 'create_featured_works.rb'
62
61
  ].each do |file|
63
62
  better_migration_template file
64
63
  end
@@ -106,10 +105,15 @@ This generator makes the following changes to your application:
106
105
  end
107
106
 
108
107
  # Sets up proxies and transfers
109
- def full_text_indexing
108
+ def proxies
110
109
  generate "sufia:models:proxies"
111
110
  end
112
111
 
112
+ # Sets up cached usage stats
113
+ def cached_stats
114
+ generate 'sufia:models:cached_stats'
115
+ end
116
+
113
117
  private
114
118
 
115
119
  def better_migration_template(file)
@@ -0,0 +1,12 @@
1
+ class CreateFileDownloadStats < ActiveRecord::Migration
2
+ def change
3
+ create_table :file_download_stats do |t|
4
+ t.datetime :date
5
+ t.integer :downloads
6
+ t.string :file_id
7
+
8
+ t.timestamps
9
+ end
10
+ add_index :file_download_stats, :file_id
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ class CreateFileViewStats < ActiveRecord::Migration
2
+ def change
3
+ create_table :file_view_stats do |t|
4
+ t.datetime :date
5
+ t.integer :views
6
+ t.string :file_id
7
+
8
+ t.timestamps
9
+ end
10
+ add_index :file_view_stats, :file_id
11
+ end
12
+ end
@@ -1,5 +1,5 @@
1
1
  module Sufia
2
2
  module Models
3
- VERSION = "4.1.0"
3
+ VERSION = "4.2.0"
4
4
  end
5
5
  end
@@ -42,4 +42,9 @@ Gem::Specification.new do |spec|
42
42
  spec.add_dependency 'google-api-client', '~> 0.7'
43
43
  spec.add_dependency 'legato', '~> 0.3'
44
44
  spec.add_dependency 'activerecord-import', '~> 0.5'
45
+ if RUBY_VERSION < '2.1.0'
46
+ spec.add_dependency 'mini_magick', '< 4'
47
+ else
48
+ spec.add_dependency 'mini_magick'
49
+ end
45
50
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sufia
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.0
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-10-31 00:00:00.000000000 Z
12
+ date: 2014-11-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sufia-models
@@ -17,14 +17,14 @@ dependencies:
17
17
  requirements:
18
18
  - - '='
19
19
  - !ruby/object:Gem::Version
20
- version: 4.1.0
20
+ version: 4.2.0
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - '='
26
26
  - !ruby/object:Gem::Version
27
- version: 4.1.0
27
+ version: 4.2.0
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: blacklight_advanced_search
30
30
  requirement: !ruby/object:Gem::Requirement
@@ -629,6 +629,7 @@ files:
629
629
  - app/views/_flash_msg.html.erb
630
630
  - app/views/_footer.html.erb
631
631
  - app/views/_ga.html.erb
632
+ - app/views/_head_tag_extras.html.erb
632
633
  - app/views/_logo.html.erb
633
634
  - app/views/_masthead.html.erb
634
635
  - app/views/_user_util_links.html.erb
@@ -977,7 +978,9 @@ files:
977
978
  - spec/models/featured_work_list_spec.rb
978
979
  - spec/models/featured_work_spec.rb
979
980
  - spec/models/file_content_datastream_spec.rb
981
+ - spec/models/file_download_stat_spec.rb
980
982
  - spec/models/file_usage_spec.rb
983
+ - spec/models/file_view_stat_spec.rb
981
984
  - spec/models/fits_datastream_spec.rb
982
985
  - spec/models/generic_file/reload_on_save_spec.rb
983
986
  - spec/models/generic_file/visibility_spec.rb
@@ -1007,6 +1010,7 @@ files:
1007
1010
  - spec/support/poltergeist.rb
1008
1011
  - spec/support/proxies.rb
1009
1012
  - spec/support/selectors.rb
1013
+ - spec/support/statistic_helper.rb
1010
1014
  - spec/support/uploaded_file_monkeypatch.rb
1011
1015
  - spec/test_app_templates/lib/generators/test_app_generator.rb
1012
1016
  - spec/views/batch/edit.html.erb_spec.rb
@@ -1044,6 +1048,7 @@ files:
1044
1048
  - sufia-models/app/models/collection.rb
1045
1049
  - sufia-models/app/models/concerns/sufia/ability.rb
1046
1050
  - sufia-models/app/models/concerns/sufia/collection.rb
1051
+ - sufia-models/app/models/concerns/sufia/file_stat_utils.rb
1047
1052
  - sufia-models/app/models/concerns/sufia/generic_file.rb
1048
1053
  - sufia-models/app/models/concerns/sufia/generic_file/accessible_attributes.rb
1049
1054
  - sufia-models/app/models/concerns/sufia/generic_file/audit.rb
@@ -1072,7 +1077,9 @@ files:
1072
1077
  - sufia-models/app/models/datastreams/properties_datastream.rb
1073
1078
  - sufia-models/app/models/domain_term.rb
1074
1079
  - sufia-models/app/models/featured_work.rb
1080
+ - sufia-models/app/models/file_download_stat.rb
1075
1081
  - sufia-models/app/models/file_usage.rb
1082
+ - sufia-models/app/models/file_view_stat.rb
1076
1083
  - sufia-models/app/models/follow.rb
1077
1084
  - sufia-models/app/models/generic_file.rb
1078
1085
  - sufia-models/app/models/geo_names_resource.rb
@@ -1093,6 +1100,7 @@ files:
1093
1100
  - sufia-models/app/services/sufia/id_service.rb
1094
1101
  - sufia-models/app/services/sufia/noid.rb
1095
1102
  - sufia-models/config/locales/sufia.en.yml
1103
+ - sufia-models/lib/generators/sufia/models/cached_stats_generator.rb
1096
1104
  - sufia-models/lib/generators/sufia/models/fulltext_generator.rb
1097
1105
  - sufia-models/lib/generators/sufia/models/install_generator.rb
1098
1106
  - sufia-models/lib/generators/sufia/models/proxies_generator.rb
@@ -1117,6 +1125,8 @@ files:
1117
1125
  - sufia-models/lib/generators/sufia/models/templates/migrations/create_checksum_audit_logs.rb
1118
1126
  - sufia-models/lib/generators/sufia/models/templates/migrations/create_content_blocks.rb
1119
1127
  - sufia-models/lib/generators/sufia/models/templates/migrations/create_featured_works.rb
1128
+ - sufia-models/lib/generators/sufia/models/templates/migrations/create_file_download_stats.rb
1129
+ - sufia-models/lib/generators/sufia/models/templates/migrations/create_file_view_stats.rb
1120
1130
  - sufia-models/lib/generators/sufia/models/templates/migrations/create_local_authorities.rb
1121
1131
  - sufia-models/lib/generators/sufia/models/templates/migrations/create_proxy_deposit_requests.rb
1122
1132
  - sufia-models/lib/generators/sufia/models/templates/migrations/create_proxy_deposit_rights.rb
@@ -1206,7 +1216,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1206
1216
  version: '0'
1207
1217
  requirements: []
1208
1218
  rubyforge_project:
1209
- rubygems_version: 2.4.2
1219
+ rubygems_version: 2.4.3
1210
1220
  signing_key:
1211
1221
  specification_version: 4
1212
1222
  summary: Sufia was extracted from ScholarSphere developed by Penn State University
@@ -1321,7 +1331,9 @@ test_files:
1321
1331
  - spec/models/featured_work_list_spec.rb
1322
1332
  - spec/models/featured_work_spec.rb
1323
1333
  - spec/models/file_content_datastream_spec.rb
1334
+ - spec/models/file_download_stat_spec.rb
1324
1335
  - spec/models/file_usage_spec.rb
1336
+ - spec/models/file_view_stat_spec.rb
1325
1337
  - spec/models/fits_datastream_spec.rb
1326
1338
  - spec/models/generic_file/reload_on_save_spec.rb
1327
1339
  - spec/models/generic_file/visibility_spec.rb
@@ -1351,6 +1363,7 @@ test_files:
1351
1363
  - spec/support/poltergeist.rb
1352
1364
  - spec/support/proxies.rb
1353
1365
  - spec/support/selectors.rb
1366
+ - spec/support/statistic_helper.rb
1354
1367
  - spec/support/uploaded_file_monkeypatch.rb
1355
1368
  - spec/test_app_templates/lib/generators/test_app_generator.rb
1356
1369
  - spec/views/batch/edit.html.erb_spec.rb