docbrown/app/services/articles/feeds/large_forem_experimental.rb
Joshua Wehner 8a45a4bf67
Enable multiple reactions on #index (including front-end render) (#19169)
* Revert "(Temporarily) Remove multiple reactions from #index (#19142)"

This reverts commit a45d300639.

* Clean up reaction category count mechanism for #index

* Remove, unnecessary

* Expose reaction categories in JSON response

* Update test with new categories

* Fix flaky sample / minimum floor

* buildArticle (infinite scroll) with multiple reactions

* If we're doing #index front-end, we aren't feature-flag'd

* nbsp

* react (home feed) has multiple_reactions

* Update app/assets/javascripts/utilities/buildArticleHTML.js

Co-authored-by: Rajat Talesra <rajat@forem.com>

* Clean up Reactable#reaction_categories

* Use 'multiple_reactions_icons_container'

* Try adding a ReactionCount test

* Adapt memory fix from 76dd53d

* Try adding categories to fixture

* Attempt to eager-load distinct public categories

* Setting dependent to the default for rubocop

* Try making image assets more public?

* Revert "Fix flaky sample / minimum floor"

---------

Co-authored-by: Rajat Talesra <rajat@forem.com>
Co-authored-by: Mac Siri <mac@forem.com>
2023-03-03 15:17:19 +01:00

121 lines
5.1 KiB
Ruby

module Articles
module Feeds
class LargeForemExperimental
def initialize(user: nil, number_of_articles: Article::DEFAULT_FEED_PAGINATION_WINDOW_SIZE, page: 1, tag: nil)
@user = user
@number_of_articles = number_of_articles
@page = page
@tag = tag
@article_score_applicator = Articles::Feeds::ArticleScoreCalculatorForUser.new(user: user)
end
def default_home_feed(user_signed_in: false)
_featured_story, stories = featured_story_and_default_home_feed(user_signed_in: user_signed_in, ranking: true)
stories
end
# @param user_signed_in [Boolean] are we treating this as an
# anonymous user?
# @param ranking [Boolean] if true, apply a ranking algorithm
# @param must_have_main_image [Boolean] if true, the featured
# story must have a main image
#
# @note the must_have_main_image parameter name matches PR #15240
def featured_story_and_default_home_feed(user_signed_in: false, ranking: true, must_have_main_image: true)
featured_story, hot_stories = globally_hot_articles(user_signed_in, must_have_main_image: must_have_main_image)
hot_stories = rank_and_sort_articles(hot_stories) if @user && ranking
[featured_story, hot_stories]
end
# Adding an alias to preserve public method signature.
# Eventually, we should be able to remove the alias.
alias default_home_feed_and_featured_story featured_story_and_default_home_feed
def more_comments_minimal_weight_randomized
_featured_story, stories = featured_story_and_default_home_feed(user_signed_in: true)
first_quarter(stories).shuffle + last_three_quarters(stories)
end
# Adding an alias to preserve public method signature. However,
# in this code base there are no further references of
# :more_comments_minimal_weight_randomized_at_end
alias more_comments_minimal_weight_randomized_at_end more_comments_minimal_weight_randomized
# @api private
def rank_and_sort_articles(articles)
ranked_articles = articles.each_with_object({}) do |article, result|
article_points = score_single_article(article)
result[article] = article_points
end
ranked_articles = ranked_articles.sort_by { |_article, article_points| -article_points }.map(&:first)
ranked_articles.to(@number_of_articles - 1)
end
# @api private
def score_single_article(article, base_article_points: 0)
article_points = base_article_points
article_points += score_followed_user(article)
article_points += score_followed_organization(article)
article_points += score_followed_tags(article)
article_points += score_experience_level(article)
article_points += score_comments(article)
article_points
end
delegate(:score_followed_user,
:score_followed_tags,
:score_followed_organization,
:score_experience_level,
:score_comments,
to: :@article_score_applicator)
# @api private
# rubocop:disable Layout/LineLength
def globally_hot_articles(user_signed_in, must_have_main_image: true, article_score_threshold: -15, min_rand_limit: 15, max_rand_limit: 80)
# rubocop:enable Layout/LineLength
if user_signed_in
hot_stories = experimental_hot_story_grab
hot_stories = hot_stories.not_authored_by(UserBlock.cached_blocked_ids_for_blocker(@user.id))
featured_story = featured_story_from(stories: hot_stories, must_have_main_image: must_have_main_image)
new_stories = Article.published
.where("score > ?", article_score_threshold)
.limited_column_select.includes(top_comments: :user).order(published_at: :desc)
.limit(rand(min_rand_limit..max_rand_limit))
hot_stories = hot_stories.to_a + new_stories.to_a
else
hot_stories = Article.published.limited_column_select
.page(@page).per(@number_of_articles)
.with_at_least_home_feed_minimum_score
.order(hotness_score: :desc)
featured_story = featured_story_from(stories: hot_stories, must_have_main_image: must_have_main_image)
end
[featured_story, hot_stories.to_a]
end
private
def featured_story_from(stories:, must_have_main_image:)
return stories.first unless must_have_main_image
stories.where.not(main_image: nil).first
end
def experimental_hot_story_grab
start_time = Articles::Feeds.oldest_published_at_to_consider_for(user: @user)
Article.published.limited_column_select.includes(top_comments: :user)
.includes(:distinct_reaction_categories)
.where("published_at > ?", start_time)
.page(@page).per(@number_of_articles)
.order(score: :desc)
end
def first_quarter(array)
array[0...(array.length / 4)]
end
def last_three_quarters(array)
array[(array.length / 4)..array.length]
end
end
end
end