Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/controllers/users/settings_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ class SettingsController < ApplicationController
before_action :authenticate_user!
after_action :verify_authorized

ALLOWED_PARAMS = %i[config_theme
ALLOWED_PARAMS = %i[disallow_subforem_reassignment
config_theme
config_font
config_navbar
content_preferences_input
Expand Down
3 changes: 2 additions & 1 deletion app/models/article.rb
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,9 @@ def self.unique_url_error

scope :active_help, lambda {
stories = published.cached_tagged_with("help").order(created_at: :desc)
minimum_score = Settings::UserExperience.home_feed_minimum_score

stories.where(published_at: 12.hours.ago.., comments_count: ..5, score: -3..).presence || stories
stories.where(published_at: 12.hours.ago.., comments_count: ..5, score: minimum_score..).presence || stories
}

scope :limited_column_select, lambda {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def trigger_sidekiq_worker(record, delete)
end

def indexable
published && score.positive? && published_at > Time.current
published && score.positive? && published_at < Time.current
end

def indexable_changed?
Expand Down
1 change: 1 addition & 0 deletions app/models/users/setting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class Setting < ApplicationRecord
allow_nil: true
validates :experience_level, numericality: { in: 1..10 }, allow_blank: true
validates :feed_referential_link, inclusion: { in: [true, false] }
validates :disallow_subforem_reassignment, inclusion: { in: [true, false] }
validates :feed_url, length: { maximum: 500 }, allow_nil: true
validates :inbox_guidelines, length: { maximum: 250 }, allow_nil: true
validates :content_preferences_input, length: { maximum: 1250 }, allow_nil: true
Expand Down
14 changes: 12 additions & 2 deletions app/services/subforem_reassignment_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ def check_and_reassign
##
# Determines if the article should be reassigned based on its automod label.
#
# @return [Boolean] true if the article has an offtopic label and is not spam
# @return [Boolean] true if the article has an offtopic label, is not spam, and user allows reassignment
def should_reassign?
OFFTOPIC_LABELS.include?(article.automod_label) && !spam_label?
OFFTOPIC_LABELS.include?(article.automod_label) &&
!spam_label? &&
user_allows_reassignment?
end

##
Expand All @@ -63,6 +65,14 @@ def spam_label?
].include?(article.automod_label)
end

##
# Checks if the user allows automatic subforem reassignment.
#
# @return [Boolean] true if the user allows reassignment, false otherwise
def user_allows_reassignment?
article.user&.setting&.disallow_subforem_reassignment != true
end

##
# Gets the on-topic equivalent of the current offtopic automod label.
#
Expand Down
18 changes: 18 additions & 0 deletions app/views/users/_customization.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,24 @@
</fieldset>
</div>

<div class="crayons-card crayons-card--content-rows">
<header>
<h2 class="crayons-subtitle-1">
<%= t("views.settings.custom.subforem_reassignment") %>
</h2>
<p>
<%= t("views.settings.custom.subforem_reassignment_desc") %>
</p>
</header>

<fieldset class="grid gap-4">
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :disallow_subforem_reassignment, class: "crayons-checkbox" %>
<%= f.label :disallow_subforem_reassignment, t("views.settings.custom.subforem_reassignment_field"), class: "crayons-field__label" %>
</div>
</fieldset>
</div>

<div class="save-footer crayons-card crayons-card--content-rows">
<button class="crayons-btn" type="submit"><%= t("views.settings.custom.save") %></button>
</div>
Expand Down
3 changes: 3 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ class Application < Rails::Application
## Rails 6.1
# Make `form_with` generate non-remote forms by default. We want this to be true as it was the default in 5.2
config.action_view.form_with_generates_remote_forms = true

# Disable partial writes to avoid issues with strong_migrations
config.active_record.partial_inserts = false

## Rails 7.0
config.action_dispatch.cookies_serializer = :json
Expand Down
3 changes: 3 additions & 0 deletions config/locales/views/settings/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ en:
sponsor_desc_html: You have the option to disable billboards from external sponsors. Our wonderful partners help sustain the platform, and we strive to ensure that their presence is constructive and valuable to the community, but you are welcome to make use of this setting if you wish. More details in our <a href="/billboards">billboards FAQ</a>.
sponsor_field: Display External Sponsors (When browsing)
static: Static top of page
subforem_reassignment: Content Relocation
subforem_reassignment_desc: When your content is marked as off-topic for its current subforem, it may be automatically moved to a more appropriate subforem. You can disable this automatic relocation if you prefer to keep your content in its original location.
subforem_reassignment_field: Disable automatic relocation of my content to other subforems
theme: Site Theme
writing: Writing
danger: Danger Zone
Expand Down
3 changes: 3 additions & 0 deletions config/locales/views/settings/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ fr:
sponsor_desc_html: You have the option to disable billboards from external sponsors. Our wonderful partners help sustain the platform, and we strive to ensure that their presence is constructive and valuable to the community, but you are welcome to make use of this setting if you wish. More details in our <a href="/billboards">billboards FAQ</a>.
sponsor_field: Display External Sponsors (When browsing)
static: Static top of page
subforem_reassignment: Relocalisation du contenu
subforem_reassignment_desc: Lorsque votre contenu est marqué comme hors-sujet pour son sous-forum actuel, il peut être automatiquement déplacé vers un sous-forum plus approprié. Vous pouvez désactiver cette relocalisation automatique si vous préférez garder votre contenu dans son emplacement d'origine.
subforem_reassignment_field: Désactiver la relocalisation automatique de mon contenu vers d'autres sous-forums
theme: Site Theme
writing: Writing
danger: Danger Zone
Expand Down
4 changes: 4 additions & 0 deletions config/locales/views/settings/pt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ pt:
disconnect_account: Desconectar conta
connected: Conectado
not_connected: Não conectado
custom:
subforem_reassignment: Realocação de Conteúdo
subforem_reassignment_desc: Quando seu conteúdo é marcado como fora de tópico para seu subfórum atual, ele pode ser automaticamente movido para um subfórum mais apropriado. Você pode desativar essa realocação automática se preferir manter seu conteúdo em sua localização original.
subforem_reassignment_field: Desativar a realocação automática do meu conteúdo para outros subfóruns
billing:
title: Cobrança
subscription: Assinatura
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddDisallowSubforemReassignmentToUsersSettings < ActiveRecord::Migration[7.0]
def change
add_column :users_settings, :disallow_subforem_reassignment, :boolean, default: false, null: false
end
end
10 changes: 4 additions & 6 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.0].define(version: 2025_09_16_132332) do
ActiveRecord::Schema[7.0].define(version: 2025_09_18_134444) do
# These are extensions that must be enabled in order to support this database
enable_extension "citext"
enable_extension "ltree"
Expand Down Expand Up @@ -615,7 +615,6 @@
t.float "randomness_weight", default: 0.0, null: false
t.float "recency_weight", default: 1.0
t.float "recent_article_suppression_rate", default: 0.0, null: false
t.float "recent_article_supression_rate", default: 0.0, null: false
t.float "recent_page_views_shuffle_weight", default: 0.0, null: false
t.float "recent_subforem_weight", default: 0.0, null: false
t.integer "recent_tag_count_max", default: 0
Expand Down Expand Up @@ -1080,7 +1079,7 @@
t.index ["poll_id", "user_id"], name: "index_poll_votes_on_poll_user_regular", where: "(session_start = 0)"
t.index ["poll_option_id", "user_id", "session_start"], name: "index_poll_votes_on_poll_option_user_session_unique", unique: true
t.index ["poll_option_id", "user_id"], name: "index_poll_votes_on_poll_option_user_regular", where: "(session_start = 0)"
t.index ["user_id", "poll_id", "session_start"], name: "index_poll_votes_on_user_id_and_poll_id_and_session_start"
t.index ["user_id", "poll_id", "session_start"], name: "index_poll_votes_on_user_poll_session"
end

create_table "polls", force: :cascade do |t|
Expand Down Expand Up @@ -1169,7 +1168,6 @@
t.index ["category"], name: "index_reactions_on_category"
t.index ["created_at"], name: "index_reactions_on_created_at"
t.index ["points"], name: "index_reactions_on_points"
t.index ["reactable_id", "reactable_type", "user_id"], name: "index_reactions_on_reactable_and_user_for_moderation"
t.index ["reactable_id", "reactable_type"], name: "index_reactions_on_reactable_id_and_reactable_type"
t.index ["reactable_type"], name: "index_reactions_on_reactable_type"
t.index ["status"], name: "index_reactions_on_status"
Expand Down Expand Up @@ -1540,7 +1538,7 @@
t.datetime "last_article_at", precision: nil, default: "2017-01-01 05:00:00"
t.datetime "last_comment_at", precision: nil, default: "2017-01-01 05:00:00"
t.datetime "last_followed_at", precision: nil
t.datetime "last_moderation_notification", precision: nil, default: "2017-01-01 05:00:00"
t.datetime "last_moderation_notification", precision: nil, default: "2017-01-01 00:00:00"
t.datetime "last_notification_activity", precision: nil
t.string "last_onboarding_page"
t.datetime "last_reacted_at", precision: nil
Expand Down Expand Up @@ -1645,7 +1643,6 @@
t.datetime "updated_at", default: -> { "CURRENT_TIMESTAMP" }, null: false
t.bigint "user_id"
t.index ["user_id", "role_id"], name: "index_users_roles_on_user_id_and_role_id"
t.index ["user_id", "role_id"], name: "index_users_roles_on_user_id_role_id_for_moderation"
end

create_table "users_settings", force: :cascade do |t|
Expand All @@ -1657,6 +1654,7 @@
t.text "content_preferences_input"
t.datetime "content_preferences_updated_at"
t.datetime "created_at", null: false
t.boolean "disallow_subforem_reassignment", default: false, null: false
t.boolean "display_announcements", default: true, null: false
t.boolean "display_email_on_profile", default: false, null: false
t.boolean "display_sponsors", default: true, null: false
Expand Down
20 changes: 18 additions & 2 deletions spec/models/article_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1341,18 +1341,34 @@ def build_and_validate_article(*args)

describe ".active_help" do
it "returns properly filtered articles under the 'help' tag" do
minimum_score = Settings::UserExperience.home_feed_minimum_score
filtered_article = create(:article, :past, user: user, tags: "help",
past_published_at: 13.hours.ago, comments_count: 5, score: -3)
past_published_at: 13.hours.ago, comments_count: 5, score: minimum_score)
articles = described_class.active_help
expect(articles).to include(filtered_article)
end

