Skip to content

Commit

Permalink
Merge pull request #1531 from mlibrary/595_pageviews_line_graph
Browse files Browse the repository at this point in the history
line graph of monograph pageviews over time
  • Loading branch information
sethaj authored Jan 29, 2018
2 parents ec87c5b + b17ac8c commit fc08b51
Show file tree
Hide file tree
Showing 10 changed files with 375 additions and 47 deletions.
157 changes: 157 additions & 0 deletions app/assets/javascripts/application/heliotrope_flot_stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// This file is mostly the same as the one you'll find here:
// https://github.com/samvera/hyrax/blob/1e504c200fd9c39120f514ac33cd42cd843de9fa/app/assets/javascripts/hyrax/flot_stats.js

// Changes:
// 1) made it "Turbolinks aware", triggering when the stats tab is already active on page load, or is clicked
// 2) added a flag so it doesn't redraw more than once per page load, as the user might have zoomed the graph etc.
// 3) added setTimeOut with 250ms, which overcomes two Flot issues which stem from plotting a graph in a hidden parent:
// a) the graph never resizes appropriately to the space available (this one can also be solved by...
// adding `jquery.flot.resize` to `application.js` but that causes a noticeable "jump" as the graph resizes)
// see https://stackoverflow.com/a/26070749/3418063 and https://github.com/flot/flot/issues/1014
// b) the labels can overlap the graph (most notably on the y-axis), again from not knowing available space exactly
// see https://stackoverflow.com/q/18012123/3418063
// 4) used UTC functions (like getUTCDate etc.) for the tooltip on the graph points. The version in Hyrax (copied...
// from Sufia) takes timezone into account on the tooltip but not the axis, so the former always seems a day ...
// behind (west of GMT)
//
// Apart from these changes it's all the same with `hyrax_item_stats` changed to `heliotrope_item_stats`


// see 2) above ^
var heliotropeStatsAlreadyDrawn = false;

$(document).on('turbolinks:load', function() {
if ($('#monograph-stats-tab').hasClass('active')) {
setTimeout(monographStatFlot, 250);
heliotropeStatsAlreadyDrawn = true;
}

$("a[href='#monograph-stats']").bind('click', function () {
if(heliotropeStatsAlreadyDrawn == false) {
setTimeout(monographStatFlot, 250);
heliotropeStatsAlreadyDrawn = true;
}
});
});

function monographStatFlot() {
if (typeof heliotrope_item_stats === "undefined") {
return;
}

function weekendAreas(axes) {
var markings = [],
d = new Date(axes.xaxis.min);

// go to the first Saturday
d.setUTCDate(d.getUTCDate() - ((d.getUTCDay() + 1) % 7))
d.setUTCSeconds(0);
d.setUTCMinutes(0);
d.setUTCHours(0);

var i = d.getTime();

// when we don't set yaxis, the rectangle automatically
// extends to infinity upwards and downwards

do {
markings.push({xaxis: {from: i, to: i + 2 * 24 * 60 * 60 * 1000}});
i += 7 * 24 * 60 * 60 * 1000;
} while (i < axes.xaxis.max);

return markings;
}

var options = {
xaxis: {
mode: "time",
tickLength: 5
},
yaxis: {
tickDecimals: 0,
min: 0
},
series: {
lines: {
show: true,
fill: true
},
points: {
show: true,
fill: true
}
},
selection: {
mode: "x"
},
grid: {
hoverable: true,
clickable: true,
markings: weekendAreas
}
};

var plot = $.plot("#usage-stats", heliotrope_item_stats, options);

$("<div id='tooltip'></div>").css({
position: "absolute",
display: "none",
border: "1px solid #bce8f1",
padding: "2px",
"background-color": "#d9edf7",
opacity: 0.80
}).appendTo("body");

$("#usage-stats").bind("plothover", function (event, pos, item) {
if (item) {
date = new Date(item.datapoint[0]);
months = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"]
$("#tooltip").html("<strong>" + item.series.label + ": " + item.datapoint[1] + "</strong><br/>" + months[date.getUTCMonth()] + " " + date.getUTCDate() + ", " + date.getUTCFullYear())
.css({top: item.pageY + 5, left: item.pageX + 5})
.fadeIn(200);
} else {
$("#tooltip").fadeOut(100)
}
});

var overview = $.plot("#overview", heliotrope_item_stats, {
series: {
lines: {
show: true,
lineWidth: 1
},
shadowSize: 0
},
xaxis: {
ticks: [],
mode: "time",
minTickSize: [1, "day"]
},
yaxis: {
ticks: [],
min: 0,
autoscaleMargin: 0.1
},
selection: {
mode: "x"
},
legend: {
show: false
}
});

$("#usage-stats").bind("plotselected", function (event, ranges) {
plot = $.plot("#usage-stats", heliotrope_item_stats, $.extend(true, {}, options, {
xaxis: {
min: ranges.xaxis.from,
max: ranges.xaxis.to
}
}));
overview.setSelection(ranges, true);
});

$("#overview").bind("plotselected", function (event, ranges) {
plot.setSelection(ranges);
});
};
76 changes: 76 additions & 0 deletions app/presenters/concerns/cc_analytics_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,105 @@
module CCAnalyticsPresenter
extend ActiveSupport::Concern

