diff --git a/assets/javascripts/discourse/components/custom-field-input.js.es6 b/assets/javascripts/discourse/components/custom-field-input.js.es6 new file mode 100644 index 00000000..b5dbec5b --- /dev/null +++ b/assets/javascripts/discourse/components/custom-field-input.js.es6 @@ -0,0 +1,45 @@ +import Component from "@ember/component"; +import discourseComputed, { discourseObserve } from "discourse-common/utils/decorators"; +import { or } from "@ember/object/computed"; + +const generateContent = function(array, type) { + return array.map(key => ({ + id: key, + name: I18n.t(`admin.wizard.custom_field.${type}.${key}`) + })); +} + +export default Component.extend({ + tagName: 'tr', + topicSerializers: ['topic_view', 'topic_list_item'], + postSerializers: ['post'], + categorySerializers: ['basic_category', 'topic_view', 'topic_list_item'], + klassContent: generateContent(['topic', 'post', 'group', 'category'], 'klass'), + typeContent: generateContent(['string', 'boolean', 'json'], 'type'), + showInputs: or('field.new', 'field.edit'), + + @discourseComputed('field.klass') + serializerContent(klass) { + const serializers = this.get(`${klass}Serializers`); + + if (serializers) { + return generateContent(serializers, 'serializers'); + } else { + return []; + } + }, + + actions: { + edit() { + this.set('field.edit', true); + }, + + close() { + if (this.field.edit) { + this.set('field.edit', false); + } else { + this.removeField(this.field); + } + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 b/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 index 7d37f74f..5a8c7d63 100644 --- a/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 +++ b/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 @@ -21,9 +21,11 @@ export default Component.extend({ showGroup: computed('activeType', function() { return this.showInput('group') }), showUser: computed('activeType', function() { return this.showInput('user') }), showList: computed('activeType', function() { return this.showInput('list') }), + showCustomField: computed('activeType', function() { return this.showInput('customField') }), textEnabled: computed('options.textSelection', 'inputType', function() { return this.optionEnabled('textSelection') }), wizardFieldEnabled: computed('options.wizardFieldSelection', 'inputType', function() { return this.optionEnabled('wizardFieldSelection') }), wizardActionEnabled: computed('options.wizardActionSelection', 'inputType', function() { return this.optionEnabled('wizardActionSelection') }), + customFieldEnabled: computed('options.customFieldSelection', 'inputType', function() { return this.optionEnabled('customFieldSelection') }), userFieldEnabled: computed('options.userFieldSelection', 'inputType', function() { return this.optionEnabled('userFieldSelection') }), userFieldOptionsEnabled: computed('options.userFieldOptionsSelection', 'inputType', function() { return this.optionEnabled('userFieldOptionsSelection') }), categoryEnabled: computed('options.categorySelection', 'inputType', function() { return this.optionEnabled('categorySelection') }), @@ -34,7 +36,7 @@ export default Component.extend({ groups: alias('site.groups'), categories: alias('site.categories'), - showComboBox: or('showWizardField', 'showWizardAction', 'showUserField', 'showUserFieldOptions'), + showComboBox: or('showWizardField', 'showWizardAction', 'showUserField', 'showUserFieldOptions', 'showCustomField'), showMultiSelect: or('showCategory', 'showGroup'), hasTypes: gt('selectorTypes.length', 1), showTypes: false, @@ -88,7 +90,8 @@ export default Component.extend({ 'showController.wizard.actions.[]', 'showController.userFields.[]', 'showController.currentField.id', - 'showController.currentAction.id' + 'showController.currentAction.id', + 'showController.customFields' ) comboBoxContent( activeType, @@ -96,7 +99,8 @@ export default Component.extend({ wizardActions, userFields, currentFieldId, - currentActionId + currentActionId, + customFields ) { let content; @@ -139,6 +143,10 @@ export default Component.extend({ content = userFields; } + if (activeType === 'customField') { + content = customFields; + } + return content; }, diff --git a/assets/javascripts/discourse/controllers/admin-wizards-custom-fields.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-custom-fields.js.es6 index 72a66137..9ed7762c 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-custom-fields.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-custom-fields.js.es6 @@ -4,8 +4,7 @@ import { ajax } from 'discourse/lib/ajax'; import { popupAjaxError } from 'discourse/lib/ajax-error'; export default Controller.extend({ - fieldKeys: ['klass', 'name', 'type'], - classes: ['topic', 'user', 'group'], + fieldKeys: ['klass', 'type', 'serializers', 'name'], actions: { addField() { @@ -16,13 +15,28 @@ export default Controller.extend({ ); }, - saveFields() { + removeField(field) { + this.get('customFields').removeObject(field); + }, + + saveFields() { + this.set('saving', true); ajax(`/admin/wizards/custom-fields`, { type: 'PUT', - data: { + dataType: 'json', + contentType: 'application/json', + data: JSON.stringify({ custom_fields: this.customFields + }) + }).then(result => { + if (result.success) { + this.set('saveIcon', 'check'); + } else { + this.set('saveIcon', 'times'); } - }).catch(popupAjaxError) + setTimeout(() => this.set('saveIcon', ''), 5000); + }).finally(() => this.set('saving', false)) + .catch(popupAjaxError); } } }); \ No newline at end of file diff --git a/assets/javascripts/discourse/lib/wizard-mapper.js.es6 b/assets/javascripts/discourse/lib/wizard-mapper.js.es6 index 470a78c4..6fc50588 100644 --- a/assets/javascripts/discourse/lib/wizard-mapper.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-mapper.js.es6 @@ -86,7 +86,8 @@ const selectionTypes = [ 'group', 'category', 'tag', - 'user' + 'user', + 'customField' ] function defaultSelectionType(inputType, options = {}) { diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 index f8dd8479..21f3bb1b 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 @@ -2,6 +2,7 @@ import CustomWizard from '../models/custom-wizard'; import { ajax } from 'discourse/lib/ajax'; import DiscourseRoute from "discourse/routes/discourse"; import I18n from "I18n"; +import { selectKitContent } from '../lib/wizard'; export default DiscourseRoute.extend({ model(params) { @@ -32,6 +33,7 @@ export default DiscourseRoute.extend({ wizardList: parentModel.wizard_list, fieldTypes, userFields: parentModel.userFields, + customFields: selectKitContent(parentModel.custom_fields.map(f => f.name)), apis: parentModel.apis, themes: parentModel.themes, wizard, diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard.js.es6 index 0ba204f3..588936f8 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-wizard.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-wizard.js.es6 @@ -1,6 +1,7 @@ import DiscourseRoute from "discourse/routes/discourse"; import { buildFieldTypes } from '../lib/wizard-schema'; -import { set } from "@ember/object"; +import EmberObject, { set } from "@ember/object"; +import { A } from "@ember/array"; import { all } from "rsvp"; import { ajax } from 'discourse/lib/ajax'; @@ -62,7 +63,8 @@ export default DiscourseRoute.extend({ setupController(controller, model) { controller.setProperties({ wizardList: model.wizard_list, - wizardId: this.currentWizard() + wizardId: this.currentWizard(), + custom_fields: A(model.custom_fields.map(f => EmberObject.create(f))) }); }, diff --git a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs index 8dd340f0..a79ff3a9 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs @@ -1,32 +1,37 @@
-

{{i18n 'admin.wizard.custom_fields.nav_label'}}

+

{{i18n 'admin.wizard.custom_field.nav_label'}}

- {{d-button - label="add" - icon="plus" - action="addField"}} +
+ {{#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" + action="addField"}} +
- {{#if model}} + {{#if customFields}} {{#each fieldKeys as |key|}} - + {{/each}} + {{#each customFields as |field|}} - - {{#if field.new}} - - - - {{else}} - {{#each-in field as |k v|}} - - {{/each-in}} - {{/if}} - + {{custom-field-input + field=field + removeField=(action 'removeField')}} {{/each}}
{{i18n (concat "admin.wizard.custom_fields." key)}}{{i18n (concat "admin.wizard.custom_field." key ".label")}}
{{combo-box value=field.klass content=classes onChange=(action (mut field.klass))}}{{input value=field.name}}{{combo-box value=field.type content=types onChange=(action (mut field.type))}}{{v}}
{{/if}} diff --git a/assets/javascripts/discourse/templates/admin-wizards.hbs b/assets/javascripts/discourse/templates/admin-wizards.hbs index 1e6399ab..53ec86a6 100644 --- a/assets/javascripts/discourse/templates/admin-wizards.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards.hbs @@ -1,6 +1,6 @@ {{#admin-nav}} {{nav-item route='adminWizardsWizard' label='admin.wizard.nav_label'}} - {{nav-item route='adminWizardsCustomFields' label='admin.wizard.custom_fields.nav_label'}} + {{nav-item route='adminWizardsCustomFields' label='admin.wizard.custom_field.nav_label'}} {{nav-item route='adminWizardsSubmissions' label='admin.wizard.submissions.nav_label'}} {{#if siteSettings.wizard_apis_enabled}} {{nav-item route='adminWizardsApi' label='admin.wizard.api.nav_label'}} diff --git a/assets/javascripts/discourse/templates/components/custom-field-input.hbs b/assets/javascripts/discourse/templates/components/custom-field-input.hbs new file mode 100644 index 00000000..fdb3d26d --- /dev/null +++ b/assets/javascripts/discourse/templates/components/custom-field-input.hbs @@ -0,0 +1,44 @@ +{{#if showInputs}} + + {{combo-box + value=field.klass + content=klassContent + none="admin.wizard.custom_field.klass.select" + onChange=(action (mut field.klass))}} + + + {{combo-box + value=field.type + content=typeContent + none="admin.wizard.custom_field.type.select" + onChange=(action (mut field.type))}} + + + {{multi-select + value=field.serializers + content=serializerContent + none="admin.wizard.custom_field.serializers.select" + onChange=(action (mut field.serializers))}} + + + {{input + value=field.name + placeholder=(i18n "admin.wizard.custom_field.klass.select")}} + + + {{d-button action="close" icon="times"}} + +{{else}} + + + + {{#each field.serializers as |serializer|}} + + {{/each}} + + + + {{d-button action="edit" icon="pencil-alt"}} + {{d-button action="close" icon="times"}} + +{{/if}} \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs index 98840175..137cf0f8 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs @@ -730,6 +730,7 @@ onUpdate=(action 'mappedFieldUpdated') options=(hash inputTypes='association' + customFieldSelection='key' wizardFieldSelection='value' wizardActionSelection='value' userFieldSelection='value' diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index 99fa857e..1c7c29b2 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -535,3 +535,21 @@ background-color: $secondary; border: 1px solid $primary-medium; } + +.admin-wizards-custom-fields { + .select-kit { + width: 200px; + } + + .select-kit.multi-select { + width: 250px; + } + + input[type="text"] { + margin: 0; + } + + td:last-of-type { + text-align: right; + } +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 2ec64e81..0a7b4682 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -102,6 +102,7 @@ en: tag: "tag" group: "group" list: "list" + custom_field: "custom field" placeholder: text: "Enter text" @@ -115,6 +116,7 @@ en: tag: "Select tag" group: "Select group" list: "Enter item" + custom_field: "Select field" error: failed: "failed to save wizard" @@ -277,12 +279,31 @@ en: visibility_level: Visibility Level members_visibility_level: Members Visibility Level - custom_fields: + custom_field: nav_label: "Custom Fields" - klass: "Class" - name: "Name" - type: "Type" - + add: "Add Custom Field" + save: "Save Custom Fields" + name: + label: "Name" + select: "Enter a name" + type: + label: "Type" + select: "Select a type" + string: "String" + boolean: "Boolean" + json: "JSON" + klass: + label: "Class" + select: "Select a class" + topic: "Topic" + group: "Group" + user: "User" + serializers: + label: "Serializers" + select: "Select serializers" + topic_view: "Topic View" + topic_list_item: "Topic List Item" + submissions: nav_label: "Submissions" title: "{{name}} Submissions" diff --git a/config/routes.rb b/config/routes.rb index 908e9656..4050d810 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -19,6 +19,9 @@ Discourse::Application.routes.append do put 'admin/wizards/wizard/:wizard_id' => 'admin_wizard#save' delete 'admin/wizards/wizard/:wizard_id' => 'admin_wizard#remove' + get 'admin/wizards/custom-fields' => 'admin_custom_fields#index' + put 'admin/wizards/custom-fields' => 'admin_custom_fields#update' + get 'admin/wizards/submissions' => 'admin_submissions#index' get 'admin/wizards/submissions/:wizard_id' => 'admin_submissions#show' get 'admin/wizards/submissions/:wizard_id/download' => 'admin_submissions#download' diff --git a/controllers/custom_wizard/admin/admin.rb b/controllers/custom_wizard/admin/admin.rb index 2dd87e86..3f70b542 100644 --- a/controllers/custom_wizard/admin/admin.rb +++ b/controllers/custom_wizard/admin/admin.rb @@ -10,4 +10,8 @@ class CustomWizard::AdminController < ::Admin::AdminController params.require(:wizard_id) @wizard = CustomWizard::Wizard.create(params[:wizard_id].underscore) end + + def custom_field_list + serialize_data(CustomWizard::CustomField.list, CustomWizard::CustomFieldSerializer) + end end \ No newline at end of file diff --git a/controllers/custom_wizard/admin/custom_fields.rb b/controllers/custom_wizard/admin/custom_fields.rb index 24b5310d..6359d08f 100644 --- a/controllers/custom_wizard/admin/custom_fields.rb +++ b/controllers/custom_wizard/admin/custom_fields.rb @@ -1,18 +1,19 @@ -class CustomWizard::CustomFieldsController < CustomWizard::AdminController +class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController def index - render_custom_field_list + render_json_dump(custom_field_list) end - def update - field_data = params[:custom_fields] + def update + custom_fields = custom_field_params[:custom_fields].map do |data| + CustomWizard::CustomField.new(data.to_h) + end - custom_fields = field_data.map { |data| CustomWizard::CustomFields.new(data) } - - custom_fields.each do |field_data| + custom_fields.each do |custom_field| custom_field.validate unless custom_field.valid? - raise Discourse::InvalidParameters, "Invalid field: '#{custom_field.name}'" + raise Discourse::InvalidParameters, + custom_field.errors.full_messages.join("\n\n") end end @@ -25,16 +26,22 @@ class CustomWizard::CustomFieldsController < CustomWizard::AdminController end if all_fields_saved - render_custom_field_list + render json: success_json else render json: error_json end end - def render_custom_field_list - render_serialized( - CustomWizard::CustomFields.list, - CustomWizard::CustomFieldsSerializer + private + + def custom_field_params + params.permit( + custom_fields: [ + :klass, + :name, + :type, + serializers: [] + ] ) end end \ No newline at end of file diff --git a/controllers/custom_wizard/admin/wizard.rb b/controllers/custom_wizard/admin/wizard.rb index 2caa3dde..a4515008 100644 --- a/controllers/custom_wizard/admin/wizard.rb +++ b/controllers/custom_wizard/admin/wizard.rb @@ -7,7 +7,8 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController CustomWizard::Wizard.list, each_serializer: CustomWizard::BasicWizardSerializer ), - field_types: CustomWizard::Field.types + field_types: CustomWizard::Field.types, + custom_fields: custom_field_list ) end diff --git a/lib/custom_wizard/action.rb b/lib/custom_wizard/action.rb index 65ad3cef..5f657dc7 100644 --- a/lib/custom_wizard/action.rb +++ b/lib/custom_wizard/action.rb @@ -60,12 +60,18 @@ class CustomWizard::Action end def send_message - if action['required'].present? && data[action['required']].blank? - log_error( - "required not present", - "required: #{action['required']}; data: #{data[action['required']]}" - ) - return + + if action['required'].present? + required = CustomWizard::Mapper.new( + inputs: action['required'], + data: data, + user: user + ).perform + + if required.blank? + log_error("required input not present") + return + end end params = basic_topic_params @@ -410,17 +416,32 @@ class CustomWizard::Action user: user ).perform + registered_fields = CustomWizard::CustomField.list + field_map.each do |field| keyArr = field[:key].split('.') value = field[:value] - if keyArr.first === 'topic' + if keyArr.length > 1 + klass = keyArr.first + name = keyArr.last + else + name = keyArr.first + end + + + registered = registered_fields.select { |f| f.name == name } + if registered.first.present? + klass = registered.first.klass + end + + if klass === 'topic' params[:topic_opts] ||= {} params[:topic_opts][:custom_fields] ||= {} - params[:topic_opts][:custom_fields][keyArr.last] = value + params[:topic_opts][:custom_fields][name] = value else params[:custom_fields] ||= {} - params[:custom_fields][keyArr.last.to_sym] = value + params[:custom_fields][name] = value end end end diff --git a/lib/custom_wizard/custom_fields.rb b/lib/custom_wizard/custom_field.rb similarity index 52% rename from lib/custom_wizard/custom_fields.rb rename to lib/custom_wizard/custom_field.rb index 98c3cc01..d7e1b61b 100644 --- a/lib/custom_wizard/custom_fields.rb +++ b/lib/custom_wizard/custom_field.rb @@ -1,9 +1,11 @@ -class ::CustomWizard::CustomFields +class ::CustomWizard::CustomField include HasErrors include ActiveModel::Serialization - CLASSES ||= ["topic", "user", "group", "category"] - ATTRS ||= ["name", "klass", "type"] + CLASSES ||= ["topic", "group", "category", "post"] + SERIALIZERS ||= ["topic_view", "topic_list_item", "post", "basic_category"] + TYPES ||= ["string", "boolean", "json"] + ATTRS ||= ["name", "klass", "type", "serializers"] KEY ||= "custom_wizard_custom_fields" def initialize(data) @@ -26,7 +28,7 @@ class ::CustomWizard::CustomFields value = send(attr) if attr == 'name' - name = value + name = value.parameterize(separator: '_') else data[attr] = value end @@ -41,8 +43,27 @@ class ::CustomWizard::CustomFields def validate ATTRS.each do |attr| value = send(attr) - add_error("Attribute required: #{attr}") if value.blank? - add_error("Unsupported class: #{value}") if CLASSES.exclude?(value) + + if value.blank? + add_error("Attribute required: #{attr}") + next + end + + if attr == 'klass' && CLASSES.exclude?(value) + add_error("Unsupported class: #{value}") + end + + if attr == 'serializers' && (SERIALIZERS & value).empty? + add_error("Unsupported serializer: #{value}") + end + + if attr == 'type' && TYPES.exclude?(value) + add_error("Unsupported type: #{value}") + end + + if attr == 'name' && value.length < 3 + add_error("Field name is too short") + end end end diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb index 549b0422..04308a7f 100644 --- a/lib/custom_wizard/wizard.rb +++ b/lib/custom_wizard/wizard.rb @@ -320,10 +320,6 @@ class CustomWizard::Wizard Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard[:id]) Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard[:id]) end - - if serialize_fields.present? - - end end wizard[:id] diff --git a/plugin.rb b/plugin.rb index 01852f63..bf840879 100644 --- a/plugin.rb +++ b/plugin.rb @@ -52,7 +52,7 @@ after_initialize do ../lib/custom_wizard/action_result.rb ../lib/custom_wizard/action.rb ../lib/custom_wizard/builder.rb - ../lib/custom_wizard/custom_fields.rb + ../lib/custom_wizard/custom_field.rb ../lib/custom_wizard/field.rb ../lib/custom_wizard/mapper.rb ../lib/custom_wizard/log.rb @@ -70,7 +70,7 @@ after_initialize do ../serializers/custom_wizard/api_serializer.rb ../serializers/custom_wizard/basic_api_serializer.rb ../serializers/custom_wizard/basic_wizard_serializer.rb - ../serializers/custom_wizard/custom_fields_serializer.rb + ../serializers/custom_wizard/custom_field_serializer.rb ../serializers/custom_wizard/wizard_field_serializer.rb ../serializers/custom_wizard/wizard_step_serializer.rb ../serializers/custom_wizard/wizard_serializer.rb @@ -163,16 +163,23 @@ after_initialize do CustomWizard::Wizard.register_styles - CustomWizard::CustomFields.list.each do |field| + CustomWizard::CustomField.list.each do |field| + self.send("register_#{field.klass}_custom_field_type", field.name, field.type.to_sym) + add_to_class(field.klass.to_sym, field.name.to_sym) do custom_fields[field.name] end - add_to_serializer(field.klass.to_sym, field.name.to_sym) do - if field.klass === 'topic' - object.topic.send(field.name) - else - object.send(field.name) + if field.serializers.any? + field.serializers.each do |klass| + klass = klass.to_sym + add_to_serializer(klass, field.name.to_sym) do + if klass == :topic_view + object.topic.send(field.name) + else + object.send(field.name) + end + end end end end diff --git a/serializers/custom_wizard/custom_field_serializer.rb b/serializers/custom_wizard/custom_field_serializer.rb new file mode 100644 index 00000000..14e3fb32 --- /dev/null +++ b/serializers/custom_wizard/custom_field_serializer.rb @@ -0,0 +1,3 @@ +class CustomWizard::CustomFieldSerializer < ApplicationSerializer + attributes :klass, :name, :type, :serializers +end \ No newline at end of file diff --git a/serializers/custom_wizard/custom_fields_serializer.rb b/serializers/custom_wizard/custom_fields_serializer.rb deleted file mode 100644 index fe8c14f6..00000000 --- a/serializers/custom_wizard/custom_fields_serializer.rb +++ /dev/null @@ -1,3 +0,0 @@ -class CustomWizard::CustomFieldsSerializer < ApplicationSerializer - attributes :klass, :name, :type -end \ No newline at end of file