it "returns any published articles tagged with 'help' when there are no articles that fit the criteria" do
minimum_score = Settings::UserExperience.home_feed_minimum_score
unfiltered_article = create(:article, :past, user: user, tags: "help",
past_published_at: 10.hours.ago, comments_count: 8, score: -5)
past_published_at: 10.hours.ago, comments_count: 8, score: minimum_score - 1)
articles = described_class.active_help
expect(articles).to include(unfiltered_article)
end

it "respects the configured home feed minimum score" do
# Set a higher minimum score
allow(Settings::UserExperience).to receive(:home_feed_minimum_score).and_return(5)

low_score_article = create(:article, :past, user: user, tags: "help",
past_published_at: 1.hour.ago, comments_count: 2, score: 3)
high_score_article = create(:article, :past, user: user, tags: "help",
past_published_at: 1.hour.ago, comments_count: 2, score: 6)

articles = described_class.active_help
expect(articles).to include(high_score_article)
expect(articles).not_to include(low_score_article)
end
end

describe ".seo_boostable" do
Expand Down
1 change: 1 addition & 0 deletions spec/models/users/setting_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

it { is_expected.to validate_length_of(:inbox_guidelines).is_at_most(250).allow_nil }
it { is_expected.to validate_numericality_of(:experience_level).is_in(1..10) }
it { is_expected.to validate_inclusion_of(:disallow_subforem_reassignment).in_array([true, false]) }
it { is_expected.to define_enum_for(:inbox_type).with_values(private: 0, open: 1).with_suffix(:inbox) }
it { is_expected.to define_enum_for(:config_font).with_values(default: 0, comic_sans: 1, monospace: 2, open_dyslexic: 3, sans_serif: 4, serif: 5).with_suffix(:font) }
it { is_expected.to define_enum_for(:config_navbar).with_values(default: 0, static: 1).with_suffix(:navbar) }
Expand Down
37 changes: 36 additions & 1 deletion spec/requests/user/user_settings_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
it "displays content on Customization tab properly" do
get user_settings_path(:customization)

