sufia 4.1.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
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