1
0
Fork 0

Move to individual custom field saving

Dieser Commit ist enthalten in:
Angus McLeod 2020-11-10 11:56:11 +11:00
Ursprung 155eabd377
Commit b383538a6b
14 geänderte Dateien mit 268 neuen und 116 gelöschten Zeilen

Datei anzeigen

@ -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);
});
}
}
});

Datei anzeigen

@ -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);
});

Datei anzeigen

@ -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);

Datei anzeigen

@ -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}}

Datei anzeigen

@ -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}}

Datei anzeigen

@ -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;
}
}
}

Datei anzeigen

@ -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:

Datei anzeigen

@ -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

Datei anzeigen

@ -1,5 +1,5 @@
{
"result": {
"covered_percent": 89.04
"covered_percent": 89.03
}
}

Datei anzeigen

@ -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

Datei anzeigen

@ -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

Datei anzeigen

@ -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)

Datei anzeigen

@ -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

Datei anzeigen

@ -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