Skip to content

Commit

Permalink
add date selector to analytics dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
dnoneill committed Oct 24, 2024
1 parent cead847 commit 219b219
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 58 deletions.
83 changes: 49 additions & 34 deletions app/components/spotlight/analytics/dashboard_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,50 +1,65 @@
<% if note %>
<p><%= note %></p>
<% end %>
<%= form_with url: helpers.analytics_exhibit_dashboard_path(current_exhibit), class: 'mb-3 d-flex', method: :get do |form| %>
<div>
<%= form.label :start_date, I18n.t("spotlight.dashboards.analytics.start_date_label") %>
<%= form.date_field :start_date, value: dates['start_date'], min: min_date, max: max_date %>
<%= form.label :end_date, I18n.t("spotlight.dashboards.analytics.end_date_label"), class: 'ml-2 ms-2' %>
<%= form.date_field :end_date, value: dates['end_date'], min: min_date, max: max_date %>
</div>
<div class="ml-auto ms-auto">
<%= form.submit I18n.t("spotlight.dashboards.analytics.submit_date_label"), class: 'btn btn-primary' %>
</div>
<% end %>
<% if results? %>
<%= cache current_exhibit, expires_in: 1.hour do %>
<h2><%= I18n.t("spotlight.dashboards.analytics.reporting_period_heading") %></h2>
<h2><%= heading %></h2>

<h3 class="h5 mt-4"><%= I18n.t("spotlight.dashboards.analytics.visitor_header") %></h3>
<%= render Spotlight::Analytics::AggregationComponent.new(data: page_analytics.totals) %>

<h3 class="h5 mt-4"><%= I18n.t("spotlight.dashboards.analytics.session_header") %></h3>
<%= render Spotlight::Analytics::AggregationComponent.new(data: search_analytics.totals, exclude_fields: [:eventCount] ) %>
<% unless page_analytics.rows.empty? %>
<h3 class="h5 mt-4"><%= I18n.t("spotlight.dashboards.analytics.pages.header") %></h4>
<table class="table table-striped popular-pages">
<thead>
<tr>
<th><%= I18n.t("spotlight.dashboards.analytics.pagetitle") %></th>
<th class="text-right text-end"><%= I18n.t("spotlight.dashboards.analytics.pageviews") %></th>
</tr>
</thead>
<% page_analytics.rows.each do |p| %>
<tr>
<td><%= link_to p.pageTitle, p.pagePath %></td>
<td class="text-right text-end"><%= p.screenPageViews %></td>
</tr>
<h3 class="h5 mt-4"><%= I18n.t("spotlight.dashboards.analytics.pages.header") %></h4>
<table class="table table-striped popular-pages">
<thead>
<tr>
<th><%= I18n.t("spotlight.dashboards.analytics.pagetitle") %></th>
<th class="text-right text-end"><%= I18n.t("spotlight.dashboards.analytics.pageviews") %></th>
</tr>
</thead>
<% page_analytics.rows.each do |p| %>
<tr>
<td><%= link_to p.pageTitle, p.pagePath %></td>
<td class="text-right text-end"><%= p.screenPageViews %></td>
</tr>
<% end %>
</table>
<% end %>
</table>
<% end %>
<% unless search_analytics.rows.empty? %>
<h3 class="h5 mt-4"><%= I18n.t("spotlight.dashboards.analytics.searches.header") %></h4>
<table class="table table-striped popular-pages">
<thead>
<tr>
<th><%= I18n.t("spotlight.dashboards.analytics.searches.term") %></th>
<th class="text-right text-end"><%= I18n.t("spotlight.dashboards.analytics.searches.views") %></th>
</tr>
</thead>
<% search_analytics.rows.each do |p| %>
<% if p.searchTerm.present? %>
<% unless search_analytics.rows.empty? %>
<h3 class="h5 mt-4"><%= I18n.t("spotlight.dashboards.analytics.searches.header") %></h4>
<table class="table table-striped popular-pages">
<thead>
<tr>
<td><%= p.searchTerm %></td>
<td class="text-right text-end"><%= p.eventCount %></td>
<th><%= I18n.t("spotlight.dashboards.analytics.searches.term") %></th>
<th class="text-right text-end"><%= I18n.t("spotlight.dashboards.analytics.searches.views") %></th>
</tr>
</thead>
<% search_analytics.rows.each do |p| %>
<% if p.searchTerm.present? %>
<tr>
<td><%= p.searchTerm %></td>
<td class="text-right text-end"><%= p.eventCount %></td>
</tr>
<% end %>
<% end %>
<% end %>
</table>
</table>
<% end %>
<% end %>
<% else %>
<%= I18n.t("spotlight.dashboards.analytics.no_results", pageurl: page_url) %>
<p><%= I18n.t("spotlight.dashboards.analytics.no_results", pageurl: page_url) %></p>
<% end %>
72 changes: 69 additions & 3 deletions app/components/spotlight/analytics/dashboard_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,69 @@ class DashboardComponent < ViewComponent::Base
def initialize(current_exhibit:)
super
@current_exhibit = current_exhibit
@dates = { 'start_date' => '365daysAgo', 'end_date' => 'today' }
@default_start_date = [min_date, 1.year.ago].max
@default_end_date = [max_date, Time.zone.today].min
end

