0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-09 21:45:25 +08:00
discourse/spec/services/admin_dashboard_section_loader_spec.rb
Krzysztof Kotlarek 1101080b10
FEATURE: Add a Support section to the admin dashboard0 (#41359)
Previously, the redesigned admin dashboard couldn't show how a community
handles its support topics, and plugins had no way to contribute their
own dashboard sections — only individual highlight KPIs.
This change adds a register_admin_dashboard_section plugin API and uses
it in discourse-solved to add a Support section for communities with at
least one category where accepted answers are enabled.

**Core: register_admin_dashboard_section**

A new plugin API (mirroring the existing highlight-KPI registration)
lets a plugin register a dashboard section with an id, an optional
enabled gate, and a data loader; the JS
api.registerAdminDashboardSection(id, component) supplies the matching
client component. The section configuration, parallel loader, and
dashboard component render registered sections dynamically alongside the
core ones.

Also in the core commit: the dev database pool is raised so the
dashboard's parallel section loader isn't starved of connections with an
extra section, and a stray db-section__row-block divider on mobile is
fixed.

**Support section (discourse-solved)**

For the selected period, scoped to categories where "Allow topic owner
and staff to mark a reply as the solution" is enabled, the section
shows:

- KPIs with previous-period deltas: resolution rate (drill-through to
the accepted_solutions report), staff involvement, and average
first-reply time.
- Topic outcomes - resolved / in progress / unanswered.
- Who's answering - a breakdown of repliers by trust level and staff.
- Response time distribution - first-reply times bucketed into <1h,
1–4h, 4–24h, >24h, with a faster/slower trend.
- A category filter when more than one support category exists.

Screenshots:
<img width="804" height="748" alt="Screenshot 2026-07-02 at 10 58 13 am"
src="https://github.com/user-attachments/assets/8e938a5e-93df-446f-885c-3d779431ab9b"
/>
<img width="396" height="789" alt="Screenshot 2026-07-02 at 10 58 01 am"
src="https://github.com/user-attachments/assets/9a9a171d-42d9-482c-950c-5daad055a746"
/>

---------

Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com>
2026-07-06 09:31:51 +08:00

101 lines
3.2 KiB
Ruby
Vendored

# frozen_string_literal: true
describe AdminDashboardSectionLoader do
fab!(:admin)
after do
if thread_pool = described_class.instance_variable_get(:@thread_pool)
thread_pool.shutdown
thread_pool.wait_for_termination(timeout: 1)
described_class.remove_instance_variable(:@thread_pool)
end
end
describe ".build" do
it "ensures the sections are built in order with current user and dates" do
AdminDashboardSiteTraffic
.expects(:build)
.with do |kwargs|
kwargs[:start_date] == "2026-05-01" && kwargs[:end_date] == "2026-05-07" &&
kwargs[:guardian].is_a?(Guardian) && kwargs[:guardian].user.id == admin.id
end
.returns({ value: "traffic" })
AdminDashboardEngagement
.expects(:build)
.with(start_date: "2026-05-01", end_date: "2026-05-07", current_user: admin)
.returns({ value: "engagement" })
AdminDashboardSearch
.expects(:build)
.with(start_date: "2026-05-01", end_date: "2026-05-07")
.returns({ value: "search" })
expect(
described_class.build(
section_ids: %w[traffic engagement search],
current_user: admin,
start_date: "2026-05-01",
end_date: "2026-05-07",
),
).to eq(
[
{ id: "traffic", data: { value: "traffic" } },
{ id: "engagement", data: { value: "engagement" } },
{ id: "search", data: { value: "search" } },
],
)
end
end
describe "plugin sections" do
it "routes a registered plugin section id to its loader block" do
loader = ->(start_date:, end_date:, current_user:) do
{ value: "support", user: current_user.id }
end
DiscoursePluginRegistry.stubs(:admin_dashboard_sections).returns(
[{ id: "support", enabled: -> { true }, loader: loader }],
)
expect(
described_class.build(
section_ids: ["support"],
current_user: admin,
start_date: "2026-05-01",
end_date: "2026-05-07",
),
).to eq([{ id: "support", data: { value: "support", user: admin.id } }])
end
it "returns nil data for an unknown section id" do
DiscoursePluginRegistry.stubs(:admin_dashboard_sections).returns([])
expect(
described_class.build(
section_ids: ["frobnitz"],
current_user: admin,
start_date: "2026-05-01",
end_date: "2026-05-07",
),
).to eq([{ id: "frobnitz", data: nil }])
end
end
describe ".pool_size" do
it "caps at the DB connection pool, reserving one for the request thread" do
ActiveRecord::Base.connection_pool.stubs(:size).returns(3)
expect(described_class.pool_size).to eq(2)
end
it "uses the desired count when the pool has room to spare" do
ActiveRecord::Base.connection_pool.stubs(:size).returns(100)
desired =
AdminDashboardSectionConfiguration::KNOWN_SECTIONS.size +
DiscoursePluginRegistry.admin_dashboard_sections.size
expect(described_class.pool_size).to eq(desired)
end
it "never drops below one" do
ActiveRecord::Base.connection_pool.stubs(:size).returns(1)
expect(described_class.pool_size).to eq(1)
end
end
end