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:
parent
67c2fe11d4
commit
9eaff67318
14 changed files with 87 additions and 68 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue