diff --git a/app/services/discourse_rewind/action/new_user_interactions.rb b/app/services/discourse_rewind/action/new_user_interactions.rb index 96ce495..30cd7c1 100644 --- a/app/services/discourse_rewind/action/new_user_interactions.rb +++ b/app/services/discourse_rewind/action/new_user_interactions.rb @@ -33,66 +33,45 @@ module DiscourseRewind return if new_user_ids.empty? # Count likes given to new users - likes_scope = - UserAction.where( - acting_user_id: user.id, - user_id: new_user_ids, - action_type: UserAction::WAS_LIKED, - ).where(created_at: date) - likes_given = likes_scope.count - liked_user_ids = likes_scope.distinct.pluck(:user_id) + liked_user_ids = + UserAction + .where( + acting_user_id: user.id, + user_id: new_user_ids, + action_type: UserAction::WAS_LIKED, + ) + .where(created_at: date) + .distinct + .pluck(:user_id) # Count replies to new users' posts - replies_scope = + replied_user_ids = Post .joins( "INNER JOIN posts AS parent_posts ON posts.reply_to_post_number = parent_posts.post_number AND posts.topic_id = parent_posts.topic_id", ) .where(posts: { user_id: user.id, deleted_at: nil, created_at: date }) .where("parent_posts.user_id": new_user_ids) - replies_to_new_users = replies_scope.count - replied_user_ids = replies_scope.distinct.pluck("parent_posts.user_id") - - # Count topics created by user that new users participated in - topics_with_new_users = - Topic - .joins(:posts) - .where(topics: { user_id: user.id, deleted_at: nil }) - .where(posts: { user_id: new_user_ids, deleted_at: nil }) - .where(topics: { created_at: date }) .distinct - .count + .pluck("parent_posts.user_id") - # Count direct messages/mentions to new users - mentions_scope = + # Count direct mentions to new users + mentioned_user_ids = Post .joins( "INNER JOIN user_actions ON user_actions.target_post_id = posts.id AND user_actions.action_type = #{UserAction::MENTION}", ) .where(posts: { user_id: user.id, deleted_at: nil, created_at: date }) .where(user_actions: { user_id: new_user_ids }) - mentions_to_new_users = mentions_scope.distinct.count - mentioned_user_ids = mentions_scope.distinct.pluck("user_actions.user_id") + .distinct + .pluck("user_actions.user_id") # Unique new users interacted with unique_new_users = (liked_user_ids + replied_user_ids + mentioned_user_ids).uniq.count - total_interactions = likes_given + replies_to_new_users + mentions_to_new_users + return if unique_new_users == 0 - return if total_interactions == 0 - - { - data: { - total_interactions: total_interactions, - likes_given: likes_given, - replies_to_new_users: replies_to_new_users, - mentions_to_new_users: mentions_to_new_users, - topics_with_new_users: topics_with_new_users, - unique_new_users: unique_new_users, - new_users_count: new_user_ids.count, - }, - identifier: "new-user-interactions", - } + { data: { unique_new_users: unique_new_users }, identifier: "new-user-interactions" } end end end diff --git a/app/services/discourse_rewind/action/writing_analysis.rb b/app/services/discourse_rewind/action/writing_analysis.rb new file mode 100644 index 0000000..86aaf6f --- /dev/null +++ b/app/services/discourse_rewind/action/writing_analysis.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +module DiscourseRewind + module Action + class WritingAnalysis < BaseReport + FakeData = { + data: { + total_words: 45_230, + total_posts: 197, + average_post_length: 230, + readability_score: 65.4, + }, + identifier: "writing-analysis", + } + + def call + return FakeData if Rails.env.development? + + total_words = + DB.query_single(<<~SQL, user_id: user.id, date_start: date.first, date_end: date.last) + SELECT SUM(word_count) FROM posts + WHERE user_id = :user_id + AND created_at BETWEEN :date_start AND :date_end + AND deleted_at IS NULL + SQL + + post_count = + DB.query_single(<<~SQL, user_id: user.id, date_start: date.first, date_end: date.last) + SELECT COUNT(*) FROM posts + WHERE user_id = :user_id + AND created_at BETWEEN :date_start AND :date_end + AND deleted_at IS NULL + SQL + + average_post_length = + post_count.first > 0 ? (total_words.first.to_f / post_count.first).round(2) : 0 + + # Calculated using the Flesch Reading Ease formula, + # with an approximation for syllables since this can + # be tricky to get right in SQL. + # + # Tries to handle short sentences or ones without delmiters + # and ending with emojis by treating them as a single sentence. + readability_score = + DB.query_single(<<~SQL, user_id: user.id, start: date.first, end: date.last) + WITH cleaned AS ( + SELECT + p.id AS post_id, + p.user_id, + p.created_at, + p.word_count, + regexp_replace(p.cooked, '<[^>]+>', ' ', 'g') AS plain + FROM posts p + WHERE p.user_id = :user_id + AND p.created_at BETWEEN :start AND :end + ), + metrics AS ( + SELECT + post_id, + user_id, + created_at, + plain, + word_count AS words, + regexp_count(plain, '[.!?;:](\s|$)') AS sentences_raw, + regexp_count(lower(plain), '[aeiouy]+') AS syllables + FROM cleaned + ), + scores AS ( + SELECT + post_id, + user_id, + created_at, + words, + syllables, + plain, + + CASE + WHEN sentences_raw = 0 AND words > 5 THEN 1 + ELSE sentences_raw + END AS sentences_fixed, + + -- Flesch Reading Ease formula + CASE + WHEN words = 0 THEN NULL + WHEN (CASE WHEN sentences_raw = 0 AND words > 5 THEN 1 ELSE sentences_raw END) = 0 THEN NULL + ELSE ( + 206.835 + - 1.015 * ( + words::float / + (CASE WHEN sentences_raw = 0 AND words > 5 THEN 1 ELSE sentences_raw END) + ) + - 84.6 * (syllables::float / words) + ) + END AS readability_score + + FROM metrics + ) + SELECT AVG(readability_score) AS avg_readability_score + FROM scores + GROUP BY user_id; + SQL + + { + data: { + total_words: total_words.first, + total_posts: post_count.first, + average_post_length: average_post_length, + readability_score: readability_score.first, + }, + identifier: "writing-analysis", + } + end + end + end +end diff --git a/app/services/discourse_rewind/fetch_reports.rb b/app/services/discourse_rewind/fetch_reports.rb index 3b28f68..eac34de 100644 --- a/app/services/discourse_rewind/fetch_reports.rb +++ b/app/services/discourse_rewind/fetch_reports.rb @@ -25,6 +25,7 @@ module DiscourseRewind REPORTS = [ Action::TopWords, Action::ReadingTime, + Action::WritingAnalysis, Action::Reactions, Action::Fbff, Action::MostViewedTags, diff --git a/assets/javascripts/discourse/components/reports/activity-calendar.gjs b/assets/javascripts/discourse/components/reports/activity-calendar.gjs index 8a8563c..6bb8c1b 100644 --- a/assets/javascripts/discourse/components/reports/activity-calendar.gjs +++ b/assets/javascripts/discourse/components/reports/activity-calendar.gjs @@ -57,18 +57,18 @@ export default class ActivityCalendar extends Component { @action computeClass(count) { if (!count) { - return "-empty"; + return "--empty"; } else if (count < 10) { - return "-low"; + return "--low"; } else if (count < 20) { - return "-medium"; + return "--medium"; } else { - return "-high"; + return "--high"; } }