Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/controllers/admin/ahoy_activities_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,11 @@ def prepare_chart_data
.sort_by { |_k, v| -v }.first(10).to_h

# Windows types - batch lookup
wt_ids = events
wt_raw = events
.where(name: [ "filter.workshops", "search.workshops" ])
.pluck(Arel.sql("JSON_EXTRACT(properties, '$.filters.windows_types')"))
.flat_map { |arr| safe_json_parse(arr) }
wt_ids = wt_raw.map { |wt| wt.is_a?(Hash) ? wt["id"] : wt }.compact
wt_names = WindowsType.where(id: wt_ids.uniq).pluck(:id, :short_name).to_h
@ws_windows_types = wt_ids
.map { |id| wt_names[id] }.compact.tally
Expand Down
7 changes: 7 additions & 0 deletions app/helpers/analytics_helper.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
module AnalyticsHelper
def chart_card(title, &block)
content_tag(:div, class: "bg-white border border-gray-200 rounded-xl shadow-sm p-6") do
content_tag(:h3, title, class: "text-sm font-semibold text-gray-700 mb-2") +
capture(&block)
end
end

def print_button(record,
printable_type: nil,
path: admin_analytics_print_path,
Expand Down
12 changes: 8 additions & 4 deletions app/services/analytics/ahoy_tracker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ def track_index_intent(controller, resource_class, params:, result_count:)

controller.ahoy.track("filter.#{resource_name}", properties) if cleaned[:filters].present?
controller.ahoy.track("search.#{resource_name}", properties) if cleaned[:keywords].present?
controller.ahoy.track("search_zero.#{resource_name}", properties) if cleaned[:keywords].present? && result_count.zero?
if cleaned[:keywords].present? && result_count.zero?
query = cleaned[:keywords][:full_text] || cleaned[:keywords][:title]
controller.ahoy.track("search_zero.#{resource_name}", properties.merge(query: query).compact)
end
end

private
Expand Down Expand Up @@ -171,9 +174,10 @@ def extract_search_params(params)
Array(raw["resource_kind"]).presence

# ---- OTHER FILTERS ----
filters[:categories] = raw["category_ids"] if raw["category_ids"].present?
filters[:sectors] = raw["sector_ids"] if raw["sector_ids"].present?
filters[:windows_type] = raw["windows_type_ids"] if raw["windows_type_ids"].present?
# Forms may send as "category_ids" (edit form) or "categories" (filter form)
filters[:categories] = raw["category_ids"].presence || raw["categories"].presence
filters[:sectors] = raw["sector_ids"].presence || raw["sectors"].presence
filters[:windows_type] = raw["windows_type_ids"].presence || raw["windows_types"].presence

filters.compact!

Expand Down
203 changes: 68 additions & 135 deletions app/views/admin/ahoy_activities/charts.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,23 @@
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">Visit patterns</h2>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-8">
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= column_chart scoped_visits.group_by_hour_of_day(:started_at, format: "%l %P").count,
title: "Average visits by Hour" %>
</div>
<%= chart_card("Average visits by Hour") do %>
<%= column_chart scoped_visits.group_by_hour_of_day(:started_at, format: "%l %P").count %>
<% end %>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= column_chart scoped_visits.group_by_day_of_week(:started_at, format: "%A").count,
title: "Average visits by Weekday" %>
</div>
<%= chart_card("Average visits by Weekday") do %>
<%= column_chart scoped_visits.group_by_day_of_week(:started_at, format: "%A").count %>
<% end %>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= chart_card("Top Cities") do %>
<%= bar_chart scoped_visits.where.not(city: nil)
.group(:city)
.count.sort_by { |_c, v| -v }.first(10).to_h,
title: "Top Cities" %>
</div>
.count.sort_by { |_c, v| -v }.first(10).to_h %>
<% end %>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= column_chart @session_duration_chart, title: "Session Duration" %>
</div>
<%= chart_card("Session Duration") do %>
<%= column_chart @session_duration_chart %>
<% end %>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6 flex flex-col items-center justify-center">
<div class="text-5xl font-bold text-gray-900 tabular-nums"><%= @avg_events_per_visit %></div>
Expand All @@ -63,12 +60,11 @@
</section>

<section class="mt-6">
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= chart_card("Visits by Date") do %>
<%= line_chart scoped_visits.group_by_day(:started_at).count,
xtitle: "Date", ytitle: "Visits",
height: "300px", width: "100%",
title: "Visits by Date" %>
</div>
height: "300px", width: "100%" %>
<% end %>
</section>

<section class="mt-6">
Expand Down Expand Up @@ -114,41 +110,15 @@
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">Workshop analytics</h2>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-8">
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= bar_chart(@ws_category_types, title: "Workshop Search: Category Types") %>
</div>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= bar_chart(@ws_category_names, title: "Workshop Search: Categories") %>
</div>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= bar_chart(@ws_sectors, title: "Workshop Search: Sectors") %>
</div>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= bar_chart(@ws_search_titles, title: "Workshop Search: Titles") %>
</div>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= bar_chart(@ws_search_authors, title: "Workshop Search: Authors") %>
</div>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= bar_chart(@ws_search_full_text, title: "Workshop Search: Full-Text") %>
</div>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= bar_chart(@ws_windows_types, title: "Workshop search: Windows audiences") %>
</div>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= bar_chart(@ws_zero_results, title: "Workshop Search: No Results") %>
</div>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= column_chart(@ws_funnel, title: "Workshop Discovery Funnel") %>
</div>
<%= chart_card("Workshop Search: Category Types") { bar_chart(@ws_category_types) } %>
<%= chart_card("Workshop Search: Categories") { bar_chart(@ws_category_names) } %>
<%= chart_card("Workshop Search: Sectors") { bar_chart(@ws_sectors) } %>
<%= chart_card("Workshop Search: Titles") { bar_chart(@ws_search_titles) } %>
<%= chart_card("Workshop Search: Authors") { bar_chart(@ws_search_authors) } %>
<%= chart_card("Workshop Search: Full-Text") { bar_chart(@ws_search_full_text) } %>
<%= chart_card("Workshop search: Windows audiences") { bar_chart(@ws_windows_types) } %>
<%= chart_card("Workshop Search: No Results") { bar_chart(@ws_zero_results) } %>
<%= chart_card("Workshop Discovery Funnel") { column_chart(@ws_funnel) } %>
</div>
</div>
</section>
Expand All @@ -157,17 +127,9 @@
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">Resource analytics</h2>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-8">
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= bar_chart(@rs_keywords, title: "Resource Search: Keywords") %>
</div>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= bar_chart(@rs_kinds, title: "Resource Search: Kinds") %>
</div>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= column_chart(@rs_funnel, title: "Resource Discovery Funnel") %>
</div>
<%= chart_card("Resource Search: Keywords") { bar_chart(@rs_keywords) } %>
<%= chart_card("Resource Search: Kinds") { bar_chart(@rs_kinds) } %>
<%= chart_card("Resource Discovery Funnel") { column_chart(@rs_funnel) } %>
</div>
</div>
</section>
Expand All @@ -176,51 +138,32 @@
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">Content discovery</h2>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-8">
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= bar_chart(@tagging_sectors, title: "Tagging views: Sectors") %>
</div>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= bar_chart(@tagging_categories, title: "Tagging views: Categories") %>
</div>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= column_chart(@discovery_funnel, title: "User Discovery Funnel") %>
</div>
<%= chart_card("Tagging views: Sectors") { bar_chart(@tagging_sectors) } %>
<%= chart_card("Tagging views: Categories") { bar_chart(@tagging_categories) } %>
<%= chart_card("User Discovery Funnel") { column_chart(@discovery_funnel) } %>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= chart_card("Content Types People View Most") do %>
<%= pie_chart(
scoped_events
.where("name LIKE 'view.%'")
.group("SUBSTRING_INDEX(name, '.', -1)")
.count,
title: "Content Types People View Most"
) %>
</div>
.count) %>
<% end %>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= chart_card("Content Types Printed Most") do %>
<%= pie_chart(
scoped_events
.where("name LIKE 'print.%'")
.group("SUBSTRING_INDEX(name, '.', -1)")
.count,
title: "Content Types Printed Most"
) %>
</div>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= pie_chart(@content_discovery, title: "How Users Discover Content") %>
</div>
.count) %>
<% end %>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= pie_chart(@search_conversion, title: "Search-to-View Conversion") %>
</div>
<%= chart_card("How Users Discover Content") { pie_chart(@content_discovery) } %>
<%= chart_card("Search-to-View Conversion") { pie_chart(@search_conversion) } %>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= line_chart @user_signup_trend,
title: "User Signup Trend",
points: true %>
</div>
<%= chart_card("User Signup Trend") do %>
<%= line_chart @user_signup_trend, points: true %>
<% end %>
</div>
</div>
</section>
Expand All @@ -229,42 +172,33 @@
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">Referrals & engagement</h2>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-8">
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= chart_card("Top Referrer → Landing Pages") do %>
<%= bar_chart scoped_visits.group([:referring_domain, :landing_page])
.count.sort_by { |_k, v| -v }.first(10).to_h,
title: "Top Referrer → Landing Pages" %>
</div>
.count.sort_by { |_k, v| -v }.first(10).to_h %>
<% end %>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= chart_card("Top Landing Pages") do %>
<%= bar_chart scoped_visits.group(:landing_page)
.order("count_id DESC")
.limit(10)
.count(:id),
title: "Top Landing Pages", height: "350px" %>
</div>
height: "350px" %>
<% end %>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= bar_chart @top_engaged_users,
title: "Top Engaged Users",
library: { indexAxis: "y" } %>
</div>
<%= chart_card("Top Engaged Users") do %>
<%= bar_chart @top_engaged_users, library: { indexAxis: "y" } %>
<% end %>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= pie_chart @bounce_rate, title: "Bounce Rate" %>
</div>
<%= chart_card("Bounce Rate") { pie_chart @bounce_rate } %>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= pie_chart scoped_visits.group(:country).count,
title: "Visits by Country" %>
</div>
<%= chart_card("Visits by Country") do %>
<%= pie_chart scoped_visits.group(:country).count %>
<% end %>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= bar_chart @top_exit_events, title: "Top Exit Events" %>
</div>
<%= chart_card("Top Exit Events") { bar_chart @top_exit_events } %>

