about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/admin/trends/links_controller.rb1
-rw-r--r--app/controllers/admin/trends/statuses_controller.rb1
-rw-r--r--app/controllers/api/v1/trends/links_controller.rb4
-rw-r--r--app/mailers/admin_mailer.rb4
-rw-r--r--app/models/preview_card.rb1
-rw-r--r--app/models/preview_card_trend.rb17
-rw-r--r--app/models/status.rb1
-rw-r--r--app/models/status_trend.rb21
-rw-r--r--app/models/trends.rb6
-rw-r--r--app/models/trends/base.rb4
-rw-r--r--app/models/trends/links.rb98
-rw-r--r--app/models/trends/preview_card_filter.rb25
-rw-r--r--app/models/trends/status_filter.rb25
-rw-r--r--app/models/trends/statuses.rb82
-rw-r--r--app/views/admin/trends/links/_preview_card.html.haml4
-rw-r--r--app/views/admin/trends/links/index.html.haml2
-rw-r--r--app/views/admin/trends/statuses/_status.html.haml4
-rw-r--r--app/views/admin/trends/statuses/index.html.haml2
-rw-r--r--app/views/admin_mailer/_new_trending_links.text.erb8
-rw-r--r--app/views/admin_mailer/_new_trending_statuses.text.erb8
20 files changed, 210 insertions, 108 deletions
diff --git a/app/controllers/admin/trends/links_controller.rb b/app/controllers/admin/trends/links_controller.rb
index a497eae41..a3454f2da 100644
--- a/app/controllers/admin/trends/links_controller.rb
+++ b/app/controllers/admin/trends/links_controller.rb
@@ -4,6 +4,7 @@ class Admin::Trends::LinksController < Admin::BaseController
   def index
     authorize :preview_card, :review?
 
+    @locales       = PreviewCardTrend.pluck('distinct language')
     @preview_cards = filtered_preview_cards.page(params[:page])
     @form          = Trends::PreviewCardBatch.new
   end
diff --git a/app/controllers/admin/trends/statuses_controller.rb b/app/controllers/admin/trends/statuses_controller.rb
index c538962f9..2b8226138 100644
--- a/app/controllers/admin/trends/statuses_controller.rb
+++ b/app/controllers/admin/trends/statuses_controller.rb
@@ -4,6 +4,7 @@ class Admin::Trends::StatusesController < Admin::BaseController
   def index
     authorize :status, :review?
 
+    @locales  = StatusTrend.pluck('distinct language')
     @statuses = filtered_statuses.page(params[:page])
     @form     = Trends::StatusBatch.new
   end
diff --git a/app/controllers/api/v1/trends/links_controller.rb b/app/controllers/api/v1/trends/links_controller.rb
index 1a9f918f2..8ff3b364e 100644
--- a/app/controllers/api/v1/trends/links_controller.rb
+++ b/app/controllers/api/v1/trends/links_controller.rb
@@ -28,7 +28,9 @@ class Api::V1::Trends::LinksController < Api::BaseController
   end
 
   def links_from_trends
-    Trends.links.query.allowed.in_locale(content_locale)
+    scope = Trends.links.query.allowed.in_locale(content_locale)
+    scope = scope.filtered_for(current_account) if user_signed_in?
+    scope
   end
 
   def insert_pagination_headers
diff --git a/app/mailers/admin_mailer.rb b/app/mailers/admin_mailer.rb
index f416977d8..bc6d87ae6 100644
--- a/app/mailers/admin_mailer.rb
+++ b/app/mailers/admin_mailer.rb
@@ -4,6 +4,7 @@ class AdminMailer < ApplicationMailer
   layout 'plain_mailer'
 
   helper :accounts
+  helper :languages
 
   def new_report(recipient, report)
     @report   = report
@@ -37,11 +38,8 @@ class AdminMailer < ApplicationMailer
 
   def new_trends(recipient, links, tags, statuses)
     @links                  = links
-    @lowest_trending_link   = Trends.links.query.allowed.limit(Trends.links.options[:review_threshold]).last
     @tags                   = tags
-    @lowest_trending_tag    = Trends.tags.query.allowed.limit(Trends.tags.options[:review_threshold]).last
     @statuses               = statuses
-    @lowest_trending_status = Trends.statuses.query.allowed.limit(Trends.statuses.options[:review_threshold]).last
     @me                     = recipient
     @instance               = Rails.configuration.x.local_domain
 
diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb
index c49c51a1b..b5d3f9c8f 100644
--- a/app/models/preview_card.rb
+++ b/app/models/preview_card.rb
@@ -48,6 +48,7 @@ class PreviewCard < ApplicationRecord
   enum link_type: [:unknown, :article]
 
   has_and_belongs_to_many :statuses
+  has_one :trend, class_name: 'PreviewCardTrend', inverse_of: :preview_card, dependent: :destroy
 
   has_attached_file :image, processors: [:thumbnail, :blurhash_transcoder], styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 80 -strip' }, validate_media_type: false
 
diff --git a/app/models/preview_card_trend.rb b/app/models/preview_card_trend.rb
new file mode 100644
index 000000000..018400dfa
--- /dev/null
+++ b/app/models/preview_card_trend.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: preview_card_trends
+#
+#  id              :bigint(8)        not null, primary key
+#  preview_card_id :bigint(8)        not null
+#  score           :float            default(0.0), not null
+#  rank            :integer          default(0), not null
+#  allowed         :boolean          default(FALSE), not null
+#  language        :string
+#
+class PreviewCardTrend < ApplicationRecord
+  belongs_to :preview_card
+  scope :allowed, -> { where(allowed: true) }
+end
diff --git a/app/models/status.rb b/app/models/status.rb
index de958aaf3..4805abfea 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -75,6 +75,7 @@ class Status < ApplicationRecord
   has_one :notification, as: :activity, dependent: :destroy
   has_one :status_stat, inverse_of: :status
   has_one :poll, inverse_of: :status, dependent: :destroy
+  has_one :trend, class_name: 'StatusTrend', inverse_of: :status
 
   validates :uri, uniqueness: true, presence: true, unless: :local?
   validates :text, presence: true, unless: -> { with_media? || reblog? }
diff --git a/app/models/status_trend.rb b/app/models/status_trend.rb
new file mode 100644
index 000000000..33fd6b004
--- /dev/null
+++ b/app/models/status_trend.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: status_trends
+#
+#  id         :bigint(8)        not null, primary key
+#  status_id  :bigint(8)        not null
+#  account_id :bigint(8)        not null
+#  score      :float            default(0.0), not null
+#  rank       :integer          default(0), not null
+#  allowed    :boolean          default(FALSE), not null
+#  language   :string
+#
+
+class StatusTrend < ApplicationRecord
+  belongs_to :status
+  belongs_to :account
+
+  scope :allowed, -> { where(allowed: true) }
+end
diff --git a/app/models/trends.rb b/app/models/trends.rb
index d886be89a..151d734f9 100644
--- a/app/models/trends.rb
+++ b/app/models/trends.rb
@@ -26,7 +26,7 @@ module Trends
   end
 
   def self.request_review!
-    return unless enabled?
+    return if skip_review? || !enabled?
 
     links_requiring_review    = links.request_review
     tags_requiring_review     = tags.request_review
@@ -43,6 +43,10 @@ module Trends
     Setting.trends
   end
 
+  def skip_review?
+    Setting.trendable_by_default
+  end
+
   def self.available_locales
     @available_locales ||= I18n.available_locales.map { |locale| locale.to_s.split(/[_-]/).first }.uniq
   end
diff --git a/app/models/trends/base.rb b/app/models/trends/base.rb
index 047111248..a189f11f2 100644
--- a/app/models/trends/base.rb
+++ b/app/models/trends/base.rb
@@ -98,4 +98,8 @@ class Trends::Base
       pipeline.rename(from_key, to_key)
     end
   end
+
+  def skip_review?
+    Setting.trendable_by_default
+  end
 end
diff --git a/app/models/trends/links.rb b/app/models/trends/links.rb
index 604894cd6..8808b3ab6 100644
--- a/app/models/trends/links.rb
+++ b/app/models/trends/links.rb
@@ -11,6 +11,40 @@ class Trends::Links < Trends::Base
     decay_threshold: 1,
   }
 
+  class Query < Trends::Query
+    def filtered_for!(account)
+      @account = account
+      self
+    end
+
+    def filtered_for(account)
+      clone.filtered_for!(account)
+    end
+
+    def to_arel
+      scope = PreviewCard.joins(:trend).reorder(score: :desc)
+      scope = scope.reorder(language_order_clause.desc, score: :desc) if preferred_languages.present?
+      scope = scope.merge(PreviewCardTrend.allowed) if @allowed
+      scope = scope.offset(@offset) if @offset.present?
+      scope = scope.limit(@limit) if @limit.present?
+      scope
+    end
+
+    private
+
+    def language_order_clause
+      Arel::Nodes::Case.new.when(PreviewCardTrend.arel_table[:language].in(preferred_languages)).then(1).else(0)
+    end
+
+    def preferred_languages
+      if @account&.chosen_languages.present?
+        @account.chosen_languages
+      else
+        @locale
+      end
+    end
+  end
+
   def register(status, at_time = Time.now.utc)
     original_status = status.proper
 
@@ -28,24 +62,33 @@ class Trends::Links < Trends::Base
     record_used_id(preview_card.id, at_time)
   end
 
+  def query
+    Query.new(key_prefix, klass)
+  end
+
   def refresh(at_time = Time.now.utc)
-    preview_cards = PreviewCard.where(id: (recently_used_ids(at_time) + currently_trending_ids(false, -1)).uniq)
+    preview_cards = PreviewCard.where(id: (recently_used_ids(at_time) + PreviewCardTrend.pluck(:preview_card_id)).uniq)
     calculate_scores(preview_cards, at_time)
   end
 
   def request_review
-    preview_cards = PreviewCard.where(id: currently_trending_ids(false, -1))
+    PreviewCardTrend.pluck('distinct language').flat_map do |language|
+      score_at_threshold  = PreviewCardTrend.where(language: language, allowed: true).order(rank: :desc).where('rank <= ?', options[:review_threshold]).first&.score || 0
+      preview_card_trends = PreviewCardTrend.where(language: language, allowed: false).joins(:preview_card)
 
-    preview_cards.filter_map do |preview_card|
-      next unless would_be_trending?(preview_card.id) && !preview_card.trendable? && preview_card.requires_review_notification?
+      preview_card_trends.filter_map do |trend|
+        preview_card = trend.preview_card
 
-      if preview_card.provider.nil?
-        preview_card.provider = PreviewCardProvider.create(domain: preview_card.domain, requested_review_at: Time.now.utc)
-      else
-        preview_card.provider.touch(:requested_review_at)
-      end
+        next unless trend.score > score_at_threshold && !preview_card.trendable? && preview_card.requires_review_notification?
+
+        if preview_card.provider.nil?
+          preview_card.provider = PreviewCardProvider.create(domain: preview_card.domain, requested_review_at: Time.now.utc)
+        else
+          preview_card.provider.touch(:requested_review_at)
+        end
 
-      preview_card
+        preview_card
+      end
     end
   end
 
@@ -62,10 +105,7 @@ class Trends::Links < Trends::Base
   private
 
   def calculate_scores(preview_cards, at_time)
-    global_items = []
-    locale_items = Hash.new { |h, key| h[key] = [] }
-
-    preview_cards.each do |preview_card|
+    items = preview_cards.map do |preview_card|
       expected  = preview_card.history.get(at_time - 1.day).accounts.to_f
       expected  = 1.0 if expected.zero?
       observed  = preview_card.history.get(at_time).accounts.to_f
@@ -89,26 +129,24 @@ class Trends::Links < Trends::Base
         preview_card.update_columns(max_score: max_score, max_score_at: max_time)
       end
 
-      decaying_score = max_score * (0.5**((at_time.to_f - max_time.to_f) / options[:max_score_halflife].to_f))
-
-      next unless decaying_score >= options[:decay_threshold]
+      decaying_score = begin
+        if max_score.zero? || !valid_locale?(preview_card.language)
+          0
+        else
+          max_score * (0.5**((at_time.to_f - max_time.to_f) / options[:max_score_halflife].to_f))
+        end
+      end
 
-      global_items << { score: decaying_score, item:  preview_card }
-      locale_items[preview_card.language] << { score: decaying_score, item: preview_card } if valid_locale?(preview_card.language)
+      [decaying_score, preview_card]
     end
 
