DEV: Use default admin routes for plugins with settings (#30941)

This change adds a sidebar link for each plugin that fulfils the following criteria:

- Does not have an explicit admin route defined in the plugin.
- Has at least one site setting (not including enabled/disabled.)

That sidebar link leads to the automatically generated plugin show settings page.
This commit is contained in:
Ted Johansson 2025-02-04 14:57:28 +08:00 committed by GitHub
parent 9991eacef4
commit 503f9b6f02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 70 additions and 49 deletions

View File

@ -1,6 +1,7 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
import { service } from "@ember/service";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
@ -28,6 +29,11 @@ export default class AdminAreaSettings extends Component {
return !this.loading && this.settings.length > 0;
}
@action
async reloadSettings() {
await this.#loadSettings();
}
@bind
async #loadSettings() {
this.loading = true;
@ -69,6 +75,7 @@ export default class AdminAreaSettings extends Component {
<div
class="content-body admin-config-area__settings admin-detail pull-left"
{{didUpdate this.reloadSettings @plugin}}
>
{{#if this.showSettings}}
<AdminFilteredSiteSettings

View File

@ -1,5 +1,5 @@
import { cached, tracked } from "@glimmer/tracking";
import { capitalize, dasherize } from "@ember/string";
import { dasherize } from "@ember/string";
import { snakeCaseToCamelCase } from "discourse/lib/case-converter";
import I18n, { i18n } from "discourse-i18n";
@ -50,29 +50,7 @@ export default class AdminPlugin {
@cached
get nameTitleized() {
// The category name is better in a lot of cases, as it's a human-inputted
// translation, and we can handle things like SAML instead of showing them
// as Saml from discourse-saml. We can fall back to the programmatic version
// though if needed.
let name;
if (this.translatedCategoryName) {
name = this.translatedCategoryName;
} else {
name = this.name
.split(/[-_]/)
.map((word) => {
return capitalize(word);
})
.join(" ");
}
// Cuts down on repetition.
const discoursePrefix = "Discourse ";
if (name.startsWith(discoursePrefix)) {
name = name.slice(discoursePrefix.length);
}
return name;
return this.translatedCategoryName || this.humanizedName;
}
@cached

View File

@ -60,7 +60,9 @@ class SidebarAdminSectionLink extends BaseCustomSidebarSectionLink {
get text() {
return this.adminSidebarNavLink.label
? i18n(this.adminSidebarNavLink.label)
? i18n(this.adminSidebarNavLink.label, {
translatedFallback: this.adminSidebarNavLink.text,
})
: this.adminSidebarNavLink.text;
}
@ -316,6 +318,7 @@ function pluginAdminRouteLinks(router) {
? [plugin.admin_route.location]
: [],
label: plugin.admin_route.label,
text: plugin.humanized_name,
icon: "gear",
};
});

View File

@ -12,6 +12,7 @@ acceptance("Admin - Plugins", function (needs) {
{
id: "some-test-plugin",
name: "some-test-plugin",
humanized_name: "Some test plugin",
about: "Plugin description",
version: "0.1",
url: "https://example.com",
@ -44,7 +45,7 @@ acceptance("Admin - Plugins", function (needs) {
.dom(
"table.admin-plugins-list .admin-plugins-list__row .admin-plugins-list__name-details .admin-plugins-list__name-with-badges .admin-plugins-list__name"
)
.hasText("Some Test Plugin", "displays the plugin in the table");
.hasText("Some test plugin", "displays the plugin in the table");
assert
.dom(".admin-plugins .admin-config-page .alert-error")

View File

@ -1,19 +0,0 @@
import { setupTest } from "ember-qunit";
import { module, test } from "qunit";
import AdminPlugin from "admin/models/admin-plugin";
module("Unit | Model | admin plugin", function (hooks) {
setupTest(hooks);
test("nameTitleized", function (assert) {
const adminPlugin = AdminPlugin.create({
name: "docker_manager",
});
assert.strictEqual(
adminPlugin.nameTitleized,
"Docker Manager",
"it should return titleized name replacing underscores with spaces"
);
});
});

View File

@ -659,6 +659,7 @@ class ApplicationController < ActionController::Base
.map do |plugin|
{
name: plugin.name.downcase,
humanized_name: plugin.humanized_name,
admin_route: plugin.full_admin_route,
enabled: plugin.enabled?,
}

View File

@ -11,6 +11,7 @@ class AdminPluginSerializer < ApplicationSerializer
:enabled_setting,
:has_settings,
:has_only_enabled_setting,
:humanized_name,
:is_official,
:is_discourse_owned,
:label,
@ -27,6 +28,10 @@ class AdminPluginSerializer < ApplicationSerializer
object.metadata.name
end
def humanized_name
object.humanized_name
end
def about
object.metadata.about
end

View File

@ -120,7 +120,12 @@ class Plugin::Instance
def full_admin_route
route = self.admin_route
return unless route
if route.blank?
return if !any_settings?
route = default_admin_route
end
route
.slice(:location, :label, :use_new_show_route)
@ -130,6 +135,14 @@ class Plugin::Instance
end
end
def any_settings?
return false if !configurable?
SiteSetting
.all_settings(filter_plugin: self.name)
.any? { |s| s[:setting] != @enabled_site_setting }
end
def configurable?
true
end
@ -146,7 +159,11 @@ class Plugin::Instance
delegate :name, to: :metadata
def humanized_name
(setting_category_name || name).delete_prefix("Discourse ").delete_prefix("discourse-")
(setting_category_name || name)
.delete_prefix("Discourse ")
.delete_prefix("discourse-")
.gsub("-", " ")
.upcase_first
end
def add_to_serializer(
@ -1476,4 +1493,8 @@ class Plugin::Instance
def register_permitted_bulk_action_parameter(name)
DiscoursePluginRegistry.register_permitted_bulk_action_parameter(name, self)
end
def default_admin_route
{ label: "#{name.underscore}.title", location: name, use_new_show_route: true }
end
end

View File

@ -22,6 +22,7 @@ RSpec.describe ApplicationController do
expect(JSON.parse(preloaded_json["visiblePlugins"])).to include(
{
"name" => "chat",
"humanized_name" => "Chat",
"admin_route" => {
"label" => "chat.admin.title",
"location" => "chat",

View File

@ -0,0 +1,4 @@
en:
js:
footnote:
title: "Footnotes"

View File

@ -33,7 +33,7 @@ TEXT
end
it "defaults to using the plugin name with the discourse- prefix removed" do
expect(plugin_instance.humanized_name).to eq("sample-plugin")
expect(plugin_instance.humanized_name).to eq("Sample plugin")
end
it "uses the plugin setting category name if it exists" do
@ -43,7 +43,7 @@ TEXT
it "the plugin name the plugin site settings are still under the generic plugins: category" do
plugin_instance.stubs(:setting_category).returns("plugins")
expect(plugin_instance.humanized_name).to eq("sample-plugin")
expect(plugin_instance.humanized_name).to eq("Sample plugin")
end
it "removes any Discourse prefix from the setting category name" do

View File

@ -337,4 +337,19 @@ describe "Admin | Sidebar Navigation", type: :system do
],
)
end
it "adds auto-generated plugin links for plugins with settings" do
skip if Discourse.plugins.empty?
SiteSetting.enable_markdown_footnotes = true
visit("/admin")
sidebar.toggle_section(:plugins)
expect(page).to have_css(
".sidebar-section-link-content-text",
text: I18n.t("js.footnote.title"),
)
end
end

View File

@ -60,6 +60,10 @@ module PageObjects
def toggle_all_sections
find(".sidebar-toggle-all-sections").click
end
def toggle_section(name)
find("[data-section-name='admin-#{name.to_s.downcase}']").click
end
end
end
end