Move to individual custom field saving
Dieser Commit ist enthalten in:
Ursprung
155eabd377
Commit
b383538a6b
14 geänderte Dateien mit 268 neuen und 116 gelöschten Zeilen
|
@ -1,6 +1,6 @@
|
|||
import Component from "@ember/component";
|
||||
import discourseComputed, { discourseObserve } from "discourse-common/utils/decorators";
|
||||
import { or } from "@ember/object/computed";
|
||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||
import { or, alias } from "@ember/object/computed";
|
||||
|
||||
const generateContent = function(array, type) {
|
||||
return array.map(key => ({
|
||||
|
@ -18,11 +18,19 @@ export default Component.extend({
|
|||
klassContent: generateContent(['topic', 'post', 'group', 'category'], 'klass'),
|
||||
typeContent: generateContent(['string', 'boolean', 'integer', 'json'], 'type'),
|
||||
showInputs: or('field.new', 'field.edit'),
|
||||
classNames: ['custom-field-input'],
|
||||
loading: or('saving', 'destroying'),
|
||||
destroyDisabled: alias('loading'),
|
||||
closeDisabled: alias('loading'),
|
||||
|
||||
didInsertElement() {
|
||||
this.set('originalField', JSON.parse(JSON.stringify(this.field)));
|
||||
},
|
||||
|
||||
@discourseComputed('field.klass')
|
||||
serializerContent(klass) {
|
||||
serializerContent(klass, p2) {
|
||||
const serializers = this.get(`${klass}Serializers`);
|
||||
|
||||
|
||||
if (serializers) {
|
||||
return generateContent(serializers, 'serializers');
|
||||
} else {
|
||||
|
@ -30,6 +38,44 @@ export default Component.extend({
|
|||
}
|
||||
},
|
||||
|
||||
@observes('field.klass')
|
||||
clearSerializersWhenClassChanges() {
|
||||
this.set('field.serializers', null);
|
||||
},
|
||||
|
||||
compareArrays(array1, array2) {
|
||||
return array1.length === array2.length && array1.every((value, index) => {
|
||||
return value === array2[index];
|
||||
});
|
||||
},
|
||||
|
||||
@discourseComputed(
|
||||
'saving',
|
||||
'field.name',
|
||||
'field.klass',
|
||||
'field.type',
|
||||
'field.serializers'
|
||||
)
|
||||
saveDisabled(saving) {
|
||||
if (saving) return true;
|
||||
|
||||
const originalField = this.originalField;
|
||||
if (!originalField) return false;
|
||||
|
||||
return ['name', 'klass', 'type', 'serializers'].every(attr => {
|
||||
let current = this.get(attr);
|
||||
let original = originalField[attr];
|
||||
|
||||
if (!current) return false;
|
||||
|
||||
if (attr == 'serializers') {
|
||||
return this.compareArrays(current, original);
|
||||
} else {
|
||||
return current == original;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
edit() {
|
||||
this.set('field.edit', true);
|
||||
|
@ -42,8 +88,32 @@ export default Component.extend({
|
|||
},
|
||||
|
||||
destroy() {
|
||||
this.set('removing', true);
|
||||
this.set('destroying', true);
|
||||
this.removeField(this.field);
|
||||
},
|
||||
|
||||
save() {
|
||||
this.set('saving', true);
|
||||
|
||||
const field = this.field;
|
||||
|
||||
let data = {
|
||||
id: field.id,
|
||||
klass: field.klass,
|
||||
type: field.type,
|
||||
serializers: field.serializers,
|
||||
name: field.name
|
||||
}
|
||||
|
||||
this.saveField(data).then((result) => {
|
||||
if (result.success) {
|
||||
this.set('saveIcon', 'check');
|
||||
} else {
|
||||
this.set('saveIcon', 'times');
|
||||
}
|
||||
setTimeout(() => this.set('saveIcon', null), 10000);
|
||||
this.set('saving', false);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
|
@ -3,8 +3,10 @@ import EmberObject from '@ember/object';
|
|||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import CustomWizardCustomField from "../models/custom-wizard-custom-field";
|
||||
import { default as discourseComputed } from 'discourse-common/utils/decorators';
|
||||
|
||||
export default Controller.extend({
|
||||
messageKey: 'create',
|
||||
fieldKeys: ['klass', 'type', 'serializers', 'name'],
|
||||
documentationUrl: "https://thepavilion.io/t/3572",
|
||||
|
||||
|
@ -15,24 +17,36 @@ export default Controller.extend({
|
|||
);
|
||||
},
|
||||
|
||||
saveFields() {
|
||||
this.set('saving', true);
|
||||
CustomWizardCustomField.saveFields(this.customFields)
|
||||
saveField(field) {
|
||||
return CustomWizardCustomField.saveField(field)
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
this.set('saveIcon', 'check');
|
||||
} else {
|
||||
this.set('saveIcon', 'times');
|
||||
this.setProperties({
|
||||
messageKey: 'saved',
|
||||
messageType: 'success'
|
||||
});
|
||||
} else {
|
||||
if (result.messages) {
|
||||
this.setProperties({
|
||||
messageKey: 'error',
|
||||
messageType: 'error',
|
||||
messageOpts: { messages: result.messages }
|
||||
})
|
||||
}
|
||||
}
|
||||
setTimeout(() => this.set('saveIcon', ''), 5000);
|
||||
this.get('customFields').setEach('edit', false);
|
||||
}).finally(() => {
|
||||
this.set('saving', false);
|
||||
|
||||
setTimeout(() => this.setProperties({
|
||||
messageKey: 'create',
|
||||
messageType: null,
|
||||
messageOpts: null
|
||||
}), 10000);
|
||||
|
||||
return result;
|
||||
});
|
||||
},
|
||||
|
||||
removeField(field) {
|
||||
CustomWizardCustomField.removeField(field)
|
||||
return CustomWizardCustomField.destroyField(field)
|
||||
.then(result => {
|
||||
this.get('customFields').removeObject(field);
|
||||
});
|
||||
|
|
|
@ -14,18 +14,16 @@ CustomWizardCustomField.reopenClass({
|
|||
return ajax(basePath).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
saveFields(customFields) {
|
||||
saveField(customField) {
|
||||
return ajax(basePath, {
|
||||
type: 'PUT',
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
custom_fields: customFields
|
||||
})
|
||||
data: {
|
||||
custom_field: customField
|
||||
}
|
||||
}).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
removeField(field) {
|
||||
destroyField(field) {
|
||||
return ajax(`${basePath}/${field.name}`, {
|
||||
type: 'DELETE'
|
||||
}).catch(popupAjaxError);
|
||||
|
|
|
@ -2,16 +2,6 @@
|
|||
<h3>{{i18n 'admin.wizard.custom_field.nav_label'}}</h3>
|
||||
|
||||
<div class="buttons">
|
||||
{{#if saving}}
|
||||
{{loading-spinner size="small"}}
|
||||
{{else}}
|
||||
{{#if saveIcon}}
|
||||
{{d-icon saveIcon}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{d-button
|
||||
label="admin.wizard.custom_field.save"
|
||||
action="saveFields"}}
|
||||
{{d-button
|
||||
label="admin.wizard.custom_field.add"
|
||||
icon="plus"
|
||||
|
@ -20,7 +10,9 @@
|
|||
</div>
|
||||
|
||||
{{wizard-message
|
||||
key='create'
|
||||
key=messageKey
|
||||
opts=messageOpts
|
||||
type=messageType
|
||||
url=documentationUrl
|
||||
component='custom_fields'}}
|
||||
|
||||
|
@ -36,7 +28,8 @@
|
|||
{{#each customFields as |field|}}
|
||||
{{custom-field-input
|
||||
field=field
|
||||
removeField=(action 'removeField')}}
|
||||
removeField=(action 'removeField')
|
||||
saveField=(action 'saveField')}}
|
||||
{{/each}}
|
||||
</table>
|
||||
{{/if}}
|
||||
|
|
|
@ -13,40 +13,51 @@
|
|||
none="admin.wizard.custom_field.type.select"
|
||||
onChange=(action (mut field.type))}}
|
||||
</td>
|
||||
<td>
|
||||
<td class="multi-select">
|
||||
{{multi-select
|
||||
value=field.serializers
|
||||
content=serializerContent
|
||||
none="admin.wizard.custom_field.serializers.select"
|
||||
onChange=(action (mut field.serializers))}}
|
||||
</td>
|
||||
<td>
|
||||
<td class="input">
|
||||
{{input
|
||||
value=field.name
|
||||
placeholder=(i18n "admin.wizard.custom_field.klass.select")}}
|
||||
placeholder=(i18n "admin.wizard.custom_field.name.select")}}
|
||||
</td>
|
||||
<td>
|
||||
{{d-button action="close" icon="times"}}
|
||||
<td class="actions">
|
||||
{{#if loading}}
|
||||
{{loading-spinner size="small"}}
|
||||
{{else}}
|
||||
{{#if saveIcon}}
|
||||
{{d-icon saveIcon}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{d-button
|
||||
action="destroy"
|
||||
icon="trash-alt"
|
||||
class="destroy"
|
||||
disabled=destroyDisabled}}
|
||||
{{d-button
|
||||
icon="save"
|
||||
action="save"
|
||||
disabled=saveDisabled
|
||||
class="save"}}
|
||||
{{d-button
|
||||
action="close"
|
||||
icon="times"
|
||||
disabled=closeDisabled}}
|
||||
</td>
|
||||
{{else}}
|
||||
<td><label>{{field.klass}}</label></td>
|
||||
<td><label>{{field.type}}</label></td>
|
||||
<td>
|
||||
<td class="multi-select">
|
||||
{{#each field.serializers as |serializer|}}
|
||||
<label>{{serializer}}</label>
|
||||
{{/each}}
|
||||
</td>
|
||||
<td><label>{{field.name}}</label></td>
|
||||
<td>
|
||||
<td class="input"><label>{{field.name}}</label></td>
|
||||
<td class="actions">
|
||||
{{d-button action="edit" icon="pencil-alt"}}
|
||||
{{#if field.edit}}
|
||||
{{d-button action="close" icon="times"}}
|
||||
{{else}}
|
||||
{{#if destroying}}
|
||||
{{loading-spinner size="small"}}
|
||||
{{else}}
|
||||
{{d-button action="destroy" icon="trash-alt"}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</td>
|
||||
{{/if}}
|
|
@ -1,6 +1,7 @@
|
|||
@import 'wizard-mapper';
|
||||
@import 'wizard-manager';
|
||||
@import 'wizard-api';
|
||||
@import 'common/components/buttons';
|
||||
|
||||
.admin-wizard-controls {
|
||||
display: flex;
|
||||
|
@ -565,16 +566,35 @@
|
|||
}
|
||||
|
||||
.admin-wizards-custom-fields {
|
||||
.admin-wizard-controls {
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
button.btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn.save:enabled {
|
||||
@extend .btn-primary;
|
||||
}
|
||||
|
||||
.btn.destroy {
|
||||
@extend .btn-danger;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.select-kit {
|
||||
width: 200px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.select-kit.multi-select {
|
||||
width: 200px;
|
||||
width: 300px;
|
||||
|
||||
.choices .choice,
|
||||
.select-kit-filter .filter-input {
|
||||
|
@ -587,20 +607,39 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
td {
|
||||
vertical-align: top;
|
||||
table {
|
||||
td {
|
||||
vertical-align: top;
|
||||
|
||||
label {
|
||||
margin: 0;
|
||||
line-height: 30px;
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
margin: 0;
|
||||
line-height: 30px;
|
||||
td {
|
||||
min-width: 170px;
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
td.multi-select {
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
td.input {
|
||||
min-width: 210px;
|
||||
width: 210px;
|
||||
}
|
||||
|
||||
td.actions {
|
||||
min-width: 100px;
|
||||
text-align: right;
|
||||
|
||||
button.btn {
|
||||
margin-left: 5px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td:not(:last-of-type) {
|
||||
min-width: 230px;
|
||||
}
|
||||
|
||||
td:last-of-type {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -73,7 +73,9 @@ en:
|
|||
edit: "You're editing an action"
|
||||
documentation: "Check out the action documentation"
|
||||
custom_fields:
|
||||
create: "Create or edit a custom field record"
|
||||
create: "Create, edit or destroy a custom field record"
|
||||
saved: "Saved custom field"
|
||||
error: "Failed to save: {{messages}}"
|
||||
documentation: Check out the custom field documentation
|
||||
manager:
|
||||
info: "Export, import or destroy wizards"
|
||||
|
@ -296,11 +298,10 @@ en:
|
|||
|
||||
custom_field:
|
||||
nav_label: "Custom Fields"
|
||||
add: "Add Custom Field"
|
||||
save: "Save Custom Fields"
|
||||
add: "Add"
|
||||
name:
|
||||
label: "Name"
|
||||
select: "Enter a name"
|
||||
select: "underscored_name"
|
||||
type:
|
||||
label: "Type"
|
||||
select: "Select a type"
|
||||
|
@ -322,6 +323,7 @@ en:
|
|||
topic_view: "Topic View"
|
||||
topic_list_item: "Topic List Item"
|
||||
basic_category: "Category"
|
||||
basic_group: "Group"
|
||||
post: "Post"
|
||||
|
||||
submissions:
|
||||
|
|
|
@ -4,39 +4,38 @@ class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController
|
|||
end
|
||||
|
||||
def update
|
||||
fields_to_save = []
|
||||
|
||||
custom_field_params[:custom_fields].each do |field_param|
|
||||
field_id = nil
|
||||
field_data = {}
|
||||
|
||||
if saved_field = CustomWizard::CustomField.find(field_param[:name])
|
||||
CustomWizard::CustomField::ATTRS.each do |attr|
|
||||
field_data[attr] = saved_field.send(attr)
|
||||
end
|
||||
field_id = saved_field.id
|
||||
end
|
||||
|
||||
errors = []
|
||||
field_id = nil
|
||||
field_data = {}
|
||||
|
||||
if saved_field = CustomWizard::CustomField.find(field_params[:id].to_i)
|
||||
CustomWizard::CustomField::ATTRS.each do |attr|
|
||||
field_data[attr] = field_param[attr]
|
||||
field_data[attr] = saved_field.send(attr)
|
||||
end
|
||||
|
||||
field = CustomWizard::CustomField.new(field_id, field_data)
|
||||
fields_to_save.push(field)
|
||||
field_id = saved_field.id
|
||||
end
|
||||
|
||||
CustomWizard::CustomField::ATTRS.each do |attr|
|
||||
field_data[attr] = field_params[attr]
|
||||
end
|
||||
|
||||
field = CustomWizard::CustomField.new(field_id, field_data)
|
||||
|
||||
PluginStoreRow.transaction do
|
||||
fields_to_save.each do |field|
|
||||
unless field.save
|
||||
raise ActiveRecord::Rollback.new,
|
||||
field.errors.any? ?
|
||||
field.errors.full_messages.join("\n\n") :
|
||||
I18n.t("wizard.custom_field.error.save_default", name: field.name)
|
||||
end
|
||||
unless field.save
|
||||
field_errors = field.errors.any? ?
|
||||
field.errors.full_messages.join("\n\n") :
|
||||
I18n.t("wizard.custom_field.error.save_default", name: field.name)
|
||||
errors << field_errors
|
||||
raise ActiveRecord::Rollback.new
|
||||
end
|
||||
end
|
||||
|
||||
render json: success_json
|
||||
|
||||
if errors.any?
|
||||
render json: failed_json.merge(messages: errors)
|
||||
else
|
||||
render json: success_json
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
@ -51,14 +50,14 @@ class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController
|
|||
|
||||
private
|
||||
|
||||
def custom_field_params
|
||||
params.permit(
|
||||
custom_fields: [
|
||||
def field_params
|
||||
params.required(:custom_field)
|
||||
.permit(
|
||||
:id,
|
||||
:name,
|
||||
:klass,
|
||||
:type,
|
||||
serializers: []
|
||||
]
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"result": {
|
||||
"covered_percent": 89.04
|
||||
"covered_percent": 89.03
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ class ::CustomWizard::CustomField
|
|||
attr_reader :id
|
||||
|
||||
ATTRS ||= ["name", "klass", "type", "serializers"]
|
||||
REQUIRED ||= ["name", "klass"]
|
||||
REQUIRED ||= ["name", "klass", "type"]
|
||||
NAMESPACE ||= "custom_wizard_custom_fields"
|
||||
NAME_MIN_LENGTH ||= 3
|
||||
|
||||
|
@ -143,11 +143,21 @@ class ::CustomWizard::CustomField
|
|||
PluginStoreRow.where(plugin_name: NAMESPACE, key: name).exists?
|
||||
end
|
||||
|
||||
def self.find(name)
|
||||
records = PluginStoreRow.where(plugin_name: NAMESPACE, key: name)
|
||||
def self.find(field_id)
|
||||
record = PluginStoreRow.find_by(id: field_id, plugin_name: NAMESPACE)
|
||||
|
||||
if records.exists?
|
||||
create_from_store(records.first)
|
||||
if record
|
||||
create_from_store(record)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def self.find_by_name(name)
|
||||
record = PluginStoreRow.find_by(key: name, plugin_name: NAMESPACE)
|
||||
|
||||
if record
|
||||
create_from_store(record)
|
||||
else
|
||||
false
|
||||
end
|
||||
|
@ -161,8 +171,9 @@ class ::CustomWizard::CustomField
|
|||
|
||||
def self.save_to_store(id = nil, key, data)
|
||||
if id
|
||||
record = PluginStoreRow.find_by(id: id, plugin_name: NAMESPACE, key: key)
|
||||
record = PluginStoreRow.find_by(id: id, plugin_name: NAMESPACE)
|
||||
return false if !record
|
||||
record.key = key
|
||||
record.value = data.to_json
|
||||
record.save
|
||||
else
|
||||
|
|
|
@ -31,6 +31,7 @@ if respond_to?(:register_svg_icon)
|
|||
register_svg_icon "far-calendar"
|
||||
register_svg_icon "chevron-right"
|
||||
register_svg_icon "chevron-left"
|
||||
register_svg_icon "save"
|
||||
end
|
||||
|
||||
after_initialize do
|
||||
|
|
|
@ -31,7 +31,7 @@ describe CustomWizard::CustomField do
|
|||
|
||||
updated_field_json = custom_field_json['custom_fields'][0]
|
||||
updated_field_json['serializers'] = ["topic_view"]
|
||||
existing_field = CustomWizard::CustomField.find(updated_field_json["name"])
|
||||
existing_field = CustomWizard::CustomField.find_by_name(updated_field_json["name"])
|
||||
updated_field = CustomWizard::CustomField.new(existing_field.id, updated_field_json)
|
||||
|
||||
expect(updated_field.save).to eq(true)
|
||||
|
|
|
@ -21,12 +21,26 @@ describe CustomWizard::AdminCustomFieldsController do
|
|||
expect(response.parsed_body.length).to eq(4)
|
||||
end
|
||||
|
||||
it "updates the list of custom fields" do
|
||||
custom_field_json['custom_fields'][0]['type'] = 'string'
|
||||
put "/admin/wizards/custom-fields.json", params: custom_field_json
|
||||
it "saves custom fields" do
|
||||
topic_field = CustomWizard::CustomField.find_by_name('topic_field_1')
|
||||
topic_field_json = topic_field.as_json
|
||||
topic_field_json['type'] = 'string'
|
||||
|
||||
put "/admin/wizards/custom-fields.json", params: {
|
||||
custom_field: topic_field_json
|
||||
}
|
||||
expect(response.status).to eq(200)
|
||||
expect(
|
||||
CustomWizard::CustomField.find('topic_field_1').type
|
||||
CustomWizard::CustomField.find_by_name('topic_field_1').type
|
||||
).to eq('string')
|
||||
end
|
||||
|
||||
it "destroys custom fields" do
|
||||
topic_field = custom_field_json['custom_fields'][0]
|
||||
delete "/admin/wizards/custom-fields/#{topic_field["name"]}.json"
|
||||
expect(response.status).to eq(200)
|
||||
expect(
|
||||
CustomWizard::CustomField.exists?('topic_field_1')
|
||||
).to eq(false)
|
||||
end
|
||||
end
|
|
@ -17,7 +17,7 @@ describe CustomWizard::CustomFieldSerializer do
|
|||
end
|
||||
|
||||
json = CustomWizard::CustomFieldSerializer.new(
|
||||
CustomWizard::CustomField.find("topic_field_1"),
|
||||
CustomWizard::CustomField.find_by_name("topic_field_1"),
|
||||
scope: Guardian.new(user),
|
||||
root: false
|
||||
).as_json
|
||||
|
|
Laden …
In neuem Issue referenzieren