discourse/spec/services/admin_dashboard_section_configuration_spec.rb
Natalie Tay 37e13f0afc
FEATURE: Allow admins to configure sections in the experimental admin dashboard (#39964)
Allow admins to configure which sections they want to see.

A few tasks in this commit:
- rename the "Customise" menu to "Configure" and removes the calendar
icon from the Custom date range option
- implements the Configure menu:
  - toggle section visibility
  - drag-and-drop reorder on desktop, up/down arrows on mobile
  - empty state when all sections are off
- persists configuration site-wide on menu close via
`admin_dashboard_sections` site setting
  - admins and moderators can view, but admins write and moderators read
- `AdminDashboardSectionConfiguration` owns configuration, so storage
can change later if we move to per-user
- `PUT /admin/dashboard/configuration.json` is admin-only (mentioned
above)
  - audit log (`SiteSetting.set_and_log`)
- the backend filters `sections` payload to only the visible-and-ordered
list, so mods render the layout admins set without seeing the
configuration meta
    - hidden sections skip their server-side data computation
2026-05-13 18:03:39 +08:00

142 lines
4.5 KiB
Ruby

# frozen_string_literal: true
describe AdminDashboardSectionConfiguration do
fab!(:admin)
before { SiteSetting.admin_dashboard_sections = "highlights|reports|traffic|engagement" }
describe "the site setting" do
it "rejects unknown ids at the validator level" do
expect { SiteSetting.admin_dashboard_sections = "frobnitz|highlights" }.to raise_error(
Discourse::InvalidParameters,
)
end
end
describe ".visible_section_ids" do
it "returns ids in stored order" do
SiteSetting.admin_dashboard_sections = "reports|highlights|engagement"
expect(described_class.visible_section_ids).to eq(%w[reports highlights engagement])
end
it "dedupes repeated ids, keeping the first occurrence" do
SiteSetting.admin_dashboard_sections = "highlights|highlights|reports"
expect(described_class.visible_section_ids).to eq(%w[highlights reports])
end
it "returns an empty array when the setting is empty" do
SiteSetting.admin_dashboard_sections = ""
expect(described_class.visible_section_ids).to eq([])
end
it "falls back to canonical defaults when the raw value is non-empty but unusable" do
SiteSetting.stubs(:admin_dashboard_sections).returns("|||")
expect(described_class.visible_section_ids).to eq(described_class::KNOWN_SECTIONS)
end
it "falls back to canonical defaults when every id is unknown" do
SiteSetting.stubs(:admin_dashboard_sections).returns("frobnitz|wibble")
expect(described_class.visible_section_ids).to eq(described_class::KNOWN_SECTIONS)
end
end
describe ".sections" do
it "returns visible sections first, then hidden in canonical order" do
SiteSetting.admin_dashboard_sections = "reports|engagement"
expect(described_class.sections).to eq(
[
{ id: "reports", visible: true },
{ id: "engagement", visible: true },
{ id: "highlights", visible: false },
{ id: "traffic", visible: false },
],
)
end
it "marks every known section visible: false when the setting is empty" do
SiteSetting.admin_dashboard_sections = ""
expect(described_class.sections.map { |s| s[:visible] }.uniq).to eq([false])
expect(described_class.sections.map { |s| s[:id] }).to match_array(
described_class::KNOWN_SECTIONS,
)
end
end
describe ".update" do
it "persists the visible ids as a pipe-delimited string" do
described_class.update(
[{ id: "reports", visible: true }, { id: "highlights", visible: true }],
actor: admin,
)
expect(SiteSetting.admin_dashboard_sections).to eq("reports|highlights")
end
it "drops sections with visible: false" do
described_class.update(
[
{ id: "highlights", visible: true },
{ id: "traffic", visible: false },
{ id: "reports", visible: true },
],
actor: admin,
)
expect(SiteSetting.admin_dashboard_sections).to eq("highlights|reports")
end
it "coerces non-boolean visible values" do
described_class.update(
[
{ id: "highlights", visible: "true" },
{ id: "reports", visible: "false" },
{ id: "engagement", visible: 1 },
],
actor: admin,
)
expect(SiteSetting.admin_dashboard_sections).to eq("highlights|engagement")
end
it "drops unknown section ids" do
described_class.update(
[{ id: "frobnitz", visible: true }, { id: "highlights", visible: true }],
actor: admin,
)
expect(SiteSetting.admin_dashboard_sections).to eq("highlights")
end
it "accepts string keys as well as symbols" do
described_class.update(
[{ "id" => "highlights", "visible" => true }, { "id" => "reports", "visible" => true }],
actor: admin,
)
expect(SiteSetting.admin_dashboard_sections).to eq("highlights|reports")
end
it "writes empty string when all sections are hidden" do
described_class.update(
[{ id: "highlights", visible: false }, { id: "reports", visible: false }],
actor: admin,
)
expect(SiteSetting.admin_dashboard_sections).to eq("")
end
it "returns the new sections snapshot" do
result =
described_class.update(
[{ id: "engagement", visible: true }, { id: "highlights", visible: true }],
actor: admin,
)
expect(result.first(2)).to eq(
[{ id: "engagement", visible: true }, { id: "highlights", visible: true }],
)
end
end
end