Update styling and refine features for analytics page (#20410)

* Update styling and refine features for analytics page

* Remove follower reference from js

* Update html

* Adjust test
This commit is contained in:
Ben Halpern 2023-11-30 16:07:16 -05:00 committed by GitHub
parent 67c2fe11d4
commit 9eaff67318
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 87 additions and 68 deletions

View file

@ -259,13 +259,16 @@
}
.analytics-container {
width: 1400px;
max-width: 1400px;
margin: 0 auto;
}
.crayons-tabs--analytics {
width: min-content;
margin: var(--su-8) auto;
margin: var(--su-1) auto var(--su-4);
@media screen and (min-width: 800px) {
margin: var(--su-1) var(--su-4) var(--su-4);
}
}
$column-flex: 1;
@ -277,6 +280,7 @@ $column-flex: 1;
&__col {
flex: $column-flex;
min-height: 100px;
}
}
@ -285,13 +289,19 @@ $column-flex: 1;
}
.graphs {
text-align: center;
display: flex;
justify-content: space-between;
&__col {
flex: $column-flex;
}
.charts-container {
padding: var(--su-4);
height: 320px;
canvas {
min-height: 280px;
max-height: 280px;
}
}
}
@media screen and (max-width: $breakpoint-s) {
@ -357,6 +367,7 @@ $column-flex: 1;
.featured-stat {
font-size: 2em;
font-weight: bold;
}
.header-card {

View file

@ -207,6 +207,7 @@ class ArticlesController < ApplicationController
def stats
authorize @article
@organization_id = @article.organization_id
@reactions = @article.reactions.public_category.order(created_at: :desc).limit(500).includes(:user)
end
def admin_unpublish

View file

@ -42,6 +42,7 @@ class DashboardsController < ApplicationController
end
@reactions_count = @articles.sum(&:public_reactions_count)
@comments_count = @articles.sum(&:comments_count)
@page_views_count = @articles.sum(&:page_views_count)
@articles = @articles.includes(:collection).sorting(params[:sort]).decorate

View file

@ -31,17 +31,14 @@ function writeCards(data, timeRangeLabel) {
const readers = sumAnalytics(data, 'page_views');
const reactions = sumAnalytics(data, 'reactions');
const comments = sumAnalytics(data, 'comments');
const follows = sumAnalytics(data, 'follows');
const reactionCard = document.getElementById('reactions-card');
const commentCard = document.getElementById('comments-card');
const followerCard = document.getElementById('followers-card');
const readerCard = document.getElementById('readers-card');
readerCard.innerHTML = cardHTML(readers, `Readers ${timeRangeLabel}`);
commentCard.innerHTML = cardHTML(comments, `Comments ${timeRangeLabel}`);
reactionCard.innerHTML = cardHTML(reactions, `Reactions ${timeRangeLabel}`);
followerCard.innerHTML = cardHTML(follows, `Followers ${timeRangeLabel}`);
}
function drawChart({ id, showPoints = true, title, labels, datasets }) {
@ -121,7 +118,6 @@ function drawCharts(data, timeRangeLabel) {
const likes = parsedData.map((date) => date.reactions.like);
const readingList = parsedData.map((date) => date.reactions.readinglist);
const unicorns = parsedData.map((date) => date.reactions.unicorn);
const followers = parsedData.map((date) => date.follows.total);
const readers = parsedData.map((date) => date.page_views.total);
// When timeRange is "Infinity" we hide the points to avoid over-crowding the UI
@ -185,23 +181,6 @@ function drawCharts(data, timeRangeLabel) {
],
});
drawChart({
id: 'followers-chart',
showPoints,
title: `New Followers ${timeRangeLabel}`,
labels,
datasets: [
{
label: 'Followers',
data: followers,
fill: false,
borderColor: 'rgb(10, 133, 255)',
backgroundColor: 'rgb(10, 133, 255)',
lineTension: 0.1,
},
],
});
drawChart({
id: 'readers-chart',
showPoints,
@ -250,20 +229,21 @@ function renderReferrers(data) {
}
function removeCardElements() {
const el = document.getElementsByClassName("summary-stats")[0];
const el = document.getElementsByClassName('summary-stats')[0];
el && el.remove();
}
function showErrorsOnCharts() {
const target = ['reactions-chart', 'comments-chart', 'followers-chart', 'readers-chart'];
target.forEach(id => {
const target = ['reactions-chart', 'comments-chart', 'readers-chart'];
target.forEach((id) => {
const el = document.getElementById(id);
el.outerHTML = `<p class="m-5" id="${id}">Failed to fetch chart data. If this error persists for a minute, you can try to disable adblock etc. on this page or site.</p>`;
});
}
function showErrorsOnReferrers() {
document.getElementById('referrers-container').outerHTML = '<p class="m-5" id="referrers-container">Failed to fetch referrer data. If this error persists for a minute, you can try to disable adblock etc. on this page or site.</p>';
document.getElementById('referrers-container').outerHTML =
'<p class="m-5" id="referrers-container">Failed to fetch referrer data. If this error persists for a minute, you can try to disable adblock etc. on this page or site.</p>';
}
function callAnalyticsAPI(date, timeRangeLabel, { organizationId, articleId }) {

View file

@ -176,6 +176,14 @@ class Reaction < ApplicationRecord
ReactionCategory[category.to_sym]
end
def readable_date
if created_at.year == Time.current.year
I18n.l(created_at, format: :short)
else
I18n.l(created_at, format: :short_with_yy)
end
end
private
def update_reactable

View file

@ -1,7 +1,8 @@
<% title t("views.stats.meta.title") %>
<main id="main-content">
<h1 class="fs-4xl fw-medium align-center my-5"><%= t("views.stats.heading_html", article: link_to(@article.title, @article.current_state_path, id: "article", data: { "article-id" => @article.id, "organization-id" => @organization_id })) %></h1>
<main class="dashboard-container analytics-container crayons-layout" id="main-content">
<h1 class="fs-4xl fw-bold mt-5 mx-5">
<%= link_to(@article.title, @article.current_state_path, id: "article", data: { "article-id" => @article.id, "organization-id" => @organization_id }) %></h1>
<%= render "shared/stats" %>
</main>

View file

@ -1,11 +1,16 @@
<% num_views = @page_views_count %>
<div class="grid grid-cols-2 m:grid-cols-4 gap-2 m:gap-4 pt-3">
<div class="grid grid-cols-2 m:grid-cols-3 gap-2 m:gap-4 pt-3">
<div class="crayons-card crayons-card--secondary p-3 m:p-6">
<strong class="fs-2xl m:fs-3xl lh-tight color-base-90"><%= number_with_delimiter(@reactions_count, delimiter: ",") %></strong>
<span class="color-base-60 block fs-base"><%= t("views.dashboard.summary.reactions") %></span>
</div>
<div class="crayons-card crayons-card--secondary p-3 m:p-6">
<strong class="fs-2xl m:fs-3xl lh-tight color-base-90"><%= number_with_delimiter(@comments_count, delimiter: ",") %></strong>
<span class="color-base-60 block fs-base"><%= t("views.dashboard.summary.comments") %></span>
</div>
<div class="crayons-card crayons-card--secondary p-3 m:p-6">
<strong class="fs-2xl m:fs-3xl lh-tight color-base-90"><%= num_views > 500 ? number_with_delimiter(num_views, delimiter: ",") : t("views.dashboard.summary.lt_500") %></strong>
<span class="color-base-60 block fs-base"><%= t("views.dashboard.summary.views") %></span>

View file

@ -1,7 +1,7 @@
<% title t("views.dashboard.analytics.meta.title") %>
<main class="dashboard-container analytics-container crayons-layout" id="main-content">
<nav aria-label="Dashboards">
<nav aria-label="Dashboards" class="mx-5">
<ul class="list-none">
<li>
<a href="<%= dashboard_path %>" class="rounded-btn crayons-link--block rounded-btn--transparent"><%= t("views.dashboard.analytics.back") %></a>
@ -30,11 +30,7 @@
</nav>
<div class="crayons-card p-3 mt-5">
<h1 class="fs-4xl fw-medium"><%= t("views.dashboard.analytics.heading", name: @user_or_org.name) %></h1>
<p><%= t("views.dashboard.analytics.desc_1", members: Settings::Community.member_label) %></p>
<p><%= t("views.dashboard.analytics.desc_2", members: Settings::Community.member_label) %></p>
</div>
<h1 class="fs-4xl fw-bold mx-5"><%= t("views.dashboard.analytics.heading", name: @user_or_org.name) %></h1>
<%= render "shared/stats" %>
</main>

View file

@ -22,43 +22,35 @@
<div class="summary-stats__col crayons-card mx-5">
<div id="comments-card" class="py-3"></div>
</div>
<div class="summary-stats__col crayons-card mx-5">
<div id="followers-card" class="py-3"></div>
</div>
</div>
<div class="graphs my-5">
<div class="graphs my-2">
<div class="graphs__col crayons-card mx-5">
<h2 class="my-5"><%= t("views.stats.readers") %></h2>
<h2 class="mt-5 mx-5"><%= t("views.stats.readers") %></h2>
<div class="charts-container">
<canvas id="readers-chart"></canvas>
</div>
</div>
<div class="graphs__col crayons-card mx-5">
<h2 class="my-5"><%= t("views.stats.followers") %></h2>
<div class="charts-container">
<canvas id="followers-chart"></canvas>
</div>
</div>
</div>
<div class="graphs mb-5">
<div class="graphs__col crayons-card mx-5">
<h2 class="my-5"><%= t("views.stats.reactions") %></h2>
<div class="grid xl:grid-cols-2 gap-4 mx-5 mb-2">
<div class="graphs__col crayons-card">
<h2 class="mt-5 mx-5"><%= t("views.stats.reactions") %></h2>
<div class="charts-container">
<canvas id="reactions-chart"></canvas>
</div>
</div>
<div class="graphs__col crayons-card mx-5">
<h2 class="my-5"><%= t("views.stats.comments") %></h2>
<div class="graphs__col crayons-card">
<h2 class="mt-5 mx-5"><%= t("views.stats.comments") %></h2>
<div class="charts-container">
<canvas id="comments-chart"></canvas>
</div>
</div>
</div>
<div class="w-90 crayons-card mx-5 mb-5 p-5">
<h2 class="align-center mb-5"><%= t("views.stats.traffic.text") %></h2>
<div class="grid xl:grid-cols-2 gap-4 mx-5 ">
<div class="crayons-card p-2">
<h2 class="mt-5 mx-5"><%= t("views.stats.traffic.text") %></h2>
<table class="w-100 m-auto crayons-table">
<thead>
<tr>
@ -70,4 +62,30 @@
<tbody id="referrers-container">
</tbody>
</table>
</div>
<% if @reactions && @reactions.any? %>
<div class="crayons-card p-4 overflow-auto" style="max-height:calc(100vh - 150px)">
<h2 class="crayons-subtitle mb-2">
<%= t("views.stats.reactions_history") %>
</h2>
<% @reactions.each do |reaction| %>
<div class="fs-sm py-2 flex items-center">
<div class="mr-3 relative">
<img class="crayons-avatar h-8 w-8" style="height: 30px; width: 30px;" src="<%= reaction.user.profile_image_90 %>" width="25" height="25">
<img class="crayons-avatar absolute -right-1 -bottom-1 border-base-inverted" style="height: 20px; width: 20px;" src="/assets/<%= reaction.reaction_category.icon %>.svg" width="16" height="16">
</div>
<div class="flex-1 flex items-center justify-between">
<div>
<strong><%= reaction.category %></strong>
by <a href="<%= URL.user(reaction.user) %>" class="fw-bold"><%= reaction.user.name %></a>
</div>
<div class="fs-xs">
<%= reaction.readable_date %>
</div>
</div>
</div>
<% end %>
</div>
<% end %>
</div>

View file

@ -29,10 +29,8 @@ en:
analytics:
meta:
title: Analytics
heading: Analytics Dashboard for %{name}
desc_1: Welcome to the Analytics Dashboard, the home of in-depth user metrics so that authors can make data-driven decisions about the %{members} ecosystem.
desc_2: This dashboard highlights deep insights especially relevant to %{members} relations, authors, and serious bloggers.
back: "👈 Back to Main Dashboard"
heading: Analytics for %{name}
back: "Back to Main Dashboard"
your: Your Dashboard
org: "%{org} Analytics Dashboard"
article:
@ -154,6 +152,7 @@ en:
summary:
reactions: Total post reactions
views: Total post views
comments: Total post comments
listings: Listings created
credits: Credits available
lt_500: "< 500"

View file

@ -30,9 +30,7 @@ fr:
meta:
title: Analytics
heading: Analytics Dashboard for %{name}
desc_1: Welcome to the Analytics Dashboard, the home of in-depth user metrics so that authors can make data-driven decisions about the %{members} ecosystem.
desc_2: This dashboard highlights deep insights especially relevant to %{members} relations, authors, and serious bloggers.
back: "👈 Back to Main Dashboard"
back: "Back to Main Dashboard"
your: Your Dashboard
org: "%{org} Analytics Dashboard"
article:

View file

@ -116,12 +116,13 @@ en:
infinity: Infinity
readers: Readers Summary
followers: New Followers Summary
reactions: "Reactions Summary ❤🦄🔖"
comments: "Comments Summary 💬"
reactions: "Reactions Summary"
comments: "Comments Summary"
traffic:
text: "Traffic Source Summary 🚦"
text: "Traffic Source Summary"
source: Source
views: Views
reactions_history: "Reactions History"
sticky:
joined: Joined
default_cta: Learn more

View file

@ -65,7 +65,7 @@ describe('Analytics navigation', () => {
it('should navigate to correct organization analytics dashboard', () => {
cy.findByText('Bachmanity Analytics Dashboard').click();
cy.contains('h1', 'Analytics Dashboard for Bachmanity');
cy.contains('h1', 'Analytics for Bachmanity');
});
});
});

View file

@ -48,7 +48,7 @@ describe('Posts Dashboard', () => {
name: 'More options for post: Test Article',
}).click();
cy.findByRole('link', { name: 'Stats' }).click();
cy.findByRole('heading', { name: 'Stats for "Test Article"' });
cy.findByRole('heading', { name: 'Test Article' });
// Go back to the dashboard, otherwise it's possible for Cypress to detect an error as the next beforeEach hook runs
// (due to the logged out user not being able to view the stats page)