DEV: Add site age and site activities section to the redesigned about page (#28214)

This commit continues on work laid out by 6039b513fe to redesign the /about page. In this commit, we add the site age and a section on the right hand side to show site activities/statistics such as topics, posts, sign-ups, likes etc.
This commit is contained in:
Osama Sayegh 2024-08-07 11:11:41 +03:00 committed by GitHub
parent 1a09d6b246
commit 5dbf812d32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 379 additions and 2 deletions

View File

@ -2,6 +2,7 @@ import Component from "@glimmer/component";
import { hash } from "@ember/helper"; import { hash } from "@ember/helper";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
import PluginOutlet from "discourse/components/plugin-outlet"; import PluginOutlet from "discourse/components/plugin-outlet";
import { number } from "discourse/lib/formatter";
import dIcon from "discourse-common/helpers/d-icon"; import dIcon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n"; import i18n from "discourse-common/helpers/i18n";
import escape from "discourse-common/lib/escape"; import escape from "discourse-common/lib/escape";
@ -46,6 +47,61 @@ export default class AboutPage extends Component {
}), }),
}), }),
}, },
{
class: "site-creation-date",
icon: "calendar-alt",
text: this.siteAgeString,
},
];
}
get siteActivities() {
return [
{
icon: "scroll",
class: "topics",
activityText: I18n.t("about.activities.topics", {
count: this.args.model.stats.topics_7_days,
formatted_number: number(this.args.model.stats.topics_7_days),
}),
period: I18n.t("about.activities.periods.last_7_days"),
},
{
icon: "pencil-alt",
class: "posts",
activityText: I18n.t("about.activities.posts", {
count: this.args.model.stats.posts_last_day,
formatted_number: number(this.args.model.stats.posts_last_day),
}),
period: I18n.t("about.activities.periods.today"),
},
{
icon: "user-friends",
class: "active-users",
activityText: I18n.t("about.activities.active_users", {
count: this.args.model.stats.active_users_7_days,
formatted_number: number(this.args.model.stats.active_users_7_days),
}),
period: I18n.t("about.activities.periods.last_7_days"),
},
{
icon: "user-plus",
class: "sign-ups",
activityText: I18n.t("about.activities.sign_ups", {
count: this.args.model.stats.users_7_days,
formatted_number: number(this.args.model.stats.users_7_days),
}),
period: I18n.t("about.activities.periods.last_7_days"),
},
{
icon: "heart",
class: "likes",
activityText: I18n.t("about.activities.likes", {
count: this.args.model.stats.likes_count,
formatted_number: number(this.args.model.stats.likes_count),
}),
period: I18n.t("about.activities.periods.all_time"),
},
]; ];
} }
@ -66,6 +122,22 @@ export default class AboutPage extends Component {
} }
} }
get siteAgeString() {
const creationDate = new Date(this.args.model.site_creation_date);
let diff = new Date() - creationDate;
diff /= 1000 * 3600 * 24 * 30;
if (diff < 1) {
return I18n.t("about.site_age.less_than_one_month");
} else if (diff < 12) {
return I18n.t("about.site_age.month", { count: Math.round(diff) });
} else {
diff /= 12;
return I18n.t("about.site_age.year", { count: Math.round(diff) });
}
}
<template> <template>
<section class="about__header"> <section class="about__header">
<img class="about__banner" src={{@model.banner_image}} /> <img class="about__banner" src={{@model.banner_image}} />
@ -91,11 +163,29 @@ export default class AboutPage extends Component {
<div>{{htmlSafe @model.extended_site_description}}</div> <div>{{htmlSafe @model.extended_site_description}}</div>
</section> </section>
<section class="about__right-side"> <section class="about__right-side">
<h4>{{i18n "about.contact"}}</h4> <h3>{{i18n "about.contact"}}</h3>
{{#if this.contactInfo}} {{#if this.contactInfo}}
<p>{{htmlSafe this.contactInfo}}</p> <p>{{htmlSafe this.contactInfo}}</p>
{{/if}} {{/if}}
<p>{{i18n "about.report_inappropriate_content"}}</p> <p>{{i18n "about.report_inappropriate_content"}}</p>
<h3>{{i18n "about.site_activity"}}</h3>
<div class="about__activities">
{{#each this.siteActivities as |activity|}}
<div class="about__activities-item {{activity.class}}">
<span class="about__activities-item-icon">{{dIcon
activity.icon
}}</span>
<span class="about__activities-item-type">
<div
class="about__activities-item-count"
>{{activity.activityText}}</div>
<div
class="about__activities-item-period"
>{{activity.period}}</div>
</span>
</div>
{{/each}}
</div>
</section> </section>
</div> </div>
</template> </template>

View File

@ -1,7 +1,7 @@
.about { .about {
&__main-content { &__main-content {
display: grid; display: grid;
grid-template-columns: 2fr 1fr; grid-template-columns: 2.5fr 1fr;
column-gap: 4em; column-gap: 4em;
} }
@ -23,6 +23,20 @@
min-height: 300px; min-height: 300px;
max-height: 300px; max-height: 300px;
} }
&__activities-item {
display: flex;
align-items: center;
margin-bottom: 1.5em;
}
&__activities-item-icon {
margin-right: 1em;
}
&__activities-item-period {
font-size: var(--font-down-2);
}
} }
section.about { section.about {

View File

@ -64,6 +64,10 @@ class About
GlobalPath.full_cdn_url(url) GlobalPath.full_cdn_url(url)
end end
def site_creation_date
Discourse.site_creation_date
end
def moderators def moderators
@moderators ||= User.where(moderator: true, admin: false).human_users.order("last_seen_at DESC") @moderators ||= User.where(moderator: true, admin: false).human_users.order("last_seen_at DESC")
end end

View File

@ -22,6 +22,7 @@ class AboutSerializer < ApplicationSerializer
:description, :description,
:extended_site_description, :extended_site_description,
:banner_image, :banner_image,
:site_creation_date,
:title, :title,
:locale, :locale,
:version, :version,
@ -62,6 +63,10 @@ class AboutSerializer < ApplicationSerializer
render_redesigned_about_page? render_redesigned_about_page?
end end
def include_site_creation_date?
render_redesigned_about_page?
end
private private
def can_see_about_stats def can_see_about_stats

View File

@ -345,6 +345,27 @@ en:
active_user_count: "Active users" active_user_count: "Active users"
contact: "Contact us" contact: "Contact us"
contact_info: "In the event of a critical issue or urgent matter affecting this site, please contact us at %{contact_info}." contact_info: "In the event of a critical issue or urgent matter affecting this site, please contact us at %{contact_info}."
site_activity: "Site activity"
activities:
topics:
one: "%{formatted_number} topic"
other: "%{formatted_number} topics"
posts:
one: "%{formatted_number} post"
other: "%{formatted_number} posts"
active_users:
one: "%{formatted_number} active user"
other: "%{formatted_number} active users"
sign_ups:
one: "%{formatted_number} sign-up"
other: "%{formatted_number} sign-ups"
likes:
one: "%{formatted_number} like"
other: "%{formatted_number} likes"
periods:
last_7_days: "in the last 7 days"
today: "today"
all_time: "all time"
member_count: member_count:
one: "%{formatted_number} Member" one: "%{formatted_number} Member"
other: "%{formatted_number} Members" other: "%{formatted_number} Members"
@ -355,6 +376,14 @@ en:
one: "%{formatted_number} Moderator" one: "%{formatted_number} Moderator"
other: "%{formatted_number} Moderators" other: "%{formatted_number} Moderators"
report_inappropriate_content: "If you come across any inappropriate content, don't hesitate to start a conversation with our moderators and admins. Remember to log in before reaching out." report_inappropriate_content: "If you come across any inappropriate content, don't hesitate to start a conversation with our moderators and admins. Remember to log in before reaching out."
site_age:
less_than_one_month: "Created < 1 month ago"
month:
one: "Created %{count} month ago"
other: "Created %{count} months ago"
year:
one: "Created %{count} year ago"
other: "Created %{count} years ago"
bookmarked: bookmarked:
title: "Bookmark" title: "Bookmark"

View File

@ -1034,6 +1034,24 @@ module Discourse
[SiteSetting.tos_topic_id, SiteSetting.guidelines_topic_id, SiteSetting.privacy_topic_id] [SiteSetting.tos_topic_id, SiteSetting.guidelines_topic_id, SiteSetting.privacy_topic_id]
end end
def self.site_creation_date
@creation_dates ||= {}
current_db = RailsMultisite::ConnectionManagement.current_db
@creation_dates[current_db] ||= begin
result = DB.query_single <<~SQL
SELECT created_at
FROM schema_migration_details
ORDER BY created_at
LIMIT 1
SQL
result.first
end
end
def self.clear_site_creation_date_cache
@creation_dates = {}
end
cattr_accessor :last_ar_cache_reset cattr_accessor :last_ar_cache_reset
def self.reset_active_record_cache_if_needed(e) def self.reset_active_record_cache_if_needed(e)

View File

@ -201,6 +201,7 @@ module SvgSprite
reply reply
robot robot
rocket rocket
scroll
search search
search-plus search-plus
search-minus search-minus

View File

@ -63,5 +63,113 @@ describe "About page", type: :system do
expect(about_page).to have_admins_count(1, "1") expect(about_page).to have_admins_count(1, "1")
expect(about_page).to have_moderators_count(1, "1") expect(about_page).to have_moderators_count(1, "1")
end end
describe "displayed site age" do
it "says less than 1 month if the site is less than 1 month old" do
Discourse.stubs(:site_creation_date).returns(1.week.ago)
about_page.visit
expect(about_page).to have_site_created_less_than_1_month_ago
end
it "says how many months old the site is if the site is less than 1 year old" do
Discourse.stubs(:site_creation_date).returns(2.months.ago)
about_page.visit
expect(about_page).to have_site_created_in_months_ago(2)
end
it "says how many years old the site is if the site is more than 1 year old" do
Discourse.stubs(:site_creation_date).returns(5.years.ago)
about_page.visit
expect(about_page).to have_site_created_in_years_ago(5)
end
end
describe "the site activity section" do
describe "topics" do
before do
Fabricate(:topic, created_at: 2.days.ago)
Fabricate(:topic, created_at: 3.days.ago)
Fabricate(:topic, created_at: 8.days.ago)
end
it "shows the count of topics created in the last 7 days" do
about_page.visit
expect(about_page.site_activities.topics).to have_count(2, "2")
expect(about_page.site_activities.topics).to have_7_days_period
end
end
describe "posts" do
before do
Fabricate(:post, created_at: 2.days.ago)
Fabricate(:post, created_at: 1.hour.ago)
Fabricate(:post, created_at: 3.hours.ago)
Fabricate(:post, created_at: 23.hours.ago)
end
it "shows the count of topics created in the last day" do
about_page.visit
expect(about_page.site_activities.posts).to have_count(3, "3")
expect(about_page.site_activities.posts).to have_1_day_period
end
end
describe "active users" do
before do
User.update_all(last_seen_at: 1.month.ago)
Fabricate(:user, last_seen_at: 1.hour.ago)
Fabricate(:user, last_seen_at: 1.day.ago)
Fabricate(:user, last_seen_at: 3.days.ago)
Fabricate(:user, last_seen_at: 6.days.ago)
Fabricate(:user, last_seen_at: 8.days.ago)
end
it "shows the count of active users in the last 7 days" do
about_page.visit
expect(about_page.site_activities.active_users).to have_count(5, "5") # 4 fabricated above + 1 for the current user
expect(about_page.site_activities.active_users).to have_7_days_period
end
end
describe "sign ups" do
before do
User.update_all(created_at: 1.month.ago)
Fabricate(:user, created_at: 3.hours.ago)
Fabricate(:user, created_at: 3.days.ago)
Fabricate(:user, created_at: 8.days.ago)
end
it "shows the count of signups in the last 7 days" do
about_page.visit
expect(about_page.site_activities.sign_ups).to have_count(2, "2")
expect(about_page.site_activities.sign_ups).to have_7_days_period
end
end
describe "likes" do
before do
UserAction.destroy_all
Fabricate(:user_action, created_at: 1.hour.ago, action_type: UserAction::LIKE)
Fabricate(:user_action, created_at: 1.day.ago, action_type: UserAction::LIKE)
Fabricate(:user_action, created_at: 1.month.ago, action_type: UserAction::LIKE)
Fabricate(:user_action, created_at: 10.years.ago, action_type: UserAction::LIKE)
end
it "shows the count of likes of all time" do
about_page.visit
expect(about_page.site_activities.likes).to have_count(4, "4")
expect(about_page.site_activities.likes).to have_all_time_period
end
end
end
end end
end end

View File

@ -0,0 +1,48 @@
# frozen_string_literal: true
module PageObjects
module Components
class AboutPageSiteActivity < PageObjects::Components::Base
attr_reader :container
def initialize(container)
@container = container
end
def topics
AboutPageSiteActivityItem.new(
container.find(".about__activities-item.topics"),
translation_key: "about.activities.topics",
)
end
def posts
AboutPageSiteActivityItem.new(
container.find(".about__activities-item.posts"),
translation_key: "about.activities.posts",
)
end
def active_users
AboutPageSiteActivityItem.new(
container.find(".about__activities-item.active-users"),
translation_key: "about.activities.active_users",
)
end
def sign_ups
AboutPageSiteActivityItem.new(
container.find(".about__activities-item.sign-ups"),
translation_key: "about.activities.sign_ups",
)
end
def likes
AboutPageSiteActivityItem.new(
container.find(".about__activities-item.likes"),
translation_key: "about.activities.likes",
)
end
end
end
end

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
module PageObjects
module Components
class AboutPageSiteActivityItem < PageObjects::Components::Base
attr_reader :container
def initialize(container, translation_key:)
@container = container
@translation_key = translation_key
end
def has_count?(count, formatted_number)
container.find(".about__activities-item-count").has_text?(
I18n.t("js.#{@translation_key}", count: count, formatted_number:),
)
end
def has_1_day_period?
period_element.has_text?(I18n.t("js.about.activities.periods.today"))
end
def has_7_days_period?
period_element.has_text?(I18n.t("js.about.activities.periods.last_7_days"))
end
def has_all_time_period?
period_element.has_text?(I18n.t("js.about.activities.periods.all_time"))
end
private
def period_element
container.find(".about__activities-item-period")
end
end
end
end

View File

@ -33,6 +33,28 @@ module PageObjects
element = find(".about__stats-item.moderators span") element = find(".about__stats-item.moderators span")
element.has_text?(I18n.t("js.about.moderator_count", count:, formatted_number:)) element.has_text?(I18n.t("js.about.moderator_count", count:, formatted_number:))
end end
def has_site_created_less_than_1_month_ago?
site_age_stat_element.has_text?(I18n.t("js.about.site_age.less_than_one_month"))
end
def has_site_created_in_months_ago?(months)
site_age_stat_element.has_text?(I18n.t("js.about.site_age.month", count: months))
end
def has_site_created_in_years_ago?(years)
site_age_stat_element.has_text?(I18n.t("js.about.site_age.year", count: years))
end
def site_activities
PageObjects::Components::AboutPageSiteActivity.new(find(".about__activities"))
end
private
def site_age_stat_element
find(".about__stats-item.site-creation-date span")
end
end end
end end
end end