FEATURE: public custom sidebar sections visible to anonymous (#20931)

Previously, public custom sections were only visible to logged-in users. In this PR, we are making them visible to anonymous as well.

The reason is that Community Section will be moved into custom section model to be easily editable by admins.
This commit is contained in:
Krzysztof Kotlarek
2023-04-06 08:55:47 +10:00
committed by GitHub
parent cd6d47e012
commit e586f6052f
11 changed files with 168 additions and 14 deletions

View File

@ -0,0 +1,31 @@
<div class="sidebar-custom-sections">
{{#each this.sections as |section|}}
<Sidebar::Section
@sectionName={{section.slug}}
@headerLinkText={{section.decoratedTitle}}
@collapsable={{true}}
>
{{#each section.links as |link|}}
{{#if link.external}}
<Sidebar::SectionLink
@linkName={{link.name}}
@content={{replace-emoji link.name}}
@prefixType="icon"
@prefixValue={{link.icon}}
@href={{link.value}}
/>
{{else}}
<Sidebar::SectionLink
@linkName={{link.name}}
@route={{link.route}}
@models={{link.models}}
@query={{link.query}}
@content={{replace-emoji link.name}}
@prefixType="icon"
@prefixValue={{link.icon}}
/>
{{/if}}
{{/each}}
</Sidebar::Section>
{{/each}}
</div>

View File

@ -0,0 +1,17 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import Section from "discourse/components/sidebar/user/section";
export default class SidebarAnonymousCustomSections extends Component {
@service router;
@service site;
get sections() {
return this.site.anonymous_sidebar_sections?.map((section) => {
return new Section({
section,
router: this.router,
});
});
}
}

View File

@ -1,5 +1,6 @@
<div class="sidebar-sections sidebar-sections-anonymous"> <div class="sidebar-sections sidebar-sections-anonymous">
<Sidebar::Anonymous::CommunitySection @collapsable={{@collapsableSections}} /> <Sidebar::Anonymous::CommunitySection @collapsable={{@collapsableSections}} />
<Sidebar::Anonymous::CustomSections />
<Sidebar::Anonymous::CategoriesSection <Sidebar::Anonymous::CategoriesSection
@collapsable={{@collapsableSections}} @collapsable={{@collapsableSections}}
/> />

View File

@ -23,13 +23,13 @@ export default class Section {
} }
get decoratedTitle() { get decoratedTitle() {
return this.section.public && this.currentUser.staff return this.section.public && this.currentUser?.staff
? htmlSafe(`${iconHTML("globe")} ${this.section.title}`) ? htmlSafe(`${iconHTML("globe")} ${this.section.title}`)
: this.section.title; : this.section.title;
} }
get headerActions() { get headerActions() {
if (!this.section.public || this.currentUser.staff) { if (!this.section.public || this.currentUser?.staff) {
return [ return [
{ {
action: () => { action: () => {

View File

@ -25,6 +25,7 @@ class SidebarSectionsController < ApplicationController
nil, nil,
group_ids: SiteSetting.enable_custom_sidebar_sections_map, group_ids: SiteSetting.enable_custom_sidebar_sections_map,
) )
Site.clear_anon_cache!
end end
render json: SidebarSectionSerializer.new(sidebar_section) render json: SidebarSectionSerializer.new(sidebar_section)
@ -48,6 +49,7 @@ class SidebarSectionsController < ApplicationController
nil, nil,
group_ids: SiteSetting.enable_custom_sidebar_sections_map, group_ids: SiteSetting.enable_custom_sidebar_sections_map,
) )
Site.clear_anon_cache!
end end
render json: SidebarSectionSerializer.new(sidebar_section) render json: SidebarSectionSerializer.new(sidebar_section)

View File

@ -23,6 +23,8 @@ class SidebarSection < ActiveRecord::Base
maximum: MAX_TITLE_LENGTH, maximum: MAX_TITLE_LENGTH,
} }
scope :public_sections, -> { where("public") }
private private
def set_system_user_for_public_section def set_system_user_for_public_section

View File

@ -79,8 +79,9 @@ class CurrentUserSerializer < BasicUserSerializer
def sidebar_sections def sidebar_sections
SidebarSection SidebarSection
.public_sections
.or(SidebarSection.where(user_id: object.id))
.includes(sidebar_section_links: :linkable) .includes(sidebar_section_links: :linkable)
.where("public OR user_id = ?", object.id)
.order("(public IS TRUE) DESC") .order("(public IS TRUE) DESC")
.map { |section| SidebarSectionSerializer.new(section, root: false) } .map { |section| SidebarSectionSerializer.new(section, root: false) }
end end

View File

@ -39,6 +39,7 @@ class SiteSerializer < ApplicationSerializer
:displayed_about_plugin_stat_groups, :displayed_about_plugin_stat_groups,
:show_welcome_topic_banner, :show_welcome_topic_banner,
:anonymous_default_sidebar_tags, :anonymous_default_sidebar_tags,
:anonymous_sidebar_sections,
:whispers_allowed_groups_names, :whispers_allowed_groups_names,
) )
@ -260,6 +261,17 @@ class SiteSerializer < ApplicationSerializer
SiteSetting.default_sidebar_tags.present? SiteSetting.default_sidebar_tags.present?
end end
def anonymous_sidebar_sections
SidebarSection
.public_sections
.includes(sidebar_section_links: :linkable)
.map { |section| SidebarSectionSerializer.new(section, root: false) }
end
def include_anonymous_sidebar_sections?
scope.anonymous?
end
def whispers_allowed_groups_names def whispers_allowed_groups_names
Group.where(id: SiteSetting.whispers_allowed_groups_map).pluck(:name) Group.where(id: SiteSetting.whispers_allowed_groups_map).pluck(:name)
end end

View File

@ -192,6 +192,70 @@ RSpec.describe SiteSerializer do
end end
end end
describe "#anonymous_sidebar_sections" do
fab!(:user) { Fabricate(:user) }
fab!(:public_sidebar_section) do
Fabricate(:sidebar_section, title: "Public section", public: true)
end
fab!(:private_sidebar_section) do
Fabricate(:sidebar_section, title: "Private section", user: user, public: false)
end
it "is not included in the serialised object when user is not anonymous" do
guardian = Guardian.new(user)
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
end
it "includes only public sidebar sections serialised object when user is anonymous" do
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(serialized[:anonymous_sidebar_sections].map(&:title)).to eq(["Public section"])
end
it "eager loads sidebar_urls" do
public_section_link =
Fabricate(:custom_sidebar_section_link, user: user, sidebar_section: public_sidebar_section)
# warmup
described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
initial_count =
track_sql_queries do
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(
serialized[:anonymous_sidebar_sections].map { |sidebar_section| sidebar_section.id },
).to eq([public_sidebar_section.id])
expect(serialized[:anonymous_sidebar_sections].first.links.map { |link| link.id }).to eq(
[public_section_link.linkable.id],
)
end.count
public_section_link_2 =
Fabricate(:custom_sidebar_section_link, user: user, sidebar_section: public_sidebar_section)
public_section_link_3 =
Fabricate(:custom_sidebar_section_link, user: user, sidebar_section: public_sidebar_section)
final_count =
track_sql_queries do
serialized = described_class.new(Site.new(guardian), scope: guardian, root: false).as_json
expect(
serialized[:anonymous_sidebar_sections].map { |sidebar_section| sidebar_section.id },
).to eq([public_sidebar_section.id])
expect(serialized[:anonymous_sidebar_sections].first.links.map { |link| link.id }).to eq(
[
public_section_link.linkable.id,
public_section_link_2.linkable.id,
public_section_link_3.linkable.id,
],
)
end.count
expect(final_count).to eq(initial_count)
end
end
describe "#top_tags" do describe "#top_tags" do
fab!(:tag) { Fabricate(:tag) } fab!(:tag) { Fabricate(:tag) }

View File

@ -12,10 +12,10 @@ describe "Custom sidebar sections", type: :system, js: true do
Fabricate(:group_user, group: group, user: user) Fabricate(:group_user, group: group, user: user)
Fabricate(:group_user, group: group, user: admin) Fabricate(:group_user, group: group, user: admin)
SiteSetting.enable_custom_sidebar_sections = group.id.to_s SiteSetting.enable_custom_sidebar_sections = group.id.to_s
sign_in user
end end
it "allows the user to create custom section" do it "allows the user to create custom section" do
sign_in user
visit("/latest") visit("/latest")
sidebar.open_new_custom_section sidebar.open_new_custom_section
@ -30,11 +30,12 @@ describe "Custom sidebar sections", type: :system, js: true do
section_modal.save section_modal.save
expect(page).to have_button("My section") expect(sidebar).to have_section("My section")
expect(sidebar).to have_link("Sidebar Tags") expect(sidebar).to have_link("Sidebar Tags")
end end
it "allows the user to create custom section with /my link" do it "allows the user to create custom section with /my link" do
sign_in user
visit("/latest") visit("/latest")
sidebar.open_new_custom_section sidebar.open_new_custom_section
@ -49,11 +50,12 @@ describe "Custom sidebar sections", type: :system, js: true do
section_modal.save section_modal.save
expect(page).to have_button("My section") expect(sidebar).to have_section("My section")
expect(sidebar).to have_link("My preferences") expect(sidebar).to have_link("My preferences")
end end
it "allows the user to create custom section with external link" do it "allows the user to create custom section with external link" do
sign_in user
visit("/latest") visit("/latest")
sidebar.open_new_custom_section sidebar.open_new_custom_section
@ -71,7 +73,7 @@ describe "Custom sidebar sections", type: :system, js: true do
section_modal.save section_modal.save
expect(page).to have_button("My section") expect(sidebar).to have_section("My section")
expect(sidebar).to have_link("Discourse Homepage", href: "https://discourse.org") expect(sidebar).to have_link("Discourse Homepage", href: "https://discourse.org")
end end
@ -82,6 +84,7 @@ describe "Custom sidebar sections", type: :system, js: true do
sidebar_url_2 = Fabricate(:sidebar_url, name: "Sidebar Categories", value: "/categories") sidebar_url_2 = Fabricate(:sidebar_url, name: "Sidebar Categories", value: "/categories")
Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_2) Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_2)
sign_in user
visit("/latest") visit("/latest")
sidebar.edit_custom_section("My section") sidebar.edit_custom_section("My section")
@ -93,7 +96,7 @@ describe "Custom sidebar sections", type: :system, js: true do
section_modal.save section_modal.save
expect(page).to have_button("Edited section") expect(sidebar).to have_section("Edited section")
expect(sidebar).to have_link("Edited Tag") expect(sidebar).to have_link("Edited Tag")
expect(page).not_to have_link("Sidebar Categories") expect(page).not_to have_link("Sidebar Categories")
@ -106,6 +109,7 @@ describe "Custom sidebar sections", type: :system, js: true do
sidebar_url_2 = Fabricate(:sidebar_url, name: "Sidebar Categories", value: "/categories") sidebar_url_2 = Fabricate(:sidebar_url, name: "Sidebar Categories", value: "/categories")
Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_2) Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_2)
sign_in user
visit("/latest") visit("/latest")
within(".sidebar-custom-sections .sidebar-section-link-wrapper:nth-child(1)") do within(".sidebar-custom-sections .sidebar-section-link-wrapper:nth-child(1)") do
expect(page).to have_css(".sidebar-section-link-sidebar-tags") expect(page).to have_css(".sidebar-section-link-sidebar-tags")
@ -127,15 +131,16 @@ describe "Custom sidebar sections", type: :system, js: true do
end end
it "does not allow the user to edit public section" do it "does not allow the user to edit public section" do
sidebar_section = Fabricate(:sidebar_section, title: "Public section", user: user, public: true) sidebar_section = Fabricate(:sidebar_section, title: "Public section", public: true)
sidebar_url_1 = Fabricate(:sidebar_url, name: "Sidebar Tags", value: "/tags") sidebar_url_1 = Fabricate(:sidebar_url, name: "Sidebar Tags", value: "/tags")
Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_1) Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_1)
sidebar_url_2 = Fabricate(:sidebar_url, name: "Sidebar Categories", value: "/categories") sidebar_url_2 = Fabricate(:sidebar_url, name: "Sidebar Categories", value: "/categories")
Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_2) Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_2)
sign_in user
visit("/latest") visit("/latest")
expect(page).to have_button("Public section") expect(sidebar).to have_section("Public section")
find(".sidebar-section[data-section-name='public-section']").hover find(".sidebar-section[data-section-name='public-section']").hover
@ -153,6 +158,7 @@ describe "Custom sidebar sections", type: :system, js: true do
sidebar_url_1 = Fabricate(:sidebar_url, name: "tags", value: "/tags") sidebar_url_1 = Fabricate(:sidebar_url, name: "tags", value: "/tags")
Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_1) Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_1)
sign_in user
visit("/latest") visit("/latest")
sidebar.edit_custom_section("My section") sidebar.edit_custom_section("My section")
@ -160,7 +166,7 @@ describe "Custom sidebar sections", type: :system, js: true do
section_modal.delete section_modal.delete
section_modal.confirm_delete section_modal.confirm_delete
expect(page).not_to have_button("My section") expect(sidebar).not_to have_section("My section")
end end
it "allows admin to create, edit and delete public section" do it "allows admin to create, edit and delete public section" do
@ -173,7 +179,7 @@ describe "Custom sidebar sections", type: :system, js: true do
section_modal.mark_as_public section_modal.mark_as_public
section_modal.save section_modal.save
expect(page).to have_button("Public section") expect(sidebar).to have_section("Public section")
expect(sidebar).to have_link("Sidebar Tags") expect(sidebar).to have_link("Sidebar Tags")
expect(page).to have_css(".sidebar-section[data-section-name='public-section'] .d-icon-globe") expect(page).to have_css(".sidebar-section[data-section-name='public-section'] .d-icon-globe")
@ -181,16 +187,30 @@ describe "Custom sidebar sections", type: :system, js: true do
section_modal.fill_name("Edited public section") section_modal.fill_name("Edited public section")
section_modal.save section_modal.save
expect(page).to have_button("Edited public section") expect(sidebar).to have_section("Edited public section")
sidebar.edit_custom_section("Edited public section") sidebar.edit_custom_section("Edited public section")
section_modal.delete section_modal.delete
section_modal.confirm_delete section_modal.confirm_delete
expect(page).not_to have_button("Edited public section") expect(sidebar).not_to have_section("Edited public section")
end
it "shows anonymous public sections" do
sidebar_section = Fabricate(:sidebar_section, title: "Public section", public: true)
sidebar_url_1 = Fabricate(:sidebar_url, name: "Sidebar Tags", value: "/tags")
Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_1)
sidebar_url_2 = Fabricate(:sidebar_url, name: "Sidebar Categories", value: "/categories")
Fabricate(:sidebar_section_link, sidebar_section: sidebar_section, linkable: sidebar_url_2)
visit("/latest")
expect(sidebar).to have_section("Public section")
expect(sidebar).to have_link("Sidebar Tags")
expect(sidebar).to have_link("Sidebar Categories")
end end
it "validates custom section fields" do it "validates custom section fields" do
sign_in user
visit("/latest") visit("/latest")
sidebar.open_new_custom_section sidebar.open_new_custom_section

View File

@ -36,6 +36,10 @@ module PageObjects
def custom_section_modal_title def custom_section_modal_title
find("#discourse-modal-title") find("#discourse-modal-title")
end end
def has_section?(name)
find(".sidebar-wrapper").has_button?(name)
end
end end
end end
end end