FEATURE: participating users statistics (#28322)

Adds a new statistics (hidden from the UI, but available via the API) that tracks daily participating users.

A user is considered as "participating" if they have

- Reacted to a post
- Replied to a topic
- Created a new topic
- Created a new PM
- Sent a chat message
- Reacted to a chat message

Internal ref - t/131013
This commit is contained in:
Régis Hanol 2024-08-12 23:47:13 +02:00 committed by GitHub
parent 5b78bbd138
commit d10fd36319
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 90 additions and 5 deletions

View File

@ -46,6 +46,9 @@ class Stat
Stat.new("users", show_in_ui: true, expose_via_api: true) { Statistics.users }, Stat.new("users", show_in_ui: true, expose_via_api: true) { Statistics.users },
Stat.new("active_users", show_in_ui: true, expose_via_api: true) { Statistics.active_users }, Stat.new("active_users", show_in_ui: true, expose_via_api: true) { Statistics.active_users },
Stat.new("likes", show_in_ui: true, expose_via_api: true) { Statistics.likes }, Stat.new("likes", show_in_ui: true, expose_via_api: true) { Statistics.likes },
Stat.new("participating_users", show_in_ui: false, expose_via_api: true) do
Statistics.participating_users
end,
] ]
end end

View File

@ -3,7 +3,7 @@
class Statistics class Statistics
def self.active_users def self.active_users
{ {
last_day: User.where("last_seen_at > ?", 1.days.ago).count, last_day: User.where("last_seen_at > ?", 1.day.ago).count,
"7_days": User.where("last_seen_at > ?", 7.days.ago).count, "7_days": User.where("last_seen_at > ?", 7.days.ago).count,
"30_days": User.where("last_seen_at > ?", 30.days.ago).count, "30_days": User.where("last_seen_at > ?", 30.days.ago).count,
} }
@ -12,7 +12,7 @@ class Statistics
def self.likes def self.likes
{ {
last_day: last_day:
UserAction.where(action_type: UserAction::LIKE).where("created_at > ?", 1.days.ago).count, UserAction.where(action_type: UserAction::LIKE).where("created_at > ?", 1.day.ago).count,
"7_days": "7_days":
UserAction.where(action_type: UserAction::LIKE).where("created_at > ?", 7.days.ago).count, UserAction.where(action_type: UserAction::LIKE).where("created_at > ?", 7.days.ago).count,
"30_days": "30_days":
@ -23,7 +23,7 @@ class Statistics
def self.posts def self.posts
{ {
last_day: Post.where("created_at > ?", 1.days.ago).count, last_day: Post.where("created_at > ?", 1.day.ago).count,
"7_days": Post.where("created_at > ?", 7.days.ago).count, "7_days": Post.where("created_at > ?", 7.days.ago).count,
"30_days": Post.where("created_at > ?", 30.days.ago).count, "30_days": Post.where("created_at > ?", 30.days.ago).count,
count: Post.count, count: Post.count,
@ -32,7 +32,7 @@ class Statistics
def self.topics def self.topics
{ {
last_day: Topic.listable_topics.where("created_at > ?", 1.days.ago).count, last_day: Topic.listable_topics.where("created_at > ?", 1.day.ago).count,
"7_days": Topic.listable_topics.where("created_at > ?", 7.days.ago).count, "7_days": Topic.listable_topics.where("created_at > ?", 7.days.ago).count,
"30_days": Topic.listable_topics.where("created_at > ?", 30.days.ago).count, "30_days": Topic.listable_topics.where("created_at > ?", 30.days.ago).count,
count: Topic.listable_topics.count, count: Topic.listable_topics.count,
@ -41,10 +41,38 @@ class Statistics
def self.users def self.users
{ {
last_day: User.real.where("created_at > ?", 1.days.ago).count, last_day: User.real.where("created_at > ?", 1.day.ago).count,
"7_days": User.real.where("created_at > ?", 7.days.ago).count, "7_days": User.real.where("created_at > ?", 7.days.ago).count,
"30_days": User.real.where("created_at > ?", 30.days.ago).count, "30_days": User.real.where("created_at > ?", 30.days.ago).count,
count: User.real.count, count: User.real.count,
} }
end end
def self.participating_users
{
last_day: participating_users_count(1.day.ago),
"7_days": participating_users_count(7.days.ago),
"30_days": participating_users_count(30.days.ago),
}
end
private
def self.participating_users_count(date)
subqueries = [
"SELECT DISTINCT user_id FROM user_actions WHERE created_at > :date AND action_type IN (:action_types)",
]
if ActiveRecord::Base.connection.data_source_exists?("chat_messages")
subqueries << "SELECT DISTINCT user_id FROM chat_messages WHERE created_at > :date"
end
if ActiveRecord::Base.connection.data_source_exists?("chat_message_reactions")
subqueries << "SELECT DISTINCT user_id FROM chat_message_reactions WHERE created_at > :date"
end
sql = "SELECT COUNT(user_id) FROM (#{subqueries.join(" UNION ")}) u"
DB.query_single(sql, date: date, action_types: UserAction::USER_ACTED_TYPES).first
end
end end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
RSpec.describe Statistics do
describe "#participating_users" do
it "returns users who have sent a chat message" do
Fabricate(:chat_message)
expect(described_class.participating_users[:last_day]).to eq(1)
end
it "returns users who have reacted to a chat message" do
Fabricate(:chat_message_reaction)
expect(described_class.participating_users[:last_day]).to eq(2) # 2 because the chat message creator is also counted
end
end
end

View File

@ -62,6 +62,8 @@ RSpec.describe DiscourseHub do
expect(json["likes_count"]).to be_present expect(json["likes_count"]).to be_present
expect(json["likes_7_days"]).to be_present expect(json["likes_7_days"]).to be_present
expect(json["likes_30_days"]).to be_present expect(json["likes_30_days"]).to be_present
expect(json["participating_users_7_days"]).to be_present
expect(json["participating_users_30_days"]).to be_present
expect(json["installed_version"]).to be_present expect(json["installed_version"]).to be_present
expect(json["branch"]).to be_present expect(json["branch"]).to be_present
end end

View File

@ -105,6 +105,9 @@ TEXT
:likes_7_days, :likes_7_days,
:likes_30_days, :likes_30_days,
:likes_count, :likes_count,
:participating_users_last_day,
:participating_users_7_days,
:participating_users_30_days,
) )
end end

View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
RSpec.describe Statistics do
describe "#participating_users" do
it "returns no participating users by default" do
pu = described_class.participating_users
expect(pu[:last_day]).to eq(0)
expect(pu[:"7_days"]).to eq(0)
expect(pu[:"30_days"]).to eq(0)
end
it "returns users who have reacted to a post" do
Fabricate(:user_action, action_type: UserAction::LIKE)
expect(described_class.participating_users[:last_day]).to eq(1)
end
it "returns users who have created a new topic" do
Fabricate(:user_action, action_type: UserAction::NEW_TOPIC)
expect(described_class.participating_users[:last_day]).to eq(1)
end
it "returns users who have replied to a post" do
Fabricate(:user_action, action_type: UserAction::REPLY)
expect(described_class.participating_users[:last_day]).to eq(1)
end
it "returns users who have created a new PM" do
Fabricate(:user_action, action_type: UserAction::NEW_PRIVATE_MESSAGE)
expect(described_class.participating_users[:last_day]).to eq(1)
end
end
end

View File

@ -68,6 +68,8 @@ RSpec.describe SiteController do
expect(json["likes_count"]).to be_present expect(json["likes_count"]).to be_present
expect(json["likes_7_days"]).to be_present expect(json["likes_7_days"]).to be_present
expect(json["likes_30_days"]).to be_present expect(json["likes_30_days"]).to be_present
expect(json["participating_users_7_days"]).to be_present
expect(json["participating_users_30_days"]).to be_present
end end
it "is not visible if site setting share_anonymized_statistics is disabled" do it "is not visible if site setting share_anonymized_statistics is disabled" do