attr_accessor :pageviews

def pageviews_by_path(path)
start_time = date_uploaded.strftime('%Q').to_i
count = 0
pageviews = Rails.cache.read('ga_pageviews')
return '?' if pageviews.nil?
if pageviews.is_a?(Array)
pageviews.each do |entry|
timestamp = DateTime.strptime(entry[:date], '%Y%m%d').strftime('%Q').to_i
# block pageviews older than date_uploaded, likely from reused NOID
next unless timestamp >= start_time
count += entry[:pageviews].to_i if entry[:pagePath] == path
end
end
count
end

def pageviews_by_ids(ids)
start_time = date_uploaded.strftime('%Q').to_i
count = 0
pageviews = Rails.cache.read('ga_pageviews')
return '?' if pageviews.nil?
if pageviews.is_a?(Array)
pageviews.each do |entry|
timestamp = DateTime.strptime(entry[:date], '%Y%m%d').strftime('%Q').to_i
# block pageviews older than date_uploaded, likely from reused NOID
next unless timestamp >= start_time
ids.each do |id|
count += entry[:pageviews].to_i if entry[:pagePath].include? id
Rails.logger.info("GETTING CALLED!!!!")
end
end
end
count
end

def timestamped_pageviews_by_ids(ids)
# we want the output here to be structured for Flot as used in Hyrax, with unixtimes in milliseconds

start_time = date_uploaded.strftime('%Q').to_i
pageviews = Rails.cache.read('ga_pageviews')

if pageviews.nil?
@pageviews ||= '?'
return {}
end

data = {}
# as this stuff looks expensive, going to do a count here as well and memoize it
count = 0
if pageviews.is_a?(Array)
pageviews.each do |entry|
timestamp = DateTime.strptime(entry[:date], '%Y%m%d').strftime('%Q').to_i
# block pageviews older than date_uploaded, likely from reused NOID
next unless timestamp >= start_time
ids.each do |id|
next unless entry[:pagePath].include? id
views = entry[:pageviews].to_i
data[timestamp] = if data[timestamp].blank?
views
else
data[timestamp] + views
end
count += views
end
end
end
@pageviews ||= count
data # essentially a hash with 12am timestamp keys and pageview values for that day
end

def flot_daily_pageviews_zero_pad(ids)
data = timestamped_pageviews_by_ids(ids)
return [] if data.blank?
start_timestamp = date_uploaded.strftime('%Q').to_i
end_timestamp = DateTime.now.strftime('%Q').to_i

# zero-pad data to fill in days with no page views
i = start_timestamp
while i <= end_timestamp
data[i] = 0 if data[i].blank?
i += 86_400_000 # 1 day in milliseconds
end
data.to_a.sort
end

def flot_pageviews_over_time(ids)
data = timestamped_pageviews_by_ids(ids)
return [] if data.blank?
data = data.to_a.sort
start_timestamp = date_uploaded.strftime('%Q').to_i
# ensure that the graph starts from date_uploaded
data.unshift([start_timestamp, 0]) if data[0][0] > start_timestamp

# we want the values for a given day to be a running tally
(1..data.count - 1).each do |i|
data[i][1] += data[i - 1][1]
end
data
end
end
2 changes: 1 addition & 1 deletion app/presenters/hyrax/file_set_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def generic_embed_code

# Google Analytics
def pageviews
pageviews_by_path(hyrax_file_set_path(id))
@pageviews ||= pageviews_by_path(hyrax_file_set_path(id))
end

# Embed Code Stuff
Expand Down
14 changes: 12 additions & 2 deletions app/presenters/hyrax/monograph_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class MonographPresenter < WorkShowPresenter
include FeaturedRepresentatives::MonographPresenter
include ActionView::Helpers::UrlHelper

attr_accessor :pageviews

delegate :date_created, :date_modified, :date_uploaded,
:description, :creator, :creator_display, :editor, :contributor, :subject,
:publisher, :date_published, :language, :isbn, :isbn_paper,
Expand Down Expand Up @@ -100,8 +102,16 @@ def next_file_sets_id(file_sets_id)
ordered_file_sets_ids[(ordered_file_sets_ids.find_index(file_sets_id) + 1)]
end

def pageviews
pageviews_by_ids(ordered_file_sets_ids << id)
def monograph_analytics_ids
ordered_file_sets_ids + [id]
end

def pageviews_count
@pageviews ||= pageviews_by_ids(monograph_analytics_ids)
end

def pageviews_over_time_graph_data
[{ "label": "Total Pageviews", "data": flot_pageviews_over_time(monograph_analytics_ids).to_a.sort }]
end

def ordered_file_sets_ids
Expand Down
8 changes: 3 additions & 5 deletions app/views/monograph_catalog/_index_monograph.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ $(document).ready(function() {
<% if @monograph_presenter.aboutware? %>
<li role="presentation"><a href="#aboutware" aria-controls="aboutware" role="tab" data-toggle="tab">About</a></li>
<% end %>
<li role="presentation"><a href="#stats" aria-controls="stats" role="tab" data-toggle="tab">Stats</a></li>
<li id="monograph-stats-tab" role="presentation"><a href="#monograph-stats" aria-controls="monograph-stats" role="tab" data-toggle="tab">Stats</a></li>
</ul>
<div class="tab-content monograph-assets-toc-epub-content">
<% if @monograph_presenter.epub? %>
Expand Down Expand Up @@ -141,10 +141,8 @@ $(document).ready(function() {
</div>
</div>
<% end %>
<div role="tabpanel" class="tab-pane stats row" id="stats">
<div class="col-sm-12 pageviews">
<p><%= t('monograph_catalog.index.pageviews_html', count: number_with_delimiter(@monograph_presenter.pageviews)) %></p>
</div><!-- /.pageviews -->
<div role="tabpanel" class="tab-pane stats row" id="monograph-stats">
<%= render 'stats' %>
</div>
</div>
</div>
Expand Down
19 changes: 19 additions & 0 deletions app/views/monograph_catalog/_stats.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<%# see https://github.com/samvera/hyrax/blob/1e504c200fd9c39120f514ac33cd42cd843de9fa/app/presenters/hyrax/stats_usage_presenter.rb %>
<%= javascript_tag do %>
var heliotrope_item_stats = <%= @monograph_presenter.pageviews_over_time_graph_data.to_json.html_safe %>;
<% end %>

<div class="row">
<div class="col-sm-12">
<div class="alert alert-info">
<i class="glyphicon glyphicon-signal large-icon"></i>
<%= t('monograph_catalog.index.pageviews_html', count: number_with_delimiter(@monograph_presenter.pageviews_count), date: @monograph_presenter.date_uploaded.strftime("%B %d, %Y")) %>
</div>
<div class="stats-container">
<div id="usage-stats" class="stats-placeholder"></div>
</div>
<div class="stats-container" style="height:150px;">
<div id="overview" class="stats-placeholder" style="width: 100%"></div>
</div>
</div>
</div>
2 changes: 1 addition & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ en:
can_edit: "Manage Monograph and Files"
edited_by: "Edited by %{editors}"
home: "Home"
pageviews_html: "This item has been viewed <b>%{count}</b> times."
pageviews_html: "<strong>%{count}</strong> views since %{date}"
read: "Read %{title}"
read_book: "Read Book"
reindex: "Reindex Monograph"
Expand Down
1 change: 1 addition & 0 deletions spec/factories/monographs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
sequence(:title) { |n| ["Test Monograph #{n}"] }
press { FactoryBot.create(:press).subdomain }
visibility Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_PRIVATE
date_uploaded { DateTime.now }

factory :public_monograph do
visibility Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_PUBLIC
Expand Down
Loading

0 comments on commit fc08b51

Please sign in to comment.