DEV: Load theme objects typed setting metadata when routing to editor (#26354)

Why this change?

Previously, we were preloading the necessary metadata for
`adminCustomizeThemes.show.schema` route in the
`adminCustomizeThemes.show` route. This is wasteful because we're
loading data upfront when the objects setting editor may not be used.

This change also lays the ground work for a future commit where we need
to be shipping down additional metadata which may further add to the
payload.
This commit is contained in:
Alan Guo Xiang Tan 2024-03-26 14:02:05 +08:00 committed by GitHub
parent fbde7f8bc1
commit ef99b97ea7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 186 additions and 68 deletions

View File

@ -243,7 +243,7 @@ export default class SchemaThemeSettingEditor extends Component {
} }
fieldDescription(fieldName) { fieldDescription(fieldName) {
const descriptions = this.args.setting.objects_schema_property_descriptions; const descriptions = this.args.setting.metadata?.property_descriptions;
if (!descriptions) { if (!descriptions) {
return; return;

View File

@ -1,5 +1,6 @@
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import Setting from "admin/mixins/setting-object"; import Setting from "admin/mixins/setting-object";
export default class ThemeSettings extends EmberObject.extend(Setting) { export default class ThemeSettings extends EmberObject.extend(Setting) {
@ -16,4 +17,12 @@ export default class ThemeSettings extends EmberObject.extend(Setting) {
}, },
}); });
} }
loadMetadata(themeId) {
return ajax(
`/admin/themes/${themeId}/objects_setting_metadata/${this.setting}.json`
)
.then((result) => this.set("metadata", result))
.catch(popupAjaxError);
}
} }

View File