<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= chart_card("Content Creation Velocity by Type") do %>
<%= line_chart @creation_velocity_data,
title: "Content Creation Velocity by Type",
height: "400px",
points: true,
curve: false,
Expand All @@ -278,7 +212,7 @@
legend: { position: "top" },
yAxis: { title: { text: "Items Created" } }
} %>
</div>
<% end %>
</div>
</div>
</section>
Expand All @@ -287,21 +221,20 @@
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">Geographic & technical</h2>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-8">
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= pie_chart ({"Logged In" => scoped_visits.where.not(user_id: nil).count,
"Anonymous" => scoped_visits.where(user_id: nil).count }),
title: "Login status" %>
</div>
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6"><%= pie_chart scoped_visits.group(:device_type).count, title: "Device Type" %></div>
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6"><%= pie_chart scoped_visits.group(:utm_source).count, title: "UTM Sources" %></div>
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6"><%= pie_chart scoped_visits.group(:referring_domain).count, title: "Referring Domains" %></div>
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6"><%= pie_chart scoped_visits.group(:browser).count, title: "Browsers" %></div>
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6">
<%= chart_card("Login status") do %>
<%= pie_chart({ "Logged In" => scoped_visits.where.not(user_id: nil).count,
"Anonymous" => scoped_visits.where(user_id: nil).count }) %>
<% end %>
<%= chart_card("Device Type") { pie_chart scoped_visits.group(:device_type).count } %>
<%= chart_card("UTM Sources") { pie_chart scoped_visits.group(:utm_source).count } %>
<%= chart_card("Referring Domains") { pie_chart scoped_visits.group(:referring_domain).count } %>
<%= chart_card("Browsers") { pie_chart scoped_visits.group(:browser).count } %>
<%= chart_card("New vs Returning Visitors") do %>
<%= pie_chart({
"New" => scoped_visits.group(:visitor_token).having("count(*) = 1").count.size,
"Returning" => scoped_visits.group(:visitor_token).having("count(*) > 1").count.size
}, title: "New vs Returning Visitors") %>
</div>
}) %>
<% end %>
</div>
</div>
</section>
Expand Down
Loading