def before_render
flash[:error] = nil
@dates = { 'start_date' => @default_start_date.to_date.to_s,
'end_date' => @default_end_date.to_date.to_s }

validate_dates if params[:start_date] || params[:end_date]
end

def validate_dates
@start_date = parse_date(params[:start_date], min_date)
@end_date = parse_date(params[:end_date], max_date)
if @start_date > @end_date
flash[:error] = I18n.t('spotlight.dashboards.analytics.error_heading', date: "#{@start_date} to #{@end_date}")
elsif @start_date < min_date
flash[:error] = I18n.t('spotlight.dashboards.analytics.error_heading', date: @start_date)
elsif @end_date > max_date
flash[:error] = I18n.t('spotlight.dashboards.analytics.error_heading', date: @end_date)
else
update_dates
end
end

def update_dates
@dates['start_date'] = @start_date.to_date.to_s if parse_date(params[:start_date], nil)
@dates['end_date'] = @end_date.to_date.to_s if parse_date(params[:end_date], nil)
end

def parse_date(date, backup_date)
return backup_date unless date

Date.parse(date)
rescue Date::Error
flash[:error] = I18n.t('spotlight.dashboards.analytics.error_heading', date:)
backup_date
end

def min_date
Spotlight::Engine.config.ga_date_range['start_date'] || Date.new(2015, 8, 14) # This is the minimum date supported by GA
end

def max_date
Spotlight::Engine.config.ga_date_range['end_date'] || Time.zone.today
end

def note
I18n.t('spotlight.dashboards.analytics.note', default: nil)
end

def heading
if params[:start_date] || params[:end_date]
I18n.t('spotlight.dashboards.analytics.reporting_period_heading_dynamic', start_date: formatted_date(dates['start_date']),
end_date: formatted_date(dates['end_date']))
else
I18n.t('spotlight.dashboards.analytics.reporting_period_heading')
end
end

def formatted_date(date_string)
Date.parse(date_string).strftime('%m/%d/%Y')
end

def results?
Expand All @@ -21,11 +83,15 @@ def page_url
end

def page_analytics
@page_analytics ||= current_exhibit.page_analytics(dates, page_url)
Rails.cache.fetch([current_exhibit, dates['start_date'], dates['end_date'], 'page_analytics'], expires_in: 1.hour) do
current_exhibit.page_analytics(dates, page_url)
end
end

def search_analytics
@search_analytics ||= current_exhibit.analytics(dates, page_url)
Rails.cache.fetch([current_exhibit, dates['start_date'], dates['end_date'], 'search_analytics'], expires_in: 1.hour) do
current_exhibit.analytics(dates, page_url)
end
end
end
end
Expand Down
8 changes: 2 additions & 6 deletions app/models/concerns/spotlight/exhibit_analytics.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,13 @@ module ExhibitAnalytics
def analytics(dates = { start_date: '365daysAgo', end_date: 'today' }, path = nil)
return OpenStruct.new unless analytics_provider&.enabled?

@analytics ||= {}
start_date = dates['start_date'] || 1.month.ago
@analytics[start_date] ||= analytics_provider.exhibit_data(path || self, dates)
analytics_provider.exhibit_data(path || self, dates)
end

def page_analytics(dates = { start_date: '365daysAgo', end_date: 'today' }, path = nil)
return [] unless analytics_provider&.enabled?

@page_analytics ||= {}
start_date = dates['start_date'] || 1.month.ago
@page_analytics[start_date] ||= analytics_provider.page_data(path || self, dates)
analytics_provider.page_data(path || self, dates)
end

def analytics_provider
Expand Down
6 changes: 6 additions & 0 deletions config/locales/spotlight.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -482,14 +482,18 @@ en:
dashboards:
analytics:
activeUsers: unique visits
end_date_label: End date
engagementRate: engagement rate
error_heading: "%{date} does not constitute a valid date range. Please enter a valid date range."
header: Analytics
no_results: There are no analytics for the path "%{pageurl}"
note:
pages:
header: Most popular pages
pagetitle: Page title
pageviews: Page views
reporting_period_heading: User activity over the past year
reporting_period_heading_dynamic: User activity from %{start_date} to %{end_date}
screenPageViews: page views
screenPageViewsPerSession: pages per session
searches:
Expand All @@ -498,6 +502,8 @@ en:
views: Searches
session_header: Sessions
sessions: sessions
start_date_label: Start date
submit_date_label: Submit
totalUsers: visitors
users: unique visits
visitor_header: Visitors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,16 @@
# (https://console.cloud.google.com/iam-admin/iam -> Service accounts -> click on service account -> keys)
# c) set ga_property_id below to your site's property id (located in admin -> Property -> Property details upper right hand corner)
# d) Set the ga_web_property_id. (located in admin -> Data collection and modification -> Web stream details and begins with G-)
# e) (optional) set ga_date_range. This allows you throttle the dates the user can filter by.
# ga_date_range values should use a Date object i.e. (Date.new(YYYY, MM, DD)).
# ga_property_id is used for fetching analytics data from google's api, ga_web_property_id is used for sending events to GA analtyics
# ga_web_property_id will probably change in V5 to ga_measurement_id for clarity
# Rails.application.config.to_prepare do
# Spotlight::Engine.config.analytics_provider = Spotlight::Analytics::Ga
# Spotlight::Engine.config.ga_json_key_path = nil
# Spotlight::Engine.config.ga_web_property_id = 'G-XXXXXXXXXX'
# Spotlight::Engine.config.ga_property_id = '12345678'
# Spotlight::Engine.config.ga_date_range = { 'start_date' => nil, 'end_date' => nil }
# Spotlight::Engine.config.ga_analytics_options = {}
# Spotlight::Engine.config.ga_page_analytics_options = Spotlight::Engine.config.ga_analytics_options.merge(limit: 5)
# Spotlight::Engine.config.ga_search_analytics_options = Spotlight::Engine.config.ga_analytics_options.merge(limit: 11)
Expand Down
1 change: 1 addition & 0 deletions lib/spotlight/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ def self.blacklight_config
config.ga_property_id = nil
config.ga_analytics_options = {}
config.ga_page_analytics_options = config.ga_analytics_options.merge(limit: 5)
config.ga_date_range = { 'start_date' => nil, 'end_date' => nil }
config.ga_debug_mode = false

config.max_pages = 1000
Expand Down
117 changes: 103 additions & 14 deletions spec/components/spotlight/analytics/dashboard_component_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,119 @@
totals: OpenStruct.new(screenPageViews: 1, users: 2, sessions: 3))
end

let(:default_start_date) { 1.year.ago.to_date.to_s }
let(:default_end_date) { Time.zone.today.to_date.to_s }
let(:default_min_date) { '2015-08-14' } # This is the minimum date GA's API will allow

before do
allow_any_instance_of(SpotlightHelper).to receive(:exhibit_root_path).and_return('/path')
allow_any_instance_of(Rails.application.routes.url_helpers).to receive(:analytics_exhibit_dashboard_path).and_return('/path')
end

it 'shows translated header' do
expect(rendered).to have_content 'User activity over the past year'
def get_min_max(field)
field = rendered.find("input[name='#{field}']")
{ min: field[:min], max: field[:max] }
end

it 'has metric labels' do
expect(rendered).to have_content 'page views'
expect(rendered).to have_content 'unique visits'
expect(rendered).to have_content 'sessions'
context 'default page load' do
it 'has start and end date selectors' do
expect(get_min_max('start_date')).to eq({ max: default_end_date, min: default_min_date })
expect(get_min_max('end_date')).to eq({ max: default_end_date, min: default_min_date })
expect(rendered).to have_field('start_date', with: default_start_date)
expect(rendered).to have_field('end_date', with: default_end_date)
expect(rendered).to have_content 'User activity over the past year'
end

it 'shows translated header' do
expect(rendered).to have_content 'User activity over the past year'
end