expect(response.body).to include("Appearance", "Writing", "Content", "Sponsors", "Announcements")
expect(response.body).to include("Appearance", "Writing", "Content", "Sponsors", "Announcements", "Content Relocation")
end

it "displays content on Notifications tab properly" do
Expand Down Expand Up @@ -270,6 +270,41 @@
expect(user.setting.reload.display_announcements).to be(false)
end

it "updates the users subforem reassignment preferences" do
expect(user.setting.disallow_subforem_reassignment).to be(false)

expect do
put users_settings_path(user.setting.id), params: { users_setting: { disallow_subforem_reassignment: 1 } }
end.to change { user.setting.reload.disallow_subforem_reassignment }.from(false).to(true)

expect(user.setting.reload.disallow_subforem_reassignment).to be(true)
end

it "allows users to enable subforem reassignment" do
user.setting.update!(disallow_subforem_reassignment: true)

expect do
put users_settings_path(user.setting.id), params: { users_setting: { disallow_subforem_reassignment: 0 } }
end.to change { user.setting.reload.disallow_subforem_reassignment }.from(true).to(false)

expect(user.setting.reload.disallow_subforem_reassignment).to be(false)
end

it "displays the subforem reassignment setting in the customization form" do
get user_settings_path(:customization)

expect(response.body).to include("disallow_subforem_reassignment")
expect(response.body).to include("Disable automatic relocation of my content to other subforems")
end

it "shows the correct checkbox state for subforem reassignment setting" do
user.setting.update!(disallow_subforem_reassignment: true)

get user_settings_path(:customization)

expect(response.body).to include('checked="checked"')
end

it "updates username to too short username" do
put "/users/#{user.id}", params: { user: { tab: "profile", username: "h" } }
expect(response.body).to include("Username is too short")
Expand Down
Loading
Loading