Split Settings::UserExperience (#13495)
* Add Settings::UserExperience * Add DUS * Update usage * Add controller * Add e2e test * Fix specs
This commit is contained in:
parent
02037b1e99
commit
1c7de11bef
34 changed files with 327 additions and 123 deletions
|
|
@ -0,0 +1,34 @@
|
|||
module Admin
|
||||
module Settings
|
||||
class UserExperiencesController < Admin::ApplicationController
|
||||
def create
|
||||
errors = upsert_config(settings_params)
|
||||
|
||||
if errors.none?
|
||||
Audit::Logger.log(:internal, current_user, params.dup)
|
||||
redirect_to admin_config_path, notice: "Site configuration was successfully updated."
|
||||
else
|
||||
redirect_to admin_config_path, alert: "😭 #{errors.to_sentence}"
|
||||
end
|
||||
end
|
||||
|
||||
def settings_params
|
||||
params
|
||||
.require(:settings_user_experience)
|
||||
.permit(*::Settings::UserExperience.keys)
|
||||
end
|
||||
|
||||
def upsert_config(configs)
|
||||
errors = []
|
||||
configs.each do |key, value|
|
||||
::Settings::UserExperience.public_send("#{key}=", value) if value.present?
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
errors << e.message
|
||||
next
|
||||
end
|
||||
|
||||
errors
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -51,7 +51,7 @@ class ApplicationController < ActionController::Base
|
|||
def verify_private_forem
|
||||
return if controller_name.in?(PUBLIC_CONTROLLERS)
|
||||
return if self.class.module_parent.to_s == "Admin"
|
||||
return if user_signed_in? || SiteConfig.public
|
||||
return if user_signed_in? || Settings::UserExperience.public
|
||||
|
||||
if api_action?
|
||||
authenticate!
|
||||
|
|
@ -139,7 +139,7 @@ class ApplicationController < ActionController::Base
|
|||
def feed_style_preference
|
||||
# TODO: Future functionality will let current_user override this value with UX preferences
|
||||
# if current_user exists and has a different preference.
|
||||
SiteConfig.feed_style
|
||||
Settings::UserExperience.feed_style
|
||||
end
|
||||
helper_method :feed_style_preference
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ class ArticlesController < ApplicationController
|
|||
.includes(:user)
|
||||
else
|
||||
@articles
|
||||
.where(featured: true).or(@articles.where(score: SiteConfig.home_feed_minimum_score..))
|
||||
.where(featured: true)
|
||||
.or(@articles.where(score: Settings::UserExperience.home_feed_minimum_score..))
|
||||
.includes(:user)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ module CachingHeaders
|
|||
stale_while_revalidate: nil,
|
||||
stale_if_error: 26_400
|
||||
)
|
||||
return unless SiteConfig.public # Only public forems should be edge-cached based on current functionality.
|
||||
# Only public forems should be edge-cached based on current functionality.
|
||||
return unless Settings::UserExperience.public
|
||||
|
||||
request.session_options[:skip] = true # no cookies
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ module Stories
|
|||
end
|
||||
|
||||
def signed_in_base_feed
|
||||
if SiteConfig.feed_strategy == "basic"
|
||||
if Settings::UserExperience.feed_strategy == "basic"
|
||||
Articles::Feeds::Basic.new(user: current_user, page: @page, tag: params[:tag]).feed
|
||||
else
|
||||
optimized_signed_in_feed
|
||||
|
|
@ -30,7 +30,7 @@ module Stories
|
|||
end
|
||||
|
||||
def signed_out_base_feed
|
||||
if SiteConfig.feed_strategy == "basic"
|
||||
if Settings::UserExperience.feed_strategy == "basic"
|
||||
Articles::Feeds::Basic.new(user: nil, page: @page, tag: params[:tag]).feed
|
||||
else
|
||||
Articles::Feeds::LargeForemExperimental.new(user: current_user, page: @page, tag: params[:tag])
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ class StoriesController < ApplicationController
|
|||
|
||||
@num_published_articles = if @tag_model.requires_approval?
|
||||
@tag_model.articles.published.where(approved: true).count
|
||||
elsif SiteConfig.feed_strategy == "basic"
|
||||
elsif Settings::UserExperience.feed_strategy == "basic"
|
||||
tagged_count
|
||||
else
|
||||
Rails.cache.fetch("article-cached-tagged-count-#{@tag}", expires_in: 2.hours) do
|
||||
|
|
@ -353,7 +353,7 @@ class StoriesController < ApplicationController
|
|||
elsif params[:timeframe] == "latest"
|
||||
@stories.where("score > ?", -20).order(published_at: :desc)
|
||||
else
|
||||
@stories.order(hotness_score: :desc).where("score >= ?", SiteConfig.home_feed_minimum_score)
|
||||
@stories.order(hotness_score: :desc).where("score >= ?", Settings::UserExperience.home_feed_minimum_score)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -485,6 +485,6 @@ class StoriesController < ApplicationController
|
|||
end
|
||||
|
||||
def tagged_count
|
||||
@tag_model.articles.published.where("score >= ?", SiteConfig.tag_feed_minimum_score).count
|
||||
@tag_model.articles.published.where("score >= ?", Settings::UserExperience.tag_feed_minimum_score).count
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class UserDecorator < ApplicationDecorator
|
|||
end
|
||||
|
||||
def config_font_name
|
||||
config_font.gsub("default", SiteConfig.default_font)
|
||||
config_font.gsub("default", Settings::UserExperience.default_font)
|
||||
end
|
||||
|
||||
def config_body_class
|
||||
|
|
|
|||
32
app/lib/constants/settings/user_experience.rb
Normal file
32
app/lib/constants/settings/user_experience.rb
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
module Constants
|
||||
module Settings
|
||||
module UserExperience
|
||||
DETAILS = {
|
||||
default_font: {
|
||||
description: "Determines the default reading font (registered users can change this in their UX settings)"
|
||||
},
|
||||
feed_strategy: {
|
||||
description: "Determines the main feed algorithm approach the app takes: basic or large_forem_experimental
|
||||
(which should only be used for 10k+ member communities)",
|
||||
placeholder: "basic"
|
||||
},
|
||||
feed_style: {
|
||||
description: "Determines which default feed the users sees (rich content, more minimal, etc.)",
|
||||
placeholder: "basic, rich, or compact"
|
||||
},
|
||||
home_feed_minimum_score: {
|
||||
description: "Minimum score needed for a post to show up on the unauthenticated home page.",
|
||||
placeholder: "0"
|
||||
},
|
||||
primary_brand_color_hex: {
|
||||
description: "Determines background/border of buttons etc. Must be dark enough to contrast with white text.",
|
||||
placeholder: "#0a0a0a"
|
||||
},
|
||||
tag_feed_minimum_score: {
|
||||
description: "Minimum score needed for a post to show up on default tag page.",
|
||||
placeholder: "0"
|
||||
}
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -22,9 +22,6 @@ module Constants
|
|||
placeholder: ""
|
||||
}
|
||||
},
|
||||
default_font: {
|
||||
description: "Determines the default Base Reading Font (registered users can change this in their UX settings)"
|
||||
},
|
||||
email_addresses: {
|
||||
description: "Email address",
|
||||
placeholder: ""
|
||||
|
|
@ -33,19 +30,6 @@ module Constants
|
|||
description: "Used as the site favicon",
|
||||
placeholder: IMAGE_PLACEHOLDER
|
||||
},
|
||||
feed_strategy: {
|
||||
description: "Determines the main feed algorithm approach the app takes: basic or large_forem_experimental
|
||||
(which should only be used for 10k+ member communities)",
|
||||
placeholder: "basic"
|
||||
},
|
||||
feed_style: {
|
||||
description: "Determines which default feed the users sees (rich content, more minimal, etc.)",
|
||||
placeholder: "basic, rich, or compact"
|
||||
},
|
||||
primary_brand_color_hex: {
|
||||
description: "Determines background/border of buttons etc. Must be dark enough to contrast with white text.",
|
||||
placeholder: "#0a0a0a"
|
||||
},
|
||||
ga_tracking_id: {
|
||||
description: "Google Analytics Tracking ID, e.g. UA-71991000-1",
|
||||
placeholder: ""
|
||||
|
|
@ -54,10 +38,6 @@ module Constants
|
|||
description: "Used to authenticate with your health check endpoints.",
|
||||
placeholder: "a secure token"
|
||||
},
|
||||
home_feed_minimum_score: {
|
||||
description: "Minimum score needed for a post to show up on the unauthenticated home page.",
|
||||
placeholder: "0"
|
||||
},
|
||||
logo_png: {
|
||||
description: "Used as a fallback to the SVG. Recommended minimum of 512x512px for PWA support",
|
||||
placeholder: IMAGE_PLACEHOLDER
|
||||
|
|
@ -148,10 +128,6 @@ module Constants
|
|||
description: "Always show suggested users as suggested people to follow even when " \
|
||||
"auto-suggestion is available"
|
||||
},
|
||||
tag_feed_minimum_score: {
|
||||
description: "Minimum score needed for a post to show up on default tag page.",
|
||||
placeholder: "0"
|
||||
},
|
||||
twitter_hashtag: {
|
||||
description: "Used as the twitter hashtag of the community",
|
||||
placeholder: "#DEVCommunity"
|
||||
|
|
|
|||
30
app/models/settings/user_experience.rb
Normal file
30
app/models/settings/user_experience.rb
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
module Settings
|
||||
# Basic UX settings that can be overriden by individual user preferences.
|
||||
class UserExperience < RailsSettings::Base
|
||||
self.table_name = :settings_user_experiences
|
||||
|
||||
HEX_COLOR_REGEX = /\A#(\h{6}|\h{3})\z/.freeze
|
||||
|
||||
# The configuration is cached, change this if you want to force update
|
||||
# the cache, or call Settings::UserExperience.clear_cache
|
||||
cache_prefix { "v1" }
|
||||
|
||||
# The default font for all users that have not chosen a custom font yet
|
||||
field :default_font, type: :string, default: "sans_serif"
|
||||
field :feed_strategy, type: :string, default: "basic"
|
||||
# basic (current default), rich (cover image on all posts), compact (more minimal)
|
||||
field :feed_style, type: :string, default: "basic"
|
||||
field :home_feed_minimum_score, type: :integer, default: 0
|
||||
field :primary_brand_color_hex, type: :string, default: "#3b49df", validates: {
|
||||
format: {
|
||||
with: HEX_COLOR_REGEX,
|
||||
message: "must be be a 3 or 6 character hex (starting with #)"
|
||||
},
|
||||
color_contrast: true
|
||||
}
|
||||
# a non-public forem will redirect all unauthenticated pages to the registration page.
|
||||
# a public forem could have more fine-grained authentication (listings ar private etc.) in future
|
||||
field :public, type: :boolean, default: 0
|
||||
field :tag_feed_minimum_score, type: :integer, default: 0
|
||||
end
|
||||
end
|
||||
|
|
@ -178,6 +178,8 @@ class SiteConfig < RailsSettings::Base
|
|||
# Tags
|
||||
field :sidebar_tags, type: :array, default: %w[]
|
||||
|
||||
# NOTE: @citizen428 - These will be removed once we migrated to Settings::UserExperience
|
||||
# across the whole fleet.
|
||||
# User Experience
|
||||
# These are the default UX settings, which can be overridded by individual user preferences.
|
||||
# basic (current default), rich (cover image on all posts), compact (more minimal)
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ module Articles
|
|||
else
|
||||
hot_stories = Article.published.limited_column_select
|
||||
.page(@page).per(@number_of_articles)
|
||||
.where("score >= ? OR featured = ?", SiteConfig.home_feed_minimum_score, true)
|
||||
.where("score >= ? OR featured = ?", Settings::UserExperience.home_feed_minimum_score, true)
|
||||
.order(hotness_score: :desc)
|
||||
featured_story = hot_stories.where.not(main_image: nil).first
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1162,7 +1162,7 @@
|
|||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= form_for(SiteConfig.new, url: admin_config_path) do |f| %>
|
||||
<%= form_for(Settings::UserExperience.new, url: admin_settings_user_experiences_path) do |f| %>
|
||||
<div class=" card mt-3">
|
||||
<%= render partial: "admin/shared/card_header",
|
||||
locals: {
|
||||
|
|
@ -1174,61 +1174,61 @@
|
|||
<div id="uxBodyContainer" class="card-body collapse hide" aria-labelledby="uxBodyContainer">
|
||||
<fieldset class="grid gap-4">
|
||||
<div class="crayons-fieldx">
|
||||
<%= admin_config_label :feed_style %>
|
||||
<%= admin_config_description Constants::SiteConfig::DETAILS[:feed_style][:description] %>
|
||||
<%= admin_config_label :feed_style, model: Settings::UserExperience %>
|
||||
<%= admin_config_description Constants::Settings::UserExperience::DETAILS[:feed_style][:description] %>
|
||||
<%= f.text_field :feed_style,
|
||||
class: "crayons-textfield",
|
||||
value: SiteConfig.feed_style,
|
||||
placeholder: Constants::SiteConfig::DETAILS[:feed_style][:placeholder] %>
|
||||
value: Settings::UserExperience.feed_style,
|
||||
placeholder: Constants::Settings::UserExperience::DETAILS[:feed_style][:placeholder] %>
|
||||
</div>
|
||||
<div class="crayons-field">
|
||||
<%= admin_config_label :feed_strategy %>
|
||||
<%= admin_config_description Constants::SiteConfig::DETAILS[:feed_strategy][:description] %>
|
||||
<%= admin_config_label :feed_strategy, model: Settings::UserExperience %>
|
||||
<%= admin_config_description Constants::Settings::UserExperience::DETAILS[:feed_strategy][:description] %>
|
||||
<%= f.text_field :feed_strategy,
|
||||
class: "crayons-textfield",
|
||||
value: SiteConfig.feed_strategy,
|
||||
placeholder: Constants::SiteConfig::DETAILS[:feed_strategy][:placeholder] %>
|
||||
value: Settings::UserExperience.feed_strategy,
|
||||
placeholder: Constants::Settings::UserExperience::DETAILS[:feed_strategy][:placeholder] %>
|
||||
</div>
|
||||
<div class="crayons-field">
|
||||
<%= admin_config_label :tag_feed_minimum_score %>
|
||||
<%= admin_config_description Constants::SiteConfig::DETAILS[:tag_feed_minimum_score][:description] %>
|
||||
<%= admin_config_label :tag_feed_minimum_score, model: Settings::UserExperience %>
|
||||
<%= admin_config_description Constants::Settings::UserExperience::DETAILS[:tag_feed_minimum_score][:description] %>
|
||||
<%= f.number_field :tag_feed_minimum_score,
|
||||
class: "crayons-textfield",
|
||||
value: SiteConfig.tag_feed_minimum_score,
|
||||
placeholder: Constants::SiteConfig::DETAILS[:tag_feed_minimum_score][:placeholder] %>
|
||||
value: Settings::UserExperience.tag_feed_minimum_score,
|
||||
placeholder: Constants::Settings::UserExperience::DETAILS[:tag_feed_minimum_score][:placeholder] %>
|
||||
</div>
|
||||
<div class="crayons-field">
|
||||
<%= admin_config_label :home_feed_minimum_score %>
|
||||
<%= admin_config_description Constants::SiteConfig::DETAILS[:home_feed_minimum_score][:description] %>
|
||||
<%= admin_config_label :home_feed_minimum_score, model: Settings::UserExperience %>
|
||||
<%= admin_config_description Constants::Settings::UserExperience::DETAILS[:home_feed_minimum_score][:description] %>
|
||||
<%= f.number_field :home_feed_minimum_score,
|
||||
class: "crayons-textfield",
|
||||
value: SiteConfig.home_feed_minimum_score,
|
||||
placeholder: Constants::SiteConfig::DETAILS[:home_feed_minimum_score][:placeholder] %>
|
||||
value: Settings::UserExperience.home_feed_minimum_score,
|
||||
placeholder: Constants::Settings::UserExperience::DETAILS[:home_feed_minimum_score][:placeholder] %>
|
||||
</div>
|
||||
<div class="crayons-field">
|
||||
<%= admin_config_label :default_font %>
|
||||
<%= admin_config_description Constants::SiteConfig::DETAILS[:default_font][:description] %>
|
||||
<%= select_tag "site_config[default_font]",
|
||||
<%= admin_config_label :default_font, model: Settings::UserExperience %>
|
||||
<%= admin_config_description Constants::Settings::UserExperience::DETAILS[:default_font][:description] %>
|
||||
<%= select_tag "settings_user_experience[default_font]",
|
||||
options_for_select(
|
||||
[%w[sans-serif sans_serif], %w[serif serif], %w[open-dyslexic open_dyslexic]],
|
||||
SiteConfig.default_font,
|
||||
Settings::UserExperience.default_font,
|
||||
),
|
||||
multiple: false,
|
||||
class: "selectpicker" %>
|
||||
</div>
|
||||
<div class="crayons-field">
|
||||
<%= admin_config_label :primary_brand_color_hex %>
|
||||
<%= admin_config_description Constants::SiteConfig::DETAILS[:primary_brand_color_hex][:description] %>
|
||||
<%= admin_config_label :primary_brand_color_hex, model: Settings::UserExperience %>
|
||||
<%= admin_config_description Constants::Settings::UserExperience::DETAILS[:primary_brand_color_hex][:description] %>
|
||||
<%= f.color_field :primary_brand_color_hex,
|
||||
class: "crayons-color-selector",
|
||||
value: SiteConfig.primary_brand_color_hex,
|
||||
value: Settings::UserExperience.primary_brand_color_hex,
|
||||
pattern: "^#+([a-fA-F0-9]{6})$",
|
||||
placeholder: Constants::SiteConfig::DETAILS[:primary_brand_color_hex][:placeholder] %>
|
||||
placeholder: Constants::Settings::UserExperience::DETAILS[:primary_brand_color_hex][:placeholder] %>
|
||||
</div>
|
||||
<div class="crayons-field crayons-field--checkbox">
|
||||
<%= f.check_box :public, checked: SiteConfig.public, class: "crayons-checkbox" %>
|
||||
<%= f.check_box :public, checked: Settings::UserExperience.public, class: "crayons-checkbox" %>
|
||||
<div class="mt-0">
|
||||
<%= admin_config_label :public %>
|
||||
<%= admin_config_label :public, model: Settings::UserExperience %>
|
||||
<p class="crayons-field__description">
|
||||
Are most of the site pages (e.g. posts, profiles) public? <strong>Please take precaution when changing this setting.</strong>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
<% cache(release_adjusted_cache_key("top-html-and-config--#{user_signed_in?}")) do %>
|
||||
<body
|
||||
data-user-status="<%= user_logged_in_status %>"
|
||||
class="<%= SiteConfig.default_font.tr("_", "-") %>-article-body default-header"
|
||||
class="<%= Settings::UserExperience.default_font.tr("_", "-") %>-article-body default-header"
|
||||
data-pusher-key="<%= ApplicationConfig["PUSHER_KEY"] %>"
|
||||
data-app-domain="<%= ChatChannel.urlsafe_encoded_app_domain %>"
|
||||
data-honeybadger-key="<%= ApplicationConfig["HONEYBADGER_JS_API_KEY"] %>"
|
||||
|
|
@ -60,10 +60,10 @@
|
|||
<div id="body-styles">
|
||||
<style>
|
||||
:root {
|
||||
--accent-brand: <%= SiteConfig.primary_brand_color_hex %>;
|
||||
--accent-brand-darker: <%= Color::CompareHex.new([SiteConfig.primary_brand_color_hex]).brightness(0.85) %>;
|
||||
--accent-brand-lighter: <%= Color::CompareHex.new([SiteConfig.primary_brand_color_hex]).brightness(1.1) %>;
|
||||
--accent-brand-a10: <%= Color::CompareHex.new([SiteConfig.primary_brand_color_hex]).opacity(0.1) %>;
|
||||
--accent-brand: <%= Settings::UserExperience.primary_brand_color_hex %>;
|
||||
--accent-brand-darker: <%= Color::CompareHex.new([Settings::UserExperience.primary_brand_color_hex]).brightness(0.85) %>;
|
||||
--accent-brand-lighter: <%= Color::CompareHex.new([Settings::UserExperience.primary_brand_color_hex]).brightness(1.1) %>;
|
||||
--accent-brand-a10: <%= Color::CompareHex.new([Settings::UserExperience.primary_brand_color_hex]).opacity(0.1) %>;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ Rails.application.routes.draw do
|
|||
resources :communities, only: [:create]
|
||||
resources :mascots, only: [:create]
|
||||
resources :rate_limits, only: [:create]
|
||||
resources :user_experiences, only: [:create]
|
||||
end
|
||||
namespace :users do
|
||||
resources :gdpr_delete_requests, only: %i[index destroy]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
describe('User experience Section', () => {
|
||||
beforeEach(() => {
|
||||
cy.testSetup();
|
||||
cy.fixture('users/adminUser.json').as('user');
|
||||
|
||||
cy.get('@user').then((user) => {
|
||||
cy.loginUser(user);
|
||||
});
|
||||
});
|
||||
|
||||
describe('default font', () => {
|
||||
it('can change the default font', () => {
|
||||
cy.get('@user').then(({ username }) => {
|
||||
cy.visit('/admin/config');
|
||||
cy.get('#new_settings_user_experience').as('userExperienceSectionForm');
|
||||
|
||||
cy.get('@userExperienceSectionForm')
|
||||
.findByText('User Experience and Brand')
|
||||
.click();
|
||||
cy.get('@userExperienceSectionForm')
|
||||
.get('#settings_user_experience_default_font')
|
||||
// We need to use force here because the select is covered by another element.
|
||||
.select('open-dyslexic', { force: true });
|
||||
|
||||
cy.get('@userExperienceSectionForm')
|
||||
.findByPlaceholderText('Confirmation text')
|
||||
.type(
|
||||
`My username is @${username} and this action is 100% safe and appropriate.`,
|
||||
);
|
||||
|
||||
cy.get('@userExperienceSectionForm')
|
||||
.findByText('Update Site Configuration')
|
||||
.click();
|
||||
|
||||
cy.url().should('contains', '/admin/config');
|
||||
|
||||
cy.findByText('Site configuration was successfully updated.').should(
|
||||
'be.visible',
|
||||
);
|
||||
|
||||
// Page reloaded so need to get a new reference to the form.
|
||||
cy.get('#new_settings_user_experience').as('userExperienceSectionForm');
|
||||
cy.get('@userExperienceSectionForm')
|
||||
.findByText('User Experience and Brand')
|
||||
.click();
|
||||
cy.get('#settings_user_experience_default_font').should(
|
||||
'have.value',
|
||||
'open_dyslexic',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
class CreateSettingsUserExperiences < ActiveRecord::Migration[6.1]
|
||||
def self.up
|
||||
create_table :settings_user_experiences do |t|
|
||||
t.string :var, null: false
|
||||
t.text :value, null: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :settings_user_experiences, :var, unique: true
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :settings_user_experiences
|
||||
end
|
||||
end
|
||||
|
|
@ -1111,6 +1111,14 @@ ActiveRecord::Schema.define(version: 2021_04_26_165234) do
|
|||
t.index ["var"], name: "index_settings_rate_limits_on_var", unique: true
|
||||
end
|
||||
|
||||
create_table "settings_user_experiences", force: :cascade do |t|
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.text "value"
|
||||
t.string "var", null: false
|
||||
t.index ["var"], name: "index_settings_user_experiences_on_var", unique: true
|
||||
end
|
||||
|
||||
create_table "site_configs", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ puts "Seeding with multiplication factor: #{SEEDS_MULTIPLIER}\n\n"
|
|||
##############################################################################
|
||||
# Default development site config if different from production scenario
|
||||
|
||||
SiteConfig.public = true
|
||||
Settings::UserExperience.public = true
|
||||
SiteConfig.waiting_on_first_user = false
|
||||
Settings::Authentication.providers = Authentication::Providers.available
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ Settings::Authentication.providers = Authentication::Providers.available
|
|||
# Put forem into "starter mode"
|
||||
|
||||
if ENV["MODE"] == "STARTER"
|
||||
SiteConfig.public = false
|
||||
Settings::UserExperience.public = false
|
||||
SiteConfig.waiting_on_first_user = true
|
||||
puts "Seeding forem in starter mode to replicate new creator experience"
|
||||
exit # We don't need any models if we're launching things from startup.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
module DataUpdateScripts
|
||||
class MoveUserExperienceSettings
|
||||
def run
|
||||
return if Settings::UserExperience.any?
|
||||
|
||||
Settings::UserExperience.editable_keys.each do |field|
|
||||
Settings::UserExperience.public_send("#{field}=", SiteConfig.public_send(field))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -109,14 +109,14 @@ RSpec.describe UserDecorator, type: :decorator do
|
|||
it "replaces 'default' with font configured for the site in SiteConfig" do
|
||||
expect(user.config_font).to eq("default")
|
||||
%w[sans_serif serif open_dyslexic].each do |font|
|
||||
allow(SiteConfig).to receive(:default_font).and_return(font)
|
||||
allow(Settings::UserExperience).to receive(:default_font).and_return(font)
|
||||
expect(user.decorate.config_font_name).to eq(font)
|
||||
end
|
||||
end
|
||||
|
||||
it "doesn't replace the user's custom selected font" do
|
||||
user_comic_sans = create(:user, config_font: "comic_sans")
|
||||
allow(SiteConfig).to receive(:default_font).and_return("open_dyslexic")
|
||||
allow(Settings::UserExperience).to receive(:default_font).and_return("open_dyslexic")
|
||||
expect(user_comic_sans.decorate.config_font_name).to eq("comic_sans")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
require "rails_helper"
|
||||
require Rails.root.join(
|
||||
"lib/data_update_scripts/20210426023014_move_user_experience_settings.rb",
|
||||
)
|
||||
|
||||
describe DataUpdateScripts::MoveUserExperienceSettings do
|
||||
it "moves existing settings" do
|
||||
allow(SiteConfig).to receive(:default_font).and_return("Comic Sans")
|
||||
allow(SiteConfig).to receive(:primary_brand_color_hex).and_return("#000")
|
||||
|
||||
expect do
|
||||
described_class.new.run
|
||||
end
|
||||
.to change(Settings::UserExperience, :default_font).to("Comic Sans")
|
||||
.and change(Settings::UserExperience, :primary_brand_color_hex).to("#000")
|
||||
end
|
||||
end
|
||||
|
|
@ -685,6 +685,10 @@ RSpec.describe User, type: :model do
|
|||
end
|
||||
|
||||
describe "theming properties" do
|
||||
before do
|
||||
allow(Settings::UserExperience).to receive(:default_font).and_return("sans-serif")
|
||||
end
|
||||
|
||||
it "creates proper body class with defaults" do
|
||||
classes = "default sans-serif-article-body trusted-status-#{user.trusted} #{user.config_navbar}-header"
|
||||
expect(user.decorate.config_body_class).to eq(classes)
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ RSpec.configure do |config|
|
|||
}).to_return(status: 200, body: "", headers: {})
|
||||
|
||||
allow(Settings::Community).to receive(:community_description).and_return("Some description")
|
||||
allow(SiteConfig).to receive(:public).and_return(true)
|
||||
allow(Settings::UserExperience).to receive(:public).and_return(true)
|
||||
allow(SiteConfig).to receive(:waiting_on_first_user).and_return(false)
|
||||
|
||||
# Default to have field a field test available.
|
||||
|
|
|
|||
|
|
@ -851,66 +851,82 @@ RSpec.describe "/admin/config", type: :request do
|
|||
describe "User Experience" do
|
||||
it "updates the feed_style" do
|
||||
feed_style = "basic"
|
||||
post "/admin/config", params: { site_config: { feed_style: feed_style },
|
||||
confirmation: confirmation_message }
|
||||
expect(SiteConfig.feed_style).to eq(feed_style)
|
||||
post admin_settings_user_experiences_path, params: {
|
||||
settings_user_experience: { feed_style: feed_style },
|
||||
confirmation: confirmation_message
|
||||
}
|
||||
expect(Settings::UserExperience.feed_style).to eq(feed_style)
|
||||
end
|
||||
|
||||
it "updates the feed_strategy" do
|
||||
feed_strategy = "optimized"
|
||||
post "/admin/config", params: { site_config: { feed_strategy: feed_strategy },
|
||||
confirmation: confirmation_message }
|
||||
expect(SiteConfig.feed_strategy).to eq(feed_strategy)
|
||||
post admin_settings_user_experiences_path, params: {
|
||||
settings_user_experience: { feed_strategy: feed_strategy },
|
||||
confirmation: confirmation_message
|
||||
}
|
||||
expect(Settings::UserExperience.feed_strategy).to eq(feed_strategy)
|
||||
end
|
||||
|
||||
it "updates the tag_feed_minimum_score" do
|
||||
tag_feed_minimum_score = 3
|
||||
post "/admin/config", params: { site_config: { tag_feed_minimum_score: tag_feed_minimum_score },
|
||||
confirmation: confirmation_message }
|
||||
expect(SiteConfig.tag_feed_minimum_score).to eq(tag_feed_minimum_score)
|
||||
post admin_settings_user_experiences_path, params: {
|
||||
settings_user_experience: { tag_feed_minimum_score: tag_feed_minimum_score },
|
||||
confirmation: confirmation_message
|
||||
}
|
||||
expect(Settings::UserExperience.tag_feed_minimum_score).to eq(tag_feed_minimum_score)
|
||||
end
|
||||
|
||||
it "updates the home_feed_minimum_score" do
|
||||
home_feed_minimum_score = 5
|
||||
post "/admin/config", params: { site_config: { home_feed_minimum_score: home_feed_minimum_score },
|
||||
confirmation: confirmation_message }
|
||||
expect(SiteConfig.home_feed_minimum_score).to eq(home_feed_minimum_score)
|
||||
post admin_settings_user_experiences_path, params: {
|
||||
settings_user_experience: { home_feed_minimum_score: home_feed_minimum_score },
|
||||
confirmation: confirmation_message
|
||||
}
|
||||
expect(Settings::UserExperience.home_feed_minimum_score).to eq(home_feed_minimum_score)
|
||||
end
|
||||
|
||||
it "updates the brand color if proper hex" do
|
||||
hex = "#0a0a0a" # dark enough
|
||||
post "/admin/config", params: { site_config: { primary_brand_color_hex: hex },
|
||||
confirmation: confirmation_message }
|
||||
expect(SiteConfig.primary_brand_color_hex).to eq(hex)
|
||||
post admin_settings_user_experiences_path, params: {
|
||||
settings_user_experience: { primary_brand_color_hex: hex },
|
||||
on: confirmation_message
|
||||
}
|
||||
expect(Settings::UserExperience.primary_brand_color_hex).to eq(hex)
|
||||
end
|
||||
|
||||
it "does not update brand color if hex not contrasting enough" do
|
||||
hex = "#bd746f" # not dark enough
|
||||
post "/admin/config", params: { site_config: { primary_brand_color_hex: hex },
|
||||
confirmation: confirmation_message }
|
||||
expect(SiteConfig.primary_brand_color_hex).not_to eq(hex)
|
||||
expect(Settings::UserExperience.primary_brand_color_hex).not_to eq(hex)
|
||||
end
|
||||
|
||||
it "does not update brand color if hex not a hex with proper format" do
|
||||
hex = "0a0a0a" # dark enough, but not proper format
|
||||
post "/admin/config", params: { site_config: { primary_brand_color_hex: hex },
|
||||
confirmation: confirmation_message }
|
||||
expect(SiteConfig.primary_brand_color_hex).not_to eq(hex)
|
||||
post admin_settings_user_experiences_path, params: {
|
||||
settings_user_experience: { primary_brand_color_hex: hex },
|
||||
confirmation: confirmation_message
|
||||
}
|
||||
expect(Settings::UserExperience.primary_brand_color_hex).not_to eq(hex)
|
||||
end
|
||||
|
||||
it "updates public to true" do
|
||||
is_public = true
|
||||
post "/admin/config", params: { site_config: { public: is_public },
|
||||
confirmation: confirmation_message }
|
||||
expect(SiteConfig.public).to eq(is_public)
|
||||
post admin_settings_user_experiences_path, params: {
|
||||
settings_user_experience: { public: is_public },
|
||||
confirmation: confirmation_message
|
||||
}
|
||||
expect(Settings::UserExperience.public).to eq(is_public)
|
||||
end
|
||||
|
||||
it "updates public to false" do
|
||||
allow(SiteConfig).to receive(:public).and_return(false)
|
||||
allow(Settings::UserExperience).to receive(:public).and_return(false)
|
||||
is_public = false
|
||||
post "/admin/config", params: { site_config: { public: is_public },
|
||||
confirmation: confirmation_message }
|
||||
expect(SiteConfig.public).to eq(is_public)
|
||||
post admin_settings_user_experiences_path, params: {
|
||||
settings_user_experience: { public: is_public },
|
||||
confirmation: confirmation_message
|
||||
}
|
||||
expect(Settings::UserExperience.public).to eq(is_public)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ RSpec.describe "Api::V0::Users", type: :request do
|
|||
end
|
||||
|
||||
it "returns unauthenticated if no authentication and site config is set to private" do
|
||||
allow(SiteConfig).to receive(:public).and_return(false)
|
||||
allow(Settings::UserExperience).to receive(:public).and_return(false)
|
||||
get api_user_path("by_username"), params: { url: user.username }
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
|
@ -78,7 +78,7 @@ RSpec.describe "Api::V0::Users", type: :request do
|
|||
end
|
||||
|
||||
it "returns 200 if no authentication and site config is set to private but user is authenticated" do
|
||||
allow(SiteConfig).to receive(:public).and_return(false)
|
||||
allow(Settings::UserExperience).to receive(:public).and_return(false)
|
||||
get me_api_users_path, params: { access_token: access_token.token }
|
||||
|
||||
response_user = response.parsed_body
|
||||
|
|
|
|||
|
|
@ -65,7 +65,9 @@ RSpec.describe "Articles", type: :request do
|
|||
|
||||
context "when :username param is not given" do
|
||||
let!(:featured_article) { create(:article, featured: true) }
|
||||
let!(:not_featured_article) { create(:article, featured: false, score: SiteConfig.home_feed_minimum_score - 1) }
|
||||
let!(:not_featured_article) do
|
||||
create(:article, featured: false, score: Settings::UserExperience.home_feed_minimum_score - 1)
|
||||
end
|
||||
|
||||
before { get feed_path }
|
||||
|
||||
|
|
@ -157,25 +159,25 @@ RSpec.describe "Articles", type: :request do
|
|||
|
||||
context "with scored articles" do
|
||||
before do
|
||||
allow(SiteConfig).to receive(:home_feed_minimum_score).and_return(10)
|
||||
allow(Settings::UserExperience).to receive(:home_feed_minimum_score).and_return(10)
|
||||
end
|
||||
|
||||
it "does not contain non featured articles with a score below SiteConfig.home_feed_minimum_score" do
|
||||
create(:article, featured: false, score: SiteConfig.home_feed_minimum_score - 1)
|
||||
it "does not contain non featured articles with a score below Settings::UserExperience.home_feed_minimum_score" do
|
||||
create(:article, featured: false, score: Settings::UserExperience.home_feed_minimum_score - 1)
|
||||
|
||||
expect { get feed_path }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
it "contains non featured articles with a score equal to SiteConfig.home_feed_minimum_score" do
|
||||
create(:article, featured: false, score: SiteConfig.home_feed_minimum_score)
|
||||
it "contains non featured articles with a score equal to Settings::UserExperience.home_feed_minimum_score" do
|
||||
create(:article, featured: false, score: Settings::UserExperience.home_feed_minimum_score)
|
||||
|
||||
get feed_path
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
|
||||
it "contains non featured articles with a score above SiteConfig.home_feed_minimum_score" do
|
||||
create(:article, featured: false, score: SiteConfig.home_feed_minimum_score + 1)
|
||||
it "contains non featured articles with a score above Settings::UserExperience.home_feed_minimum_score" do
|
||||
create(:article, featured: false, score: Settings::UserExperience.home_feed_minimum_score + 1)
|
||||
|
||||
get feed_path
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ RSpec.describe "AsyncInfo", type: :request do
|
|||
end
|
||||
|
||||
it "renders normal response even if site config is private" do
|
||||
allow(SiteConfig).to receive(:public).and_return(false)
|
||||
allow(Settings::UserExperience).to receive(:public).and_return(false)
|
||||
get "/async_info/base_data"
|
||||
expect(response.parsed_body.keys).to match_array(%w[broadcast param token])
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ RSpec.describe "GaEvents", type: :request, vcr: vcr_option do
|
|||
end
|
||||
|
||||
it "renders normal response even if site config is private" do
|
||||
allow(SiteConfig).to receive(:public).and_return(false)
|
||||
allow(Settings::UserExperience).to receive(:public).and_return(false)
|
||||
post "/fallback_activity_recorder", params: {
|
||||
path: "/ben", user_language: "en"
|
||||
}.to_json
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ RSpec.describe "Invitations", type: :request do
|
|||
|
||||
describe "Accept invitation" do
|
||||
it "renders normal response even if site config is private" do
|
||||
allow(SiteConfig).to receive(:public).and_return(false)
|
||||
allow(Settings::UserExperience).to receive(:public).and_return(false)
|
||||
get "/users/invitation/accept?invitation_token=blahblahblahblah"
|
||||
# This is a fake token, so the only thing we're testing for here is
|
||||
# that we *do not* land on the "registrations" page which shouldn't
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ RSpec.describe "Stories::Feeds", type: :request do
|
|||
end
|
||||
|
||||
it "returns feed when feed_strategy is basic" do
|
||||
allow(SiteConfig).to receive(:feed_strategy).and_return("basic")
|
||||
allow(Settings::UserExperience).to receive(:feed_strategy).and_return("basic")
|
||||
get "/stories/feed"
|
||||
expect(response_article).to include(
|
||||
"id" => article.id,
|
||||
|
|
@ -45,7 +45,7 @@ RSpec.describe "Stories::Feeds", type: :request do
|
|||
end
|
||||
|
||||
it "returns feed when feed_strategy is optimized" do
|
||||
allow(SiteConfig).to receive(:feed_strategy).and_return("optimized")
|
||||
allow(Settings::UserExperience).to receive(:feed_strategy).and_return("optimized")
|
||||
get "/stories/feed"
|
||||
expect(response_article).to include(
|
||||
"id" => article.id,
|
||||
|
|
@ -128,7 +128,7 @@ RSpec.describe "Stories::Feeds", type: :request do
|
|||
end
|
||||
|
||||
it "returns feed when feed_strategy is basic" do
|
||||
allow(SiteConfig).to receive(:feed_strategy).and_return("basic")
|
||||
allow(Settings::UserExperience).to receive(:feed_strategy).and_return("basic")
|
||||
get "/stories/feed"
|
||||
expect(response_article).to include(
|
||||
"id" => article.id,
|
||||
|
|
@ -142,7 +142,7 @@ RSpec.describe "Stories::Feeds", type: :request do
|
|||
end
|
||||
|
||||
it "returns feed when feed_strategy is optimized" do
|
||||
allow(SiteConfig).to receive(:feed_strategy).and_return("optimized")
|
||||
allow(Settings::UserExperience).to receive(:feed_strategy).and_return("optimized")
|
||||
get "/stories/feed"
|
||||
expect(response_article).to include(
|
||||
"id" => article.id,
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ RSpec.describe "StoriesIndex", type: :request do
|
|||
end
|
||||
|
||||
it "renders registration page if site config is private" do
|
||||
allow(SiteConfig).to receive(:public).and_return(false)
|
||||
allow(Settings::UserExperience).to receive(:public).and_return(false)
|
||||
|
||||
get root_path
|
||||
expect(response.body).to include("Continue with")
|
||||
|
|
@ -105,7 +105,7 @@ RSpec.describe "StoriesIndex", type: :request do
|
|||
end
|
||||
|
||||
it "does not set cache-related headers if private" do
|
||||
allow(SiteConfig).to receive(:public).and_return(false)
|
||||
allow(Settings::UserExperience).to receive(:public).and_return(false)
|
||||
get "/"
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ RSpec.describe "StoriesIndex", type: :request do
|
|||
it "shows only one cover if basic feed style" do
|
||||
create_list(:article, 3, featured: true, score: 20, main_image: "https://example.com/image.jpg")
|
||||
|
||||
allow(SiteConfig).to receive(:feed_style).and_return("basic")
|
||||
allow(Settings::UserExperience).to receive(:feed_style).and_return("basic")
|
||||
get "/"
|
||||
expect(response.body.scan(/(?=class="crayons-story__cover crayons-story__cover__image)/).count).to be 1
|
||||
end
|
||||
|
|
@ -156,7 +156,7 @@ RSpec.describe "StoriesIndex", type: :request do
|
|||
it "shows multiple cover images if rich feed style" do
|
||||
create_list(:article, 3, featured: true, score: 20, main_image: "https://example.com/image.jpg")
|
||||
|
||||
allow(SiteConfig).to receive(:feed_style).and_return("rich")
|
||||
allow(Settings::UserExperience).to receive(:feed_style).and_return("rich")
|
||||
get "/"
|
||||
expect(response.body.scan(/(?=class="crayons-story__cover crayons-story__cover__image)/).count).to be > 1
|
||||
end
|
||||
|
|
@ -199,7 +199,7 @@ RSpec.describe "StoriesIndex", type: :request do
|
|||
context "with campaign_sidebar" do
|
||||
before do
|
||||
allow(Settings::Campaign).to receive(:featured_tags).and_return("mytag,yourtag")
|
||||
allow(SiteConfig).to receive(:home_feed_minimum_score).and_return(7)
|
||||
allow(Settings::UserExperience).to receive(:home_feed_minimum_score).and_return(7)
|
||||
|
||||
a_body = "---\ntitle: Super-sheep#{rand(1000)}\npublished: true\ntags: heyheyhey,mytag\n---\n\nHello"
|
||||
create(:article, approved: true, body_markdown: a_body, score: 1)
|
||||
|
|
@ -402,7 +402,7 @@ RSpec.describe "StoriesIndex", type: :request do
|
|||
end
|
||||
|
||||
it "renders properly even if site config is private" do
|
||||
allow(SiteConfig).to receive(:public).and_return(false)
|
||||
allow(Settings::UserExperience).to receive(:public).and_return(false)
|
||||
get "/t/#{tag.name}"
|
||||
expect(response.body).to include("crayons-tabs__item crayons-tabs__item--current")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ RSpec.describe Articles::Feeds::LargeForemExperimental, type: :service do
|
|||
|
||||
before do
|
||||
article.update(published_at: 1.week.ago)
|
||||
allow(SiteConfig).to receive(:home_feed_minimum_score).and_return(0)
|
||||
allow(Settings::UserExperience).to receive(:home_feed_minimum_score).and_return(0)
|
||||
end
|
||||
|
||||
it "returns a featured article and correctly scored other articles", :aggregate_failures do
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ seeder = Seeder.new
|
|||
##############################################################################
|
||||
# Default development site config if different from production scenario
|
||||
|
||||
SiteConfig.public = true
|
||||
Settings::UserExperience.public = true
|
||||
SiteConfig.waiting_on_first_user = false
|
||||
|
||||
##############################################################################
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue