You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

233 lines
9.8 KiB

<%= content_for :title, "Analytics" %>
<%= turbo_frame_tag 'main' do %>
<%= turbo_stream.append 'flash', partial: 'layouts/flash' %>
<div class="list-title">
<h1><%= yield(:title) %></h1>
<div class="analytics-periods">
<% { "all" => "All time", "30d" => "30 days", "7d" => "7 days", "24h" => "24 hours" }.each do |key, label| %>
<%= link_to label,
url_for(period: key),
class: class_names("analytics-period", current: period == key),
data: { turbo_frame: "main", turbo_action: "advance" } %>
<% end %>
</div>
</div>
<div class="analytics">
<% if DemoActivity::ENABLED %>
<p class="analytics-warning">
<strong>Demo data is on.</strong>
<code>DemoActivity</code> keeps inventing players with no gameplay to feed the leaderboard
banner, so the counts below are inflated and the funnel shows a large drop before "Facts".
Set <code>DemoActivity::ENABLED = false</code> and wipe the players table before go-live.
</p>
<% end %>
<section class="analytics-cards">
<%= render "card", label: "Players started", value: number_with_delimiter(@analytics.total_players) %>
<%= render "card", label: "Finished", value: number_with_delimiter(@analytics.completed_players),
sub: "#{@analytics.completion_rate}% of starters" %>
<%= render "card", label: "Median play time", value: format_duration(@analytics.median_duration) %>
<%= render "card", label: "Average score", value: (@analytics.average_score || "—") %>
</section>
<section class="analytics-panel">
<h2>Did you like this game?</h2>
<% ratings = @analytics.ratings %>
<% if ratings[:total].zero? %>
<p class="analytics-empty">No votes yet.</p>
<% else %>
<div class="analytics-cards">
<%= render "card", label: "Thumbs up", value: number_with_delimiter(ratings[:up]),
sub: "#{ratings[:up_share]}%", modifier: "positive" %>
<%= render "card", label: "Thumbs down", value: number_with_delimiter(ratings[:down]),
sub: "#{ratings[:down_share]}%", modifier: "negative" %>
<%= render "card", label: "Votes", value: number_with_delimiter(ratings[:total]),
sub: "#{ratings[:response_rate]}% of players who reached the results" %>
</div>
<div class="analytics-split">
<div class="analytics-split-part positive" style="flex-grow: <%= ratings[:up] %>"></div>
<div class="analytics-split-part negative" style="flex-grow: <%= ratings[:down] %>"></div>
</div>
<% end %>
</section>
<section class="analytics-panel">
<h2>Funnel</h2>
<p class="analytics-note">
How far players got. The last-save early exit is a branch off the stages, so it is
reported separately below rather than as a funnel step.
</p>
<div class="analytics-bars">
<% @analytics.funnel.each do |row| %>
<div class="analytics-bar-row">
<div class="analytics-bar-label"><%= row[:label] %></div>
<div class="analytics-bar-track">
<div class="analytics-bar-fill" style="width: <%= row[:share] %>%"></div>
</div>
<div class="analytics-bar-value">
<strong><%= number_with_delimiter(row[:reached]) %></strong>
<span><%= row[:share] %>%</span>
</div>
<div class="analytics-bar-delta">
<% if row[:lost] > 0 %>
<span class="negative">-<%= number_with_delimiter(row[:lost]) %> (<%= row[:lost_share] %>%)</span>
<% end %>
</div>
</div>
<% end %>
</div>
</section>
<section class="analytics-panel">
<h2>Where unfinished players stopped</h2>
<% if @analytics.drop_off_points.empty? %>
<p class="analytics-empty">Nobody has abandoned a game yet.</p>
<% else %>
<div class="analytics-bars">
<% max = @analytics.drop_off_points.first[:count] %>
<% @analytics.drop_off_points.each do |row| %>
<div class="analytics-bar-row">
<div class="analytics-bar-label"><%= row[:label] %></div>
<div class="analytics-bar-track">
<div class="analytics-bar-fill negative" style="width: <%= (row[:count] * 100.0 / max).round(1) %>%"></div>
</div>
<div class="analytics-bar-value"><strong><%= number_with_delimiter(row[:count]) %></strong></div>
<div class="analytics-bar-delta"></div>
</div>
<% end %>
</div>
<% end %>
</section>
<section class="analytics-panel">
<h2>Answers by stage</h2>
<% @analytics.stages.each do |stage| %>
<div class="analytics-stage">
<h3>
<span class="analytics-stage-index"><%= stage[:index] %></span>
<%= stage[:node].title %>
<small><%= number_with_delimiter(stage[:total]) %> answers</small>
</h3>
<div class="analytics-bars">
<% stage[:answers].each do |answer| %>
<div class="analytics-bar-row">
<div class="analytics-bar-label">
<%= answer[:label] %>
<%= tag.em(answer[:node].template.humanize) %>
</div>
<div class="analytics-bar-track">
<div class="analytics-bar-fill <%= answer_tone(answer[:node]) %>" style="width: <%= answer[:share] %>%"></div>
</div>
<div class="analytics-bar-value">
<strong><%= number_with_delimiter(answer[:count]) %></strong>
<span><%= answer[:share] %>%</span>
</div>
<div class="analytics-bar-delta"></div>
</div>
<% answer[:outcomes].each do |outcome| %>
<div class="analytics-bar-row is-outcome">
<div class="analytics-bar-label">
↳ <%= outcome[:label] %>
<%= tag.em(outcome[:node].template.humanize) %>
</div>
<div class="analytics-bar-track">
<div class="analytics-bar-fill <%= answer_tone(outcome[:node]) %>" style="width: <%= outcome[:share] %>%"></div>
</div>
<div class="analytics-bar-value">
<strong><%= number_with_delimiter(outcome[:count]) %></strong>
<span><%= outcome[:share] %>% of chances</span>
</div>
<div class="analytics-bar-delta"></div>
</div>
<% end %>
<% end %>
</div>
</div>
<% end %>
</section>
<% if @analytics.last_save[:node] %>
<section class="analytics-panel">
<h2>Last save <small>early exit — <%= number_with_delimiter(@analytics.last_save[:total]) %> players</small></h2>
<% if @analytics.last_save[:total].zero? %>
<p class="analytics-empty">No player has taken the early exit yet.</p>
<% else %>
<div class="analytics-bars">
<% @analytics.last_save[:answers].each do |answer| %>
<div class="analytics-bar-row">
<div class="analytics-bar-label"><%= answer[:label] %></div>
<div class="analytics-bar-track">
<div class="analytics-bar-fill" style="width: <%= answer[:share] %>%"></div>
</div>
<div class="analytics-bar-value">
<strong><%= number_with_delimiter(answer[:count]) %></strong>
<span><%= answer[:share] %>%</span>
</div>
<div class="analytics-bar-delta"></div>
</div>
<% end %>
</div>
<% end %>
</section>
<% end %>
<section class="analytics-panel">
<h2>Endings <small>finished players, excluding early exits</small></h2>
<div class="analytics-bars">
<% @analytics.result_bands.each do |band| %>
<div class="analytics-bar-row">
<div class="analytics-bar-label"><%= t("game.results.#{band[:band]}.headline", default: band[:band].to_s.humanize).html_safe %></div>
<div class="analytics-bar-track">
<div class="analytics-bar-fill <%= band_tone(band[:band]) %>" style="width: <%= band[:share] %>%"></div>
</div>
<div class="analytics-bar-value">
<strong><%= number_with_delimiter(band[:count]) %></strong>
<span><%= band[:share] %>%</span>
</div>
<div class="analytics-bar-delta"></div>
</div>
<% end %>
</div>
</section>
<%= render "breakdown", title: "How they got here",
note: "Where the scan came from. Each printed QR code carries its own " \
"utm_content tag, so the rows below are the physical placements. " \
"The board game QR points at dice.ikeafoundation.org and is counted there, not here.",
rows: @analytics.by_source,
labeller: ->(key) { source_breakdown_label(key) } %>
<div class="analytics-columns">
<%= render "breakdown", title: "Devices", rows: @analytics.by_device,
labeller: ->(key) { device_breakdown_label(key) } %>
<%= render "breakdown", title: "Languages", rows: @analytics.by_locale,
labeller: ->(key) { language_breakdown_label(key) } %>
<%= render "breakdown", title: "Countries", rows: @analytics.by_country,
labeller: ->(key) { country_breakdown_label(key) } %>
</div>
</div>
<% end %>