-    replace_items('', global_items)
+    to_insert = items.filter { |(score, _)| score >= options[:decay_threshold] }
+    to_delete = items.filter { |(score, _)| score < options[:decay_threshold] }
 
-    Trends.available_locales.each do |locale|
-      replace_items(":#{locale}", locale_items[locale])
+    PreviewCardTrend.transaction do
+      PreviewCardTrend.upsert_all(to_insert.map { |(score, preview_card)| { preview_card_id: preview_card.id, score: score, language: preview_card.language, allowed: preview_card.trendable? || false } }, unique_by: :preview_card_id) if to_insert.any?
+      PreviewCardTrend.where(preview_card_id: to_delete.map { |(_, preview_card)| preview_card.id }).delete_all if to_delete.any?
+      PreviewCardTrend.connection.exec_update('UPDATE preview_card_trends SET rank = t0.calculated_rank FROM (SELECT id, row_number() OVER w AS calculated_rank FROM preview_card_trends WINDOW w AS (PARTITION BY language ORDER BY score DESC)) t0 WHERE preview_card_trends.id = t0.id')
     end
   end
-
-  def filter_for_allowed_items(items)
-    items.select { |item| item[:item].trendable? }
-  end
-
-  def would_be_trending?(id)
-    score(id) > score_at_rank(options[:review_threshold] - 1)
-  end
 end
diff --git a/app/models/trends/preview_card_filter.rb b/app/models/trends/preview_card_filter.rb
index 25add58c8..0a81146d4 100644
--- a/app/models/trends/preview_card_filter.rb
+++ b/app/models/trends/preview_card_filter.rb
@@ -13,10 +13,10 @@ class Trends::PreviewCardFilter
   end
 
   def results
-    scope = PreviewCard.unscoped
+    scope = initial_scope
 
     params.each do |key, value|
-      next if %w(page locale).include?(key.to_s)
+      next if %w(page).include?(key.to_s)
 
       scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
     end
@@ -26,21 +26,30 @@ class Trends::PreviewCardFilter
 
   private
 
+  def initial_scope
+    PreviewCard.select(PreviewCard.arel_table[Arel.star])
+               .joins(:trend)
+               .eager_load(:trend)
+               .reorder(score: :desc)
+  end
+
   def scope_for(key, value)
     case key.to_s
     when 'trending'
       trending_scope(value)
+    when 'locale'
+      PreviewCardTrend.where(language: value)
     else
       raise "Unknown filter: #{key}"
     end
   end
 
   def trending_scope(value)
-    scope = Trends.links.query
-
-    scope = scope.in_locale(@params[:locale].to_s) if @params[:locale].present?
-    scope = scope.allowed if value == 'allowed'
-
-    scope.to_arel
+    case value
+    when 'allowed'
+      PreviewCardTrend.allowed
+    else
+      PreviewCardTrend.all
+    end
   end
 end
diff --git a/app/models/trends/status_filter.rb b/app/models/trends/status_filter.rb
index 7c453e339..cb0f75d67 100644
--- a/app/models/trends/status_filter.rb
+++ b/app/models/trends/status_filter.rb
@@ -13,10 +13,10 @@ class Trends::StatusFilter
   end
 
   def results
-    scope = Status.unscoped.kept
+    scope = initial_scope
 
     params.each do |key, value|
-      next if %w(page locale).include?(key.to_s)
+      next if %w(page).include?(key.to_s)
 
       scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
     end
@@ -26,21 +26,30 @@ class Trends::StatusFilter
 
   private
 
+  def initial_scope
+    Status.select(Status.arel_table[Arel.star])
+          .joins(:trend)
+          .eager_load(:trend)
+          .reorder(score: :desc)
+  end
+
   def scope_for(key, value)
     case key.to_s
     when 'trending'
       trending_scope(value)
+    when 'locale'
+      StatusTrend.where(language: value)
     else
       raise "Unknown filter: #{key}"
     end
   end
 
   def trending_scope(value)
-    scope = Trends.statuses.query
-
-    scope = scope.in_locale(@params[:locale].to_s) if @params[:locale].present?
-    scope = scope.allowed if value == 'allowed'
-
-    scope.to_arel
+    case value
+    when 'allowed'
+      StatusTrend.allowed
+    else
+      StatusTrend.all
+    end
   end
 end