it 'has metric labels' do
expect(rendered).to have_content 'page views'
expect(rendered).to have_content 'unique visits'
expect(rendered).to have_content 'sessions'
end

it 'has metric values' do
expect(rendered).to have_selector '.value.screenPageViews', text: 1
expect(rendered).to have_selector '.value.users', text: 2
expect(rendered).to have_selector '.value.sessions', text: 3
end

it 'has page-level data' do
expect(rendered).to have_content 'Most popular pages'
expect(rendered).to have_link 'title', href: '/path'
expect(rendered).to have_content '123'
end
end

it 'has metric values' do
expect(rendered).to have_selector '.value.screenPageViews', text: 1
expect(rendered).to have_selector '.value.users', text: 2
expect(rendered).to have_selector '.value.sessions', text: 3
context 'has valid start and end date params' do
it 'updates the header and selector to param values' do
start_date = '2024-01-01'
end_date = '2024-01-02'
vc_test_controller.params[:start_date] = start_date
vc_test_controller.params[:end_date] = end_date
expect(get_min_max('start_date')).to eq({ max: default_end_date, min: default_min_date })
expect(get_min_max('end_date')).to eq({ max: default_end_date, min: default_min_date })
expect(rendered).to have_field('start_date', with: start_date)
expect(rendered).to have_field('end_date', with: end_date)
expect(rendered).to have_content "User activity from #{component.formatted_date(start_date)} to #{component.formatted_date(end_date)}"
end
end

it 'has page-level data' do
expect(rendered).to have_content 'Most popular pages'
expect(rendered).to have_link 'title', href: '/path'
expect(rendered).to have_content '123'
context 'has invalid start and end date params' do
it 'updates the header and selector to default values' do
vc_test_controller.params[:start_date] = '2024-41-01'
vc_test_controller.params[:end_date] = '2024-31-02'
expect(get_min_max('start_date')).to eq({ max: default_end_date, min: default_min_date })
expect(get_min_max('end_date')).to eq({ max: default_end_date, min: default_min_date })
expect(rendered).to have_field('start_date', with: default_start_date)
expect(rendered).to have_field('end_date', with: default_end_date)
expect(rendered).to have_content "User activity from #{component.formatted_date(default_start_date)} to #{component.formatted_date(default_end_date)}"
end
end

context 'has a start date larger than end date' do
it 'updates the header and selector to default values' do
vc_test_controller.params[:start_date] = '2024-02-02'
vc_test_controller.params[:end_date] = '2024-02-01'
expect(get_min_max('start_date')).to eq({ max: default_end_date, min: default_min_date })
expect(get_min_max('end_date')).to eq({ max: default_end_date, min: default_min_date })
expect(rendered).to have_field('start_date', with: default_start_date)
expect(rendered).to have_field('end_date', with: default_end_date)
expect(rendered).to have_content "User activity from #{component.formatted_date(default_start_date)} to #{component.formatted_date(default_end_date)}"
end
end

context 'Spotlight::Engine.config.ga_date_range is set' do
let(:start_date) { 6.months.ago.to_date.to_s }
let(:end_date) { 3.months.ago.to_date.to_s }

before do
Spotlight::Engine.config.ga_date_range = { 'start_date' => 6.months.ago, 'end_date' => 3.months.ago }
end

after do
Spotlight::Engine.config.ga_date_range = { 'start_date' => nil, 'end_date' => nil }
end

it 'has start and end date selectors' do
expect(get_min_max('start_date')).to eq({ max: end_date, min: start_date })
expect(get_min_max('end_date')).to eq({ max: end_date, min: start_date })
expect(rendered).to have_field('start_date', with: start_date)
expect(rendered).to have_field('end_date', with: end_date)
expect(rendered).to have_content 'User activity over the past year'
end

it 'has invalid start and end date params' do
vc_test_controller.params[:start_date] = '2024-41-01'
vc_test_controller.params[:end_date] = '2024-31-02'
expect(get_min_max('start_date')).to eq({ max: end_date, min: start_date })
expect(get_min_max('end_date')).to eq({ max: end_date, min: start_date })
expect(rendered).to have_field('start_date', with: start_date)
expect(rendered).to have_field('end_date', with: end_date)
expect(rendered).to have_content "User activity from #{component.formatted_date(start_date)} to #{component.formatted_date(end_date)}"
end
end

context 'does not have any anayltics data' do
Expand Down
Loading

0 comments on commit 219b219

Please sign in to comment.