DEV: support json_schema in theme settings (#12294)
This commit is contained in:
parent
9fb9a2c098
commit
10780d2448
|
@ -1,6 +1,7 @@
|
|||
import { action } from "@ember/object";
|
||||
import Component from "@ember/component";
|
||||
import { create } from "virtual-dom";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { iconNode } from "discourse-common/lib/icon-library";
|
||||
import loadScript from "discourse/lib/load-script";
|
||||
import { schedule } from "@ember/runloop";
|
||||
|
@ -32,12 +33,18 @@ export default Component.extend({
|
|||
disable_edit_json: true,
|
||||
disable_properties: true,
|
||||
disable_collapse: true,
|
||||
show_errors: "always",
|
||||
startval: this.model.value ? JSON.parse(this.model.value) : null,
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@discourseComputed("model.settingName")
|
||||
settingName(name) {
|
||||
return name.replace(/\_/g, " ");
|
||||
},
|
||||
|
||||
@action
|
||||
saveChanges() {
|
||||
const fieldValue = JSON.stringify(this.editor.getValue());
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{{#d-modal-body rawTitle=(i18n "admin.site_settings.json_schema.modal_title" name=model.settingName)}}
|
||||
{{#d-modal-body rawTitle=(i18n "admin.site_settings.json_schema.modal_title" name=settingName)}}
|
||||
|
||||
<div id="json-editor-holder"></div>
|
||||
{{/d-modal-body}}
|
||||
|
||||
|
|
|
@ -821,6 +821,28 @@
|
|||
display: block !important;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.table {
|
||||
td {
|
||||
vertical-align: top;
|
||||
padding: 1em 0;
|
||||
}
|
||||
td.compact {
|
||||
.invalid-feedback {
|
||||
margin: 0;
|
||||
font-size: $font-down-1;
|
||||
color: var(--danger);
|
||||
}
|
||||
}
|
||||
input[type="text"] {
|
||||
margin-bottom: 0;
|
||||
width: 95%;
|
||||
&.is-invalid {
|
||||
border-color: var(--danger);
|
||||
outline: 1px solid var(--danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.create-invite-modal {
|
||||
|
|
|
@ -177,6 +177,8 @@ class RemoteTheme < ActiveRecord::Base
|
|||
updated_fields << theme.set_field(**opts.merge(value: value))
|
||||
end
|
||||
|
||||
theme.convert_settings
|
||||
|
||||
# Destroy fields that no longer exist in the remote theme
|
||||
field_ids_to_destroy = theme.theme_fields.pluck(:id) - updated_fields.map { |tf| tf&.id }
|
||||
ThemeField.where(id: field_ids_to_destroy).destroy_all
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_dependency 'global_path'
|
||||
require 'csv'
|
||||
require 'json_schemer'
|
||||
|
||||
class Theme < ActiveRecord::Base
|
||||
include GlobalPath
|
||||
|
@ -622,6 +624,44 @@ class Theme < ActiveRecord::Base
|
|||
list_baked_fields(target, name).count > 0
|
||||
end
|
||||
|
||||
def convert_settings
|
||||
settings.each do |setting|
|
||||
setting_row = ThemeSetting.where(theme_id: self.id, name: setting.name.to_s).first
|
||||
|
||||
if setting_row && setting_row.data_type != setting.type
|
||||
if (setting_row.data_type == ThemeSetting.types[:list] &&
|
||||
setting.type == ThemeSetting.types[:string] &&
|
||||
setting.json_schema.present?)
|
||||
convert_list_to_json_schema(setting_row, setting)
|
||||
else
|
||||
Rails.logger.warn("Theme setting type has changed but cannot be converted. \n\n #{setting.inspect}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def convert_list_to_json_schema(setting_row, setting)
|
||||
schema = setting.json_schema
|
||||
return if !schema
|
||||
keys = schema["items"]["properties"].keys
|
||||
return if !keys
|
||||
|
||||
current_values = CSV.parse(setting_row.value, { col_sep: '|' }).flatten
|
||||
new_values = []
|
||||
current_values.each do |item|
|
||||
parts = CSV.parse(item, { col_sep: ',' }).flatten
|
||||
props = parts.map.with_index { |p, idx| [keys[idx], p] }.to_h
|
||||
new_values << props
|
||||
end
|
||||
|
||||
schemer = JSONSchemer.schema(schema)
|
||||
raise "Schema validation failed" if !schemer.valid?(new_values)
|
||||
|
||||
setting_row.value = new_values.to_json
|
||||
setting_row.data_type = setting.type
|
||||
setting_row.save!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def to_scss_variable(name, value)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
class ThemeSettingsSerializer < ApplicationSerializer
|
||||
attributes :setting, :type, :default, :value, :description, :valid_values,
|
||||
:list_type, :textarea
|
||||
:list_type, :textarea, :json_schema
|
||||
|
||||
def setting
|
||||
object.name
|
||||
|
@ -53,4 +53,11 @@ class ThemeSettingsSerializer < ApplicationSerializer
|
|||
object.type == ThemeSetting.types[:string]
|
||||
end
|
||||
|
||||
def json_schema
|
||||
object.json_schema
|
||||
end
|
||||
|
||||
def include_json_schema?
|
||||
object.type == ThemeSetting.types[:string] && object.json_schema.present?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5052,7 +5052,7 @@ en:
|
|||
simple_list:
|
||||
add_item: "Add item..."
|
||||
json_schema:
|
||||
edit: Launch JSON Editor
|
||||
edit: Launch Editor
|
||||
modal_title: "Edit %{name}"
|
||||
|
||||
badges:
|
||||
|
|
|
@ -111,6 +111,10 @@ class ThemeSettingsManager
|
|||
def textarea
|
||||
@opts[:textarea]
|
||||
end
|
||||
|
||||
def json_schema
|
||||
JSON.parse(@opts[:json_schema]) rescue false
|
||||
end
|
||||
end
|
||||
|
||||
class Bool < self
|
||||
|
|
|
@ -41,6 +41,7 @@ class ThemeSettingsParser
|
|||
end
|
||||
|
||||
opts[:textarea] = !!raw_opts[:textarea]
|
||||
opts[:json_schema] = raw_opts[:json_schema]
|
||||
|
||||
opts
|
||||
end
|
||||
|
|
|
@ -132,10 +132,15 @@ describe ThemeSettingsManager do
|
|||
end
|
||||
|
||||
it "can be a textarea" do
|
||||
string_setting = find_by_name(:string_setting_02)
|
||||
expect(find_by_name(:string_setting_02).textarea).to eq(false)
|
||||
expect(find_by_name(:string_setting_03).textarea).to eq(true)
|
||||
end
|
||||
|
||||
it "supports json schema" do
|
||||
expect(find_by_name(:string_setting_03).json_schema).to eq(false)
|
||||
expect(find_by_name(:invalid_json_schema_setting).json_schema).to eq(false)
|
||||
expect(find_by_name(:valid_json_schema_setting).json_schema).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context "List" do
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
test_setting:
|
||||
default: ""
|
||||
description: "A JSON schema field type"
|
||||
json_schema: '{ "type": "array", "uniqueItems": true, "items": { "type": "object", "properties": { "color": { "type": "string" }, "icon": { "type": "string" } }, "additionalProperties": false } }'
|
|
@ -72,3 +72,11 @@ enum_setting_03:
|
|||
upload_setting:
|
||||
type: upload
|
||||
default: ""
|
||||
|
||||
invalid_json_schema_setting:
|
||||
default: ""
|
||||
json_schema: '{ "type": "array", "invalid json"'
|
||||
|
||||
valid_json_schema_setting:
|
||||
default: ""
|
||||
json_schema: '{ "type": "array", "uniqueItems": true, "items": { "type": "object", "properties": { "color": { "type": "string" }, "icon": { "type": "string" } }, "additionalProperties": false } }'
|
||||
|
|
|
@ -608,6 +608,72 @@ HTML
|
|||
expect(json).to match(/\"boolean_setting\":false/)
|
||||
end
|
||||
|
||||
describe "convert_settings" do
|
||||
|
||||
it 'can migrate a list field to a string field with json schema' do
|
||||
theme.set_field(target: :settings, name: :yaml, value: "valid_json_schema_setting:\n default: \"green,globe\"\n type: \"list\"")
|
||||
theme.save!
|
||||
|
||||
setting = theme.settings.find { |s| s.name == :valid_json_schema_setting }
|
||||
setting.value = "red,globe|green,cog|brown,users"
|
||||
theme.save!
|
||||
|
||||
expect(setting.type).to eq(ThemeSetting.types[:list])
|
||||
|
||||
yaml = File.read("#{Rails.root}/spec/fixtures/theme_settings/valid_settings.yaml")
|
||||
theme.set_field(target: :settings, name: "yaml", value: yaml)
|
||||
theme.save!
|
||||
|
||||
theme.convert_settings
|
||||
setting = theme.settings.find { |s| s.name == :valid_json_schema_setting }
|
||||
|
||||
expect(JSON.parse(setting.value)).to eq(JSON.parse('[{"color":"red","icon":"globe"},{"color":"green","icon":"cog"},{"color":"brown","icon":"users"}]'))
|
||||
expect(setting.type).to eq(ThemeSetting.types[:string])
|
||||
end
|
||||
|
||||
it 'does not update setting if data does not validate against json schema' do
|
||||
theme.set_field(target: :settings, name: :yaml, value: "valid_json_schema_setting:\n default: \"green,globe\"\n type: \"list\"")
|
||||
theme.save!
|
||||
|
||||
setting = theme.settings.find { |s| s.name == :valid_json_schema_setting }
|
||||
|
||||
# json_schema_settings.yaml defines only two properties per object and disallows additionalProperties
|
||||
setting.value = "red,globe,hey|green,cog,hey|brown,users,nay"
|
||||
theme.save!
|
||||
|
||||
yaml = File.read("#{Rails.root}/spec/fixtures/theme_settings/valid_settings.yaml")
|
||||
theme.set_field(target: :settings, name: "yaml", value: yaml)
|
||||
theme.save!
|
||||
|
||||
expect { theme.convert_settings }.to raise_error("Schema validation failed")
|
||||
|
||||
setting.value = "red,globe|green,cog|brown"
|
||||
theme.save!
|
||||
|
||||
expect { theme.convert_settings }.not_to raise_error
|
||||
|
||||
setting = theme.settings.find { |s| s.name == :valid_json_schema_setting }
|
||||
expect(setting.type).to eq(ThemeSetting.types[:string])
|
||||
end
|
||||
|
||||
it 'warns when the theme has modified the setting type but data cannot be converted' do
|
||||
Rails.logger = FakeLogger.new
|
||||
theme.set_field(target: :settings, name: :yaml, value: "valid_json_schema_setting:\n default: \"\"\n type: \"list\"")
|
||||
theme.save!
|
||||
|
||||
setting = theme.settings.find { |s| s.name == :valid_json_schema_setting }
|
||||
setting.value = "red,globe"
|
||||
theme.save!
|
||||
|
||||
theme.set_field(target: :settings, name: :yaml, value: "valid_json_schema_setting:\n default: \"\"\n type: \"string\"")
|
||||
theme.save!
|
||||
|
||||
theme.convert_settings
|
||||
expect(setting.value).to eq("red,globe")
|
||||
expect(Rails.logger.warnings[0]).to include("Theme setting type has changed but cannot be converted.")
|
||||
end
|
||||
end
|
||||
|
||||
describe "theme translations" do
|
||||
it "can list working theme_translation_manager objects" do
|
||||
en_translation = ThemeField.create!(theme_id: theme.id, name: "en", type_id: ThemeField.types[:yaml], target_id: Theme.targets[:translations], value: <<~YAML)
|
||||
|
|
Loading…
Reference in New Issue