diff --git a/app/models/trends/statuses.rb b/app/models/trends/statuses.rb
index 777065d3e..c9ef7c8f2 100644
--- a/app/models/trends/statuses.rb
+++ b/app/models/trends/statuses.rb
@@ -20,13 +20,27 @@ class Trends::Statuses < Trends::Base
       clone.filtered_for!(account)
     end
 
+    def to_arel
+      scope = Status.joins(:trend).reorder(score: :desc)
+      scope = scope.reorder(language_order_clause.desc, score: :desc) if preferred_languages.present?
+      scope = scope.merge(StatusTrend.allowed) if @allowed
+      scope = scope.not_excluded_by_account(@account).not_domain_blocked_by_account(@account) if @account.present?
+      scope = scope.offset(@offset) if @offset.present?
+      scope = scope.limit(@limit) if @limit.present?
+      scope
+    end
+
     private
 
-    def apply_scopes(scope)
-      if @account.nil?
-        scope
+    def language_order_clause
+      Arel::Nodes::Case.new.when(StatusTrend.arel_table[:language].in(preferred_languages)).then(1).else(0)
+    end
+
+    def preferred_languages
+      if @account&.chosen_languages.present?
+        @account.chosen_languages
       else
-        scope.not_excluded_by_account(@account).not_domain_blocked_by_account(@account)
+        @locale
       end
     end
   end
@@ -36,9 +50,6 @@ class Trends::Statuses < Trends::Base
   end
 
   def add(status, _account_id, at_time = Time.now.utc)
-    # We rely on the total reblogs and favourites count, so we
-    # don't record which account did the what and when here
-
     record_used_id(status.id, at_time)
   end
 
@@ -47,18 +58,23 @@ class Trends::Statuses < Trends::Base
   end
 
   def refresh(at_time = Time.now.utc)
-    statuses = Status.where(id: (recently_used_ids(at_time) + currently_trending_ids(false, -1)).uniq).includes(:account, :media_attachments)
+    statuses = Status.where(id: (recently_used_ids(at_time) + StatusTrend.pluck(:status_id)).uniq).includes(:status_stat, :account)
     calculate_scores(statuses, at_time)
   end
 
   def request_review
-    statuses = Status.where(id: currently_trending_ids(false, -1)).includes(:account)
+    StatusTrend.pluck('distinct language').flat_map do |language|
+      score_at_threshold = StatusTrend.where(language: language, allowed: true).order(rank: :desc).where('rank <= ?', options[:review_threshold]).first&.score || 0
+      status_trends      = StatusTrend.where(language: language, allowed: false).joins(:status).includes(status: :account)
 
-    statuses.filter_map do |status|
-      next unless would_be_trending?(status.id) && !status.trendable? && status.requires_review_notification?
+      status_trends.filter_map do |trend|
+        status = trend.status
 
-      status.account.touch(:requested_review_at)
-      status
+        if trend.score > score_at_threshold && !status.trendable? && status.requires_review_notification?
+          status.account.touch(:requested_review_at)
+          status
+        end
+      end
     end
   end
 
@@ -75,14 +91,11 @@ class Trends::Statuses < Trends::Base
   private
 
   def eligible?(status)
-    status.public_visibility? && status.account.discoverable? && !status.account.silenced? && status.spoiler_text.blank? && !status.sensitive? && !status.reply?
+    status.public_visibility? && status.account.discoverable? && !status.account.silenced? && status.spoiler_text.blank? && !status.sensitive? && !status.reply? && valid_locale?(status.language)
   end
 
   def calculate_scores(statuses, at_time)
-    global_items = []
-    locale_items = Hash.new { |h, key| h[key] = [] }
-
-    statuses.each do |status|
+    items = statuses.map do |status|
       expected  = 1.0
       observed  = (status.reblogs_count + status.favourites_count).to_f
 
@@ -94,29 +107,24 @@ class Trends::Statuses < Trends::Base
         end
       end
 
-      decaying_score = score * (0.5**((at_time.to_f - status.created_at.to_f) / options[:score_halflife].to_f))
-
-      next unless decaying_score >= options[:decay_threshold]
+      decaying_score = begin
+        if score.zero? || !eligible?(status)
+          0
+        else
+          score * (0.5**((at_time.to_f - status.created_at.to_f) / options[:score_halflife].to_f))
+        end
+      end
 