@ -5,10 +5,12 @@ export default class AdminCustomizeThemesShowSchemaRoute extends Route {
const theme = this.modelFor("adminCustomizeThemesShow"); const theme = this.modelFor("adminCustomizeThemesShow");
const setting = theme.settings.findBy("setting", params.setting_name); const setting = theme.settings.findBy("setting", params.setting_name);
return setting.loadMetadata(theme.id).then(() => {
return { return {
theme, theme,
setting, setting,
}; };
});
} }
setupController() { setupController() {

View File

@ -173,10 +173,9 @@ class Admin::ThemesController < Admin::AdminController
@color_schemes = ColorScheme.all.includes(:theme, color_scheme_colors: :color_scheme).to_a @color_schemes = ColorScheme.all.includes(:theme, color_scheme_colors: :color_scheme).to_a
payload = { payload = {
themes: ActiveModel::ArraySerializer.new(@themes, each_serializer: ThemeSerializer), themes: serialize_data(@themes, ThemeSerializer),
extras: { extras: {
color_schemes: color_schemes: serialize_data(@color_schemes, ColorSchemeSerializer),
ActiveModel::ArraySerializer.new(@color_schemes, each_serializer: ColorSchemeSerializer),
}, },
} }
@ -293,7 +292,7 @@ class Admin::ThemesController < Admin::AdminController
@theme = Theme.include_relations.find_by(id: params[:id]) @theme = Theme.include_relations.find_by(id: params[:id])
raise Discourse::InvalidParameters.new(:id) unless @theme raise Discourse::InvalidParameters.new(:id) unless @theme
render json: ThemeSerializer.new(@theme) render_serialized(@theme, ThemeSerializer)
end end
def export def export
@ -352,6 +351,18 @@ class Admin::ThemesController < Admin::AdminController
raise Discourse::InvalidAccess if !SiteSetting.experimental_objects_type_for_theme_settings raise Discourse::InvalidAccess if !SiteSetting.experimental_objects_type_for_theme_settings
end end
def objects_setting_metadata
raise Discourse::InvalidAccess if !SiteSetting.experimental_objects_type_for_theme_settings
theme = Theme.find_by(id: params[:id])
raise Discourse::InvalidParameters.new(:id) unless theme
theme_setting = theme.settings[params[:setting_name].to_sym]
raise Discourse::InvalidParameters.new(:setting_name) unless theme_setting
render_serialized(theme_setting, ThemeObjectsSettingMetadataSerializer, root: false)
end
private private
def ban_in_allowlist_mode! def ban_in_allowlist_mode!

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
class ThemeObjectsSettingMetadataSerializer < ApplicationSerializer
attributes :property_descriptions
def property_descriptions
locales = {}
key = "theme_metadata.settings.#{object.name}.schema.properties."
object.theme.internal_translations.each do |internal_translation|
if internal_translation.key.start_with?(key)
locales[internal_translation.key.delete_prefix(key)] = internal_translation.value
end
end
locales
end
end

View File

@ -56,7 +56,9 @@ class ThemeSerializer < BasicThemeSerializer
end end
def settings def settings
object.settings.map { |_name, setting| ThemeSettingsSerializer.new(setting, root: false) } object.settings.map do |_name, setting|
ThemeSettingsSerializer.new(setting, scope:, root: false)
end
rescue ThemeSettingsParser::InvalidYaml => e rescue ThemeSettingsParser::InvalidYaml => e
@errors << e.message @errors << e.message
nil nil

View File

@ -6,7 +6,6 @@ class ThemeSettingsSerializer < ApplicationSerializer
:default, :default,
:value, :value,
:description, :description,
:objects_schema_property_descriptions,
:valid_values, :valid_values,
:list_type, :list_type,
:textarea, :textarea,
@ -38,23 +37,6 @@ class ThemeSettingsSerializer < ApplicationSerializer
locale_file_description || object.description locale_file_description || object.description
end end
def objects_schema_property_descriptions
locales = {}
key = "theme_metadata.settings.#{setting}.schema.properties."
object.theme.internal_translations.each do |internal_translation|
if internal_translation.key.start_with?(key)
locales[internal_translation.key.delete_prefix(key)] = internal_translation.value
end
end
locales
end
def include_objects_schema_property_descriptions?
include_objects_schema?
end
def valid_values def valid_values
object.choices object.choices
end end

View File

@ -235,7 +235,9 @@ Discourse::Application.routes.draw do
get "preview" => "themes#preview" get "preview" => "themes#preview"
get "translations/:locale" => "themes#get_translations" get "translations/:locale" => "themes#get_translations"
put "setting" => "themes#update_single_setting" put "setting" => "themes#update_single_setting"
get "objects_setting_metadata/:setting_name" => "themes#objects_setting_metadata"
end end
collection do collection do
post "import" => "themes#import" post "import" => "themes#import"
post "upload_asset" => "themes#upload_asset" post "upload_asset" => "themes#upload_asset"

View File

@ -1313,4 +1313,83 @@ RSpec.describe Admin::ThemesController do
delete "/admin/themes/bulk_destroy.json", params: { theme_ids: theme_ids } delete "/admin/themes/bulk_destroy.json", params: { theme_ids: theme_ids }
end end
end end
describe "#objects_setting_metadata" do
fab!(:theme)
let(:theme_setting) do
yaml = File.read("#{Rails.root}/spec/fixtures/theme_settings/objects_settings.yaml")
field = theme.set_field(target: :settings, name: "yaml", value: yaml)
theme.save!
theme.settings
end
before { SiteSetting.experimental_objects_type_for_theme_settings = true }
it "returns 404 if user is not an admin" do
get "/admin/themes/#{theme.id}/objects_setting_metadata/objects_with_categories.json"
expect(response.status).to eq(404)
sign_in(user)
get "/admin/themes/#{theme.id}/objects_setting_metadata/objects_with_categories.json"
expect(response.status).to eq(404)
sign_in(moderator)
get "/admin/themes/#{theme.id}/objects_setting_metadata/objects_with_categories.json"
expect(response.status).to eq(404)
end
context "when user is an admin" do
before { sign_in(admin) }
it "returns 403 if `experimental_objects_type_for_theme_settings` site setting is not enabled" do
SiteSetting.experimental_objects_type_for_theme_settings = false
get "/admin/themes/#{theme.id}/objects_setting_metadata/objects_with_categories.json"
expect(response.status).to eq(403)
end
it "returns 400 if the `id` param is not the id of a valid theme" do
get "/admin/themes/some_invalid_id/objects_setting_metadata/objects_with_categories.json"
expect(response.status).to eq(400)
end
it "returns 400 if the `setting_name` param does not match a valid setting" do
get "/admin/themes/#{theme.id}/objects_setting_metadata/some_invalid_setting_name.json"
expect(response.status).to eq(400)
end
it "returns 200 with the right `property_descriptions` attributes" do
theme.set_field(
target: :translations,
name: "en",
value: File.read("#{Rails.root}/spec/fixtures/theme_locales/objects_settings/en.yaml"),
)
theme.save!
theme_setting
get "/admin/themes/#{theme.id}/objects_setting_metadata/objects_setting.json"
expect(response.status).to eq(200)
expect(response.parsed_body["property_descriptions"]).to eq(
{
"links.name" => "Name of the link",
"links.url" => "URL of the link",
"name" => "Section Name",
},
)
end
end
end
end end

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
RSpec.describe ThemeObjectsSettingMetadataSerializer do
fab!(:theme)
let(:theme_setting) do
yaml = File.read("#{Rails.root}/spec/fixtures/theme_settings/objects_settings.yaml")
theme.set_field(target: :settings, name: "yaml", value: yaml)
theme.save!
theme.settings
end
before { SiteSetting.experimental_objects_type_for_theme_settings = true }
describe "#property_descriptions" do
let(:objects_setting_locale) do
theme.set_field(
target: :translations,
name: "en",
value: File.read("#{Rails.root}/spec/fixtures/theme_locales/objects_settings/en.yaml"),
)
theme.save!
end
it "should return a hash of the settings property descriptions" do
objects_setting_locale
payload = described_class.new(theme_setting[:objects_setting], root: false).as_json
expect(payload[:property_descriptions]).to eq(
{
"links.name" => "Name of the link",
"links.url" => "URL of the link",
"name" => "Section Name",
},
)
end
end
end

View File

@ -19,43 +19,4 @@ RSpec.describe ThemeSettingsSerializer do
expect(payload[:theme_settings][:objects_schema][:name]).to eq("section") expect(payload[:theme_settings][:objects_schema][:name]).to eq("section")
end end
end end
describe "#objects_schema_property_descriptions" do
let(:objects_setting_locale) do
theme.set_field(
target: :translations,
name: "en",
value: File.read("#{Rails.root}/spec/fixtures/theme_locales/objects_settings/en.yaml"),
)
theme.save!
end
before { SiteSetting.experimental_objects_type_for_theme_settings = true }
it "should not include the attribute when theme setting is not typed objects" do
yaml = File.read("#{Rails.root}/spec/fixtures/theme_settings/valid_settings.yaml")
theme.set_field(target: :settings, name: "yaml", value: yaml)
theme.save!
payload = ThemeSettingsSerializer.new(theme.settings[:string_setting]).as_json
expect(payload[:theme_settings][:objects_schema_property_descriptions]).to be_nil
end
it "should include the attribute when theme setting is of typed objects" do
objects_setting_locale
objects_setting
payload = ThemeSettingsSerializer.new(objects_setting).as_json
expect(payload[:theme_settings][:objects_schema_property_descriptions]).to eq(
{
"links.name" => "Name of the link",
"links.url" => "URL of the link",
"name" => "Section Name",
},
)
end
end
end end

View File

@ -78,12 +78,19 @@ RSpec.describe "Admin editing objects type theme setting", type: :system do
expect(admin_customize_themes_page).to have_overridden_setting("objects_setting") expect(admin_customize_themes_page).to have_overridden_setting("objects_setting")
admin_objects_theme_setting_editor =
admin_customize_themes_page.click_edit_objects_theme_setting_button("objects_setting")
expect(admin_objects_theme_setting_editor).to have_setting_field("name", "some new name")
admin_objects_theme_setting_editor.back
admin_customize_themes_page.reset_overridden_setting("objects_setting") admin_customize_themes_page.reset_overridden_setting("objects_setting")
admin_objects_theme_setting_editor = admin_objects_theme_setting_editor =
admin_customize_themes_page.click_edit_objects_theme_setting_button("objects_setting") admin_customize_themes_page.click_edit_objects_theme_setting_button("objects_setting")
expect(admin_objects_theme_setting_editor).to have_setting_field("name", "some new name") expect(admin_objects_theme_setting_editor).to have_setting_field("name", "section 1")
end end
it "allows an admin to edit a theme setting of objects type via the settings editor" do it "allows an admin to edit a theme setting of objects type via the settings editor" do

View File

@ -35,6 +35,11 @@ module PageObjects
self self
end end
def back
find(".customize-themes-show-schema__back").click
self
end
private private
def input_field(field_name) def input_field(field_name)