0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-09-19 15:21:11 +02:00

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 Component from "@ember/component";
import discourseComputed, { discourseObserve } from "discourse-common/utils/decorators"; import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { or } from "@ember/object/computed"; import { or, alias } from "@ember/object/computed";
const generateContent = function(array, type) { const generateContent = function(array, type) {
return array.map(key => ({ return array.map(key => ({
@ -18,11 +18,19 @@ export default Component.extend({
klassContent: generateContent(['topic', 'post', 'group', 'category'], 'klass'), klassContent: generateContent(['topic', 'post', 'group', 'category'], 'klass'),
typeContent: generateContent(['string', 'boolean', 'integer', 'json'], 'type'), typeContent: generateContent(['string', 'boolean', 'integer', 'json'], 'type'),
showInputs: or('field.new', 'field.edit'), 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') @discourseComputed('field.klass')
serializerContent(klass) { serializerContent(klass, p2) {
const serializers = this.get(`${klass}Serializers`); const serializers = this.get(`${klass}Serializers`);
if (serializers) { if (serializers) {
return generateContent(serializers, 'serializers'); return generateContent(serializers, 'serializers');
} else { } 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: { actions: {
edit() { edit() {
this.set('field.edit', true); this.set('field.edit', true);
@ -42,8 +88,32 @@ export default Component.extend({
}, },
destroy() { destroy() {
this.set('removing', true); this.set('destroying', true);
this.removeField(this.field); 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 { ajax } from 'discourse/lib/ajax';
import { popupAjaxError } from 'discourse/lib/ajax-error'; import { popupAjaxError } from 'discourse/lib/ajax-error';
import CustomWizardCustomField from "../models/custom-wizard-custom-field"; import CustomWizardCustomField from "../models/custom-wizard-custom-field";
import { default as discourseComputed } from 'discourse-common/utils/decorators';
export default Controller.extend({ export default Controller.extend({
messageKey: 'create',
fieldKeys: ['klass', 'type', 'serializers', 'name'], fieldKeys: ['klass', 'type', 'serializers', 'name'],
documentationUrl: "https://thepavilion.io/t/3572", documentationUrl: "https://thepavilion.io/t/3572",
@ -15,24 +17,36 @@ export default Controller.extend({
); );
}, },
saveFields() { saveField(field) {
this.set('saving', true); return CustomWizardCustomField.saveField(field)
CustomWizardCustomField.saveFields(this.customFields)
.then(result => { .then(result => {
if (result.success) { if (result.success) {
this.set('saveIcon', 'check'); this.setProperties({
} else { messageKey: 'saved',
this.set('saveIcon', 'times'); 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); setTimeout(() => this.setProperties({
}).finally(() => { messageKey: 'create',
this.set('saving', false); messageType: null,
messageOpts: null
}), 10000);
return result;
}); });
}, },
removeField(field) { removeField(field) {
CustomWizardCustomField.removeField(field) return CustomWizardCustomField.destroyField(field)
.then(result => { .then(result => {
this.get('customFields').removeObject(field); this.get('customFields').removeObject(field);
}); });

Datei anzeigen

@ -14,18 +14,16 @@ CustomWizardCustomField.reopenClass({
return ajax(basePath).catch(popupAjaxError); return ajax(basePath).catch(popupAjaxError);
}, },
saveFields(customFields) { saveField(customField) {
return ajax(basePath, { return ajax(basePath, {
type: 'PUT', type: 'PUT',
dataType: 'json', data: {
contentType: 'application/json', custom_field: customField
data: JSON.stringify({ }
custom_fields: customFields
})
}).catch(popupAjaxError); }).catch(popupAjaxError);
}, },
removeField(field) { destroyField(field) {
return ajax(`${basePath}/${field.name}`, { return ajax(`${basePath}/${field.name}`, {
type: 'DELETE' type: 'DELETE'
}).catch(popupAjaxError); }).catch(popupAjaxError);

Datei anzeigen

@ -2,16 +2,6 @@
<h3>{{i18n 'admin.wizard.custom_field.nav_label'}}</h3> <h3>{{i18n 'admin.wizard.custom_field.nav_label'}}</h3>
<div class="buttons"> <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 {{d-button
label="admin.wizard.custom_field.add" label="admin.wizard.custom_field.add"
icon="plus" icon="plus"
@ -20,7 +10,9 @@
</div> </div>
{{wizard-message {{wizard-message
key='create' key=messageKey
opts=messageOpts
type=messageType
url=documentationUrl url=documentationUrl
component='custom_fields'}} component='custom_fields'}}
@ -36,7 +28,8 @@
{{#each customFields as |field|}} {{#each customFields as |field|}}
{{custom-field-input {{custom-field-input
field=field field=field
removeField=(action 'removeField')}} removeField=(action 'removeField')
saveField=(action 'saveField')}}
{{/each}} {{/each}}
</table> </table>
{{/if}} {{/if}}

Datei anzeigen

@ -13,40 +13,51 @@
none="admin.wizard.custom_field.type.select" none="admin.wizard.custom_field.type.select"
onChange=(action (mut field.type))}} onChange=(action (mut field.type))}}
</td> </td>
<td> <td class="multi-select">
{{multi-select {{multi-select
value=field.serializers value=field.serializers
content=serializerContent content=serializerContent
none="admin.wizard.custom_field.serializers.select" none="admin.wizard.custom_field.serializers.select"
onChange=(action (mut field.serializers))}} onChange=(action (mut field.serializers))}}
</td> </td>
<td> <td class="input">
{{input {{input
value=field.name value=field.name
placeholder=(i18n "admin.wizard.custom_field.klass.select")}} placeholder=(i18n "admin.wizard.custom_field.name.select")}}
</td> </td>
<td> <td class="actions">
{{d-button action="close" icon="times"}} {{#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> </td>
{{else}} {{else}}
<td><label>{{field.klass}}</label></td> <td><label>{{field.klass}}</label></td>
<td><label>{{field.type}}</label></td> <td><label>{{field.type}}</label></td>
<td> <td class="multi-select">
{{#each field.serializers as |serializer|}} {{#each field.serializers as |serializer|}}
<label>{{serializer}}</label> <label>{{serializer}}</label>
{{/each}} {{/each}}
</td> </td>
<td><label>{{field.name}}</label></td> <td class="input"><label>{{field.name}}</label></td>
<td> <td class="actions">
{{d-button action="edit" icon="pencil-alt"}} {{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> </td>
{{/if}} {{/if}}

Datei anzeigen

@ -1,6 +1,7 @@
@import 'wizard-mapper'; @import 'wizard-mapper';
@import 'wizard-manager'; @import 'wizard-manager';
@import 'wizard-api'; @import 'wizard-api';
@import 'common/components/buttons';
.admin-wizard-controls { .admin-wizard-controls {
display: flex; display: flex;
@ -565,16 +566,35 @@
} }
.admin-wizards-custom-fields { .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 { h3 {
margin-bottom: 0; margin-bottom: 0;
} }
.select-kit { .select-kit {
width: 200px; width: 150px;
} }
.select-kit.multi-select { .select-kit.multi-select {
width: 200px; width: 300px;
.choices .choice, .choices .choice,
.select-kit-filter .filter-input { .select-kit-filter .filter-input {
@ -587,20 +607,39 @@
margin: 0; margin: 0;
} }
td { table {
vertical-align: top; td {
vertical-align: top;
label {
margin: 0;
line-height: 30px;
display: inline-block;
margin-right: 10px;
}
}
label { td {
margin: 0; min-width: 170px;
line-height: 30px; 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" edit: "You're editing an action"
documentation: "Check out the action documentation" documentation: "Check out the action documentation"
custom_fields: 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 documentation: Check out the custom field documentation
manager: manager:
info: "Export, import or destroy wizards" info: "Export, import or destroy wizards"
@ -296,11 +298,10 @@ en:
custom_field: custom_field:
nav_label: "Custom Fields" nav_label: "Custom Fields"
add: "Add Custom Field" add: "Add"
save: "Save Custom Fields"
name: name:
label: "Name" label: "Name"
select: "Enter a name" select: "underscored_name"
type: type:
label: "Type" label: "Type"
select: "Select a type" select: "Select a type"
@ -322,6 +323,7 @@ en:
topic_view: "Topic View" topic_view: "Topic View"
topic_list_item: "Topic List Item" topic_list_item: "Topic List Item"
basic_category: "Category" basic_category: "Category"
basic_group: "Group"
post: "Post" post: "Post"
submissions: submissions:

Datei anzeigen

@ -4,39 +4,38 @@ class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController
end end
def update def update
fields_to_save = [] errors = []
field_id = nil
custom_field_params[:custom_fields].each do |field_param| field_data = {}
field_id = nil
field_data = {} if saved_field = CustomWizard::CustomField.find(field_params[:id].to_i)
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
CustomWizard::CustomField::ATTRS.each do |attr| CustomWizard::CustomField::ATTRS.each do |attr|
field_data[attr] = field_param[attr] field_data[attr] = saved_field.send(attr)
end end
field_id = saved_field.id
field = CustomWizard::CustomField.new(field_id, field_data)
fields_to_save.push(field)
end 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 PluginStoreRow.transaction do
fields_to_save.each do |field| unless field.save
unless field.save field_errors = field.errors.any? ?
raise ActiveRecord::Rollback.new, field.errors.full_messages.join("\n\n") :
field.errors.any? ? I18n.t("wizard.custom_field.error.save_default", name: field.name)
field.errors.full_messages.join("\n\n") : errors << field_errors
I18n.t("wizard.custom_field.error.save_default", name: field.name) raise ActiveRecord::Rollback.new
end
end end
end end
render json: success_json if errors.any?
render json: failed_json.merge(messages: errors)
else
render json: success_json
end
end end
def destroy def destroy
@ -51,14 +50,14 @@ class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController
private private
def custom_field_params def field_params
params.permit( params.required(:custom_field)
custom_fields: [ .permit(
:id,
:name, :name,
:klass, :klass,
:type, :type,
serializers: [] serializers: []
] )
)
end end
end end

Datei anzeigen

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

Datei anzeigen

@ -7,7 +7,7 @@ class ::CustomWizard::CustomField
attr_reader :id attr_reader :id
ATTRS ||= ["name", "klass", "type", "serializers"] ATTRS ||= ["name", "klass", "type", "serializers"]
REQUIRED ||= ["name", "klass"] REQUIRED ||= ["name", "klass", "type"]
NAMESPACE ||= "custom_wizard_custom_fields" NAMESPACE ||= "custom_wizard_custom_fields"
NAME_MIN_LENGTH ||= 3 NAME_MIN_LENGTH ||= 3
@ -143,11 +143,21 @@ class ::CustomWizard::CustomField
PluginStoreRow.where(plugin_name: NAMESPACE, key: name).exists? PluginStoreRow.where(plugin_name: NAMESPACE, key: name).exists?
end end
def self.find(name) def self.find(field_id)
records = PluginStoreRow.where(plugin_name: NAMESPACE, key: name) record = PluginStoreRow.find_by(id: field_id, plugin_name: NAMESPACE)
if records.exists? if record
create_from_store(records.first) 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 else
false false
end end
@ -161,8 +171,9 @@ class ::CustomWizard::CustomField
def self.save_to_store(id = nil, key, data) def self.save_to_store(id = nil, key, data)
if id 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 return false if !record
record.key = key
record.value = data.to_json record.value = data.to_json
record.save record.save
else else

Datei anzeigen

@ -31,6 +31,7 @@ if respond_to?(:register_svg_icon)
register_svg_icon "far-calendar" register_svg_icon "far-calendar"
register_svg_icon "chevron-right" register_svg_icon "chevron-right"
register_svg_icon "chevron-left" register_svg_icon "chevron-left"
register_svg_icon "save"
end end
after_initialize do 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 = custom_field_json['custom_fields'][0]
updated_field_json['serializers'] = ["topic_view"] 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) updated_field = CustomWizard::CustomField.new(existing_field.id, updated_field_json)
expect(updated_field.save).to eq(true) 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) expect(response.parsed_body.length).to eq(4)
end end
it "updates the list of custom fields" do it "saves custom fields" do
custom_field_json['custom_fields'][0]['type'] = 'string' topic_field = CustomWizard::CustomField.find_by_name('topic_field_1')
put "/admin/wizards/custom-fields.json", params: custom_field_json 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(response.status).to eq(200)
expect( expect(
CustomWizard::CustomField.find('topic_field_1').type CustomWizard::CustomField.find_by_name('topic_field_1').type
).to eq('string') ).to eq('string')
end 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 end

Datei anzeigen

@ -17,7 +17,7 @@ describe CustomWizard::CustomFieldSerializer do
end end
json = CustomWizard::CustomFieldSerializer.new( json = CustomWizard::CustomFieldSerializer.new(
CustomWizard::CustomField.find("topic_field_1"), CustomWizard::CustomField.find_by_name("topic_field_1"),
scope: Guardian.new(user), scope: Guardian.new(user),
root: false root: false
).as_json ).as_json