-      global_items << { score: decaying_score, item: status }
-      locale_items[status.language] << { account_id: status.account_id, score: decaying_score, item: status } if valid_locale?(status.language)
+      [decaying_score, status]
     end
 
-    replace_items('', global_items)
+    to_insert = items.filter { |(score, _)| score >= options[:decay_threshold] }
+    to_delete = items.filter { |(score, _)| score < options[:decay_threshold] }
 
-    Trends.available_locales.each do |locale|
-      replace_items(":#{locale}", locale_items[locale])
+    StatusTrend.transaction do
+      StatusTrend.upsert_all(to_insert.map { |(score, status)| { status_id: status.id, account_id: status.account_id, score: score, language: status.language, allowed: status.trendable? || false } }, unique_by: :status_id) if to_insert.any?
+      StatusTrend.where(status_id: to_delete.map { |(_, status)| status.id }).delete_all if to_delete.any?
+      StatusTrend.connection.exec_update('UPDATE status_trends SET rank = t0.calculated_rank FROM (SELECT id, row_number() OVER w AS calculated_rank FROM status_trends WINDOW w AS (PARTITION BY language ORDER BY score DESC)) t0 WHERE status_trends.id = t0.id')
     end
   end
-
-  def filter_for_allowed_items(items)
-    # Show only one status per account, pick the one with the highest score
-    # that's also eligible to trend
-
-    items.group_by { |item| item[:account_id] }.values.filter_map { |account_items| account_items.select { |item| item[:item].trendable? && item[:item].account.discoverable? }.max_by { |item| item[:score] } }
-  end
-
-  def would_be_trending?(id)
-    score(id) > score_at_rank(options[:review_threshold] - 1)
-  end
 end
diff --git a/app/views/admin/trends/links/_preview_card.html.haml b/app/views/admin/trends/links/_preview_card.html.haml
index 7d4897c7e..8812feb31 100644
--- a/app/views/admin/trends/links/_preview_card.html.haml
+++ b/app/views/admin/trends/links/_preview_card.html.haml
@@ -18,9 +18,9 @@
 
       = t('admin.trends.links.shared_by_over_week', count: preview_card.history.reduce(0) { |sum, day| sum + day.accounts })
 
-      - if preview_card.trendable? && (rank = Trends.links.rank(preview_card.id, locale: params[:locale].presence))
+      - if preview_card.trend.allowed?

-        %abbr{ title: t('admin.trends.tags.current_score', score: Trends.links.score(preview_card.id, locale: params[:locale].presence)) }= t('admin.trends.tags.trending_rank', rank: rank + 1)
+        %abbr{ title: t('admin.trends.tags.current_score', score: preview_card.trend.score) }= t('admin.trends.tags.trending_rank', rank: preview_card.trend.rank)
 
         - if preview_card.decaying?

diff --git a/app/views/admin/trends/links/index.html.haml b/app/views/admin/trends/links/index.html.haml
index 49a53d979..7b5122cf1 100644
--- a/app/views/admin/trends/links/index.html.haml
+++ b/app/views/admin/trends/links/index.html.haml
@@ -16,7 +16,7 @@
     .filter-subset.filter-subset--with-select
       %strong= t('admin.follow_recommendations.language')
       .input.select.optional
-        = select_tag :locale, options_for_select(Trends.available_locales.map { |key| [standard_locale_name(key), key] }, params[:locale]), include_blank: true
+        = select_tag :locale, options_for_select(@locales.map { |key| [standard_locale_name(key), key] }, params[:locale]), include_blank: true
     .filter-subset
       %strong= t('admin.trends.trending')
       %ul
diff --git a/app/views/admin/trends/statuses/_status.html.haml b/app/views/admin/trends/statuses/_status.html.haml
index e4d75bbb9..f35e13d12 100644
--- a/app/views/admin/trends/statuses/_status.html.haml
+++ b/app/views/admin/trends/statuses/_status.html.haml
@@ -25,9 +25,9 @@
     - if status.trendable? && !status.account.discoverable?

       = t('admin.trends.statuses.not_discoverable')
-    - if status.trendable? && (rank = Trends.statuses.rank(status.id, locale: params[:locale].presence))
+    - if status.trend.allowed?

-      %abbr{ title: t('admin.trends.tags.current_score', score: Trends.statuses.score(status.id, locale: params[:locale].presence)) }= t('admin.trends.tags.trending_rank', rank: rank + 1)
+      %abbr{ title: t('admin.trends.tags.current_score', score: status.trend.score) }= t('admin.trends.tags.trending_rank', rank: status.trend.rank)
     - elsif status.requires_review?

       = t('admin.trends.pending_review')
diff --git a/app/views/admin/trends/statuses/index.html.haml b/app/views/admin/trends/statuses/index.html.haml
index b0059b20d..a42d60b00 100644
--- a/app/views/admin/trends/statuses/index.html.haml
+++ b/app/views/admin/trends/statuses/index.html.haml
@@ -16,7 +16,7 @@
     .filter-subset.filter-subset--with-select
       %strong= t('admin.follow_recommendations.language')
       .input.select.optional
-        = select_tag :locale, options_for_select(Trends.available_locales.map { |key| [standard_locale_name(key), key]}, params[:locale]), include_blank: true
+        = select_tag :locale, options_for_select(@locales.map { |key| [standard_locale_name(key), key] }, params[:locale]), include_blank: true
     .filter-subset
       %strong= t('admin.trends.trending')
       %ul
diff --git a/app/views/admin_mailer/_new_trending_links.text.erb b/app/views/admin_mailer/_new_trending_links.text.erb
index 405926fdd..602e12793 100644
--- a/app/views/admin_mailer/_new_trending_links.text.erb
+++ b/app/views/admin_mailer/_new_trending_links.text.erb
@@ -2,13 +2,7 @@
 
 <% @links.each do |link| %>
 - <%= link.title %> • <%= link.url %>
-  <%= raw t('admin.trends.links.usage_comparison', today: link.history.get(Time.now.utc).accounts, yesterday: link.history.get(Time.now.utc - 1.day).accounts) %> • <%= t('admin.trends.tags.current_score', score: Trends.links.score(link.id).round(2)) %>
-<% end %>
-
-<% if @lowest_trending_link %>
-<%= raw t('admin_mailer.new_trends.new_trending_links.requirements', lowest_link_title: @lowest_trending_link.title, lowest_link_score: Trends.links.score(@lowest_trending_link.id).round(2), rank: Trends.links.options[:review_threshold]) %>
-<% else %>
-<%= raw t('admin_mailer.new_trends.new_trending_links.no_approved_links') %>
+  <%= standard_locale_name(link.language) %> • <%= raw t('admin.trends.links.usage_comparison', today: link.history.get(Time.now.utc).accounts, yesterday: link.history.get(Time.now.utc - 1.day).accounts) %> • <%= t('admin.trends.tags.current_score', score: link.trend.score.round(2)) %>
 <% end %>
 
 <%= raw t('application_mailer.view')%> <%= admin_trends_links_url %>
diff --git a/app/views/admin_mailer/_new_trending_statuses.text.erb b/app/views/admin_mailer/_new_trending_statuses.text.erb
index 8d11a80c2..1ed3ae857 100644
--- a/app/views/admin_mailer/_new_trending_statuses.text.erb
+++ b/app/views/admin_mailer/_new_trending_statuses.text.erb
@@ -2,13 +2,7 @@
 
 <% @statuses.each do |status| %>
 - <%= ActivityPub::TagManager.instance.url_for(status) %>
-  <%= raw t('admin.trends.tags.current_score', score: Trends.statuses.score(status.id).round(2)) %>
-<% end %>
-
-<% if @lowest_trending_status %>
-<%= raw t('admin_mailer.new_trends.new_trending_statuses.requirements', lowest_status_url: ActivityPub::TagManager.instance.url_for(@lowest_trending_status), lowest_status_score: Trends.statuses.score(@lowest_trending_status.id).round(2), rank: Trends.statuses.options[:review_threshold]) %>
-<% else %>
-<%= raw t('admin_mailer.new_trends.new_trending_statuses.no_approved_statuses') %>
+  <%= standard_locale_name(status.language) %> • <%= raw t('admin.trends.tags.current_score', score: status.trend.score.round(2)) %>
 <% end %>
 
 <%= raw t('application_mailer.view')%> <%= admin_trends_statuses_url %>