From d26744cf56d79f8b56c6b456a7cf867f79abf8cb Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Thu, 16 Apr 2020 12:04:27 +1000 Subject: [PATCH] Schema handling updates - support custom field types - move client schema to seperate helper - handle schema defaults properly --- .../components/wizard-custom-action.js.es6 | 5 +- .../components/wizard-custom-field.js.es6 | 12 +- .../discourse/components/wizard-links.js.es6 | 21 +- .../discourse/lib/wizard-json.js.es6 | 9 +- .../discourse/lib/wizard-schema.js.es6 | 218 ++++++++++++++++ .../javascripts/discourse/lib/wizard.js.es6 | 245 +----------------- .../discourse/models/custom-wizard.js.es6 | 13 +- .../routes/admin-wizards-wizard-show.js.es6 | 2 +- .../routes/admin-wizards-wizard.js.es6 | 9 +- lib/custom_wizard/field.rb | 46 +++- 10 files changed, 295 insertions(+), 285 deletions(-) create mode 100644 assets/javascripts/discourse/lib/wizard-schema.js.es6 diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index 8f5a6103..4a89343b 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -1,11 +1,12 @@ import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators'; import { equal, empty, or } from "@ember/object/computed"; -import { generateName, selectKitContent, schema } from '../lib/wizard'; +import { generateName, selectKitContent } from '../lib/wizard'; +import wizardSchema from '../lib/wizard-schema'; import Component from "@ember/component"; export default Component.extend({ classNames: 'wizard-custom-action', - actionTypes: Object.keys(schema.action.types).map(t => ({ id: t, name: generateName(t) })), + actionTypes: Object.keys(wizardSchema.action.types).map(t => ({ id: t, name: generateName(t) })), createTopic: equal('action.type', 'create_topic'), updateProfile: equal('action.type', 'update_profile'), sendMessage: equal('action.type', 'send_message'), diff --git a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 index e5456806..7496336a 100644 --- a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 @@ -1,6 +1,7 @@ import { default as discourseComputed, observes } from 'discourse-common/utils/decorators'; import { equal, or } from "@ember/object/computed"; -import { selectKitContent, schema } from '../lib/wizard'; +import { selectKitContent } from '../lib/wizard'; +import { default as wizardSchema, setSchemaDefaults } from '../lib/wizard-schema'; import Component from "@ember/component"; export default Component.extend({ @@ -19,18 +20,15 @@ export default Component.extend({ showMinLength: or('isText', 'isTextarea', 'isUrl', 'isComposer'), categoryPropertyTypes: selectKitContent(['id', 'slug']), - // clearMapped only clears mapped fields if the field type of a specific field + // setTypeDefaults only set defaults if the field type of a specific field // changes, and not when switching between fields. Switching between fields also // changes the field.type property in this component @observes('field.id', 'field.type') - clearMapped(ctx, changed) { + setTypeDefaults(ctx, changed) { if (this.field.id === this.bufferedFieldId) { - schema.field.mapped.forEach(property => { - this.set(`field.${property}`, null); - }); + setSchemaDefaults(this.field, 'field'); } - if (changed === 'field.type') { this.set('bufferedFieldId', this.field.id); } diff --git a/assets/javascripts/discourse/components/wizard-links.js.es6 b/assets/javascripts/discourse/components/wizard-links.js.es6 index 5f7be87b..16573abb 100644 --- a/assets/javascripts/discourse/components/wizard-links.js.es6 +++ b/assets/javascripts/discourse/components/wizard-links.js.es6 @@ -1,5 +1,6 @@ import { default as discourseComputed, on, observes } from 'discourse-common/utils/decorators'; -import { generateName, schema } from '../lib/wizard'; +import { generateName } from '../lib/wizard'; +import { default as wizardSchema, setSchemaDefaults } from '../lib/wizard-schema'; import { notEmpty } from "@ember/object/computed"; import { scheduleOnce, bind } from "@ember/runloop"; import EmberObject from "@ember/object"; @@ -64,15 +65,6 @@ export default Component.extend({ } }); }, - - setDefaults(object, params) { - Object.keys(object).forEach(property => { - if (object[property]) { - params[property] = object[property]; - } - }); - return params; - }, actions: { add() { @@ -89,19 +81,14 @@ export default Component.extend({ isNew: true }; - let objectArrays = schema[itemType].objectArrays; + let objectArrays = wizardSchema[itemType].objectArrays; if (objectArrays) { Object.keys(objectArrays).forEach(objectType => { params[objectArrays[objectType].property] = A(); }); }; - params = this.setDefaults(schema[itemType].basic, params); - - let types = schema[itemType].types; - if (types && params.type) { - params = this.setDefaults(types[params.type], params); - } + setSchemaDefaults(params, itemType); const newItem = EmberObject.create(params); items.pushObject(newItem); diff --git a/assets/javascripts/discourse/lib/wizard-json.js.es6 b/assets/javascripts/discourse/lib/wizard-json.js.es6 index 25075f4c..33b46707 100644 --- a/assets/javascripts/discourse/lib/wizard-json.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-json.js.es6 @@ -1,4 +1,5 @@ -import { schema, listProperties, camelCase, snakeCase } from '../lib/wizard'; +import { listProperties, camelCase, snakeCase } from '../lib/wizard'; +import wizardSchema from '../lib/wizard-schema'; import EmberObject from '@ember/object'; import { A } from "@ember/array"; @@ -15,7 +16,7 @@ function present(val) { } function mapped(property, type) { - return schema[type].mapped.indexOf(property) > -1; + return wizardSchema[type].mapped.indexOf(property) > -1; } function castCase(property, value) { @@ -114,7 +115,7 @@ function buildBasicProperties(json, type, props) { function hasAdvancedProperties(object, type) { return Object.keys(object).some(p => { - return schema[type].advanced.indexOf(p) > -1 && present(object[p]); + return wizardSchema[type].advanced.indexOf(p) > -1 && present(object[p]); }); } @@ -164,7 +165,7 @@ function buildProperties(json) { props.actions = buildObjectArray(json.actions, 'action'); } else { listProperties('wizard').forEach(prop => { - props[prop] = schema.wizard.basic[prop]; + props[prop] = wizardSchema.wizard.basic[prop]; }); } diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 new file mode 100644 index 00000000..24d824c0 --- /dev/null +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -0,0 +1,218 @@ +import { set } from "@ember/object"; + +const wizard = { + basic: { + id: null, + name: null, + background: null, + save_submissions: true, + multiple_submissions: null, + after_signup: null, + after_time: null, + after_time_scheduled: null, + required: null, + prompt_completion: null, + restart_on_revisit: null, + theme_id: null, + permitted: null + }, + mapped: [ + 'permitted' + ], + advanced: [ + 'restart_on_revisit', + ], + required: [ + 'id', + ], + dependent: { + after_time: 'after_time_scheduled' + }, + objectArrays: { + step: { + property: 'steps', + required: false + }, + action: { + property: 'actions', + required: false + } + } +}; + +const step = { + basic: { + id: null, + title: null, + key: null, + banner: null, + raw_description: null, + required_data: null, + required_data_message: null, + permitted_params: null + }, + mapped: [ + 'required_data', + 'permitted_params' + ], + advanced: [ + 'required_data', + 'permitted_params' + ], + required: [ + 'id' + ], + dependent: { + }, + objectArrays: { + field: { + property: 'fields', + required: false + } + } +} + +const field = { + basic: { + id: null, + label: null, + image: null, + description: null, + required: null, + key: null, + type: null + }, + types: {}, + mapped: [ + 'prefill', + 'content' + ], + advanced: [ + 'property', + 'key' + ], + required: [ + 'id', + 'type' + ], + dependent: { + }, + objectArrays: { + } +} + +const action = { + basic: { + id: null, + run_after: 'wizard_completion', + type: null + }, + types: { + create_topic: { + title: null, + post: null, + post_builder: null, + post_template: null, + category: null, + tags: null, + custom_fields: null, + skip_redirect: null + }, + send_message: { + title: null, + post: null, + post_builder: null, + post_template: null, + skip_redirect: null, + custom_fields: null, + required: null, + recipient: null + }, + open_composer: { + title: null, + post: null, + post_builder: null, + post_template: null, + category: null, + tags: null, + custom_fields: null + }, + update_profile: { + profile_updates: null, + custom_fields: null + }, + add_to_group: { + group: null + }, + route_to: { + url: null, + code: null + } + }, + mapped: [ + 'title', + 'category', + 'tags', + 'custom_fields', + 'required', + 'recipient', + 'profile_updates', + 'group' + ], + advanced: [ + 'code', + 'custom_fields', + 'skip_redirect', + 'required' + ], + required: [ + 'id', + 'type' + ], + dependent: { + }, + objectArrays: { + } +} + +const wizardSchema = { + wizard, + step, + field, + action +} + +export function buildFieldTypes(types) { + wizardSchema.field.types = types; +} + +if (Discourse.SiteSettings.wizard_apis_enabled) { + wizardSchema.action.types.send_to_api = { + api: null, + api_endpoint: null, + api_body: null + } +} + +export function setSchemaDefaults(obj, objType) { + let objSchema = wizardSchema[objType]; + let basicDefaults = objSchema.basic; + + Object.keys(basicDefaults).forEach(property => { + if (basicDefaults[property]) { + set(obj, property, basicDefaults[property]); + } + }); + + if (objSchema.types && obj.type) { + let typeDefaults = objSchema.types[obj.type]; + + Object.keys(typeDefaults).forEach(property => { + if (typeDefaults.hasOwnProperty(property)) { + set(obj, property, typeDefaults[property]); + } + }); + } +} + +export default wizardSchema; \ No newline at end of file diff --git a/assets/javascripts/discourse/lib/wizard.js.es6 b/assets/javascripts/discourse/lib/wizard.js.es6 index 5bd202d0..aef25c13 100644 --- a/assets/javascripts/discourse/lib/wizard.js.es6 +++ b/assets/javascripts/discourse/lib/wizard.js.es6 @@ -1,4 +1,5 @@ import EmberObject from "@ember/object"; +import wizardSchema from './wizard-schema'; function selectKitContent(content) { return content.map(i => ({id: i, name: i})); @@ -48,248 +49,11 @@ const userProperties = [ 'trust_level' ]; -const wizardProperties = { - basic: { - id: null, - name: null, - background: null, - save_submissions: true, - multiple_submissions: null, - after_signup: null, - after_time: null, - after_time_scheduled: null, - required: null, - prompt_completion: null, - restart_on_revisit: null, - theme_id: null, - permitted: null - }, - mapped: [ - 'permitted' - ], - advanced: [ - 'restart_on_revisit', - ], - required: [ - 'id', - ], - dependent: { - after_time: 'after_time_scheduled' - }, - objectArrays: { - step: { - property: 'steps', - required: false - }, - action: { - property: 'actions', - required: false - } - } -}; - -const stepProperties = { - basic: { - id: null, - title: null, - key: null, - banner: null, - raw_description: null, - required_data: null, - required_data_message: null, - permitted_params: null - }, - mapped: [ - 'required_data', - 'permitted_params' - ], - advanced: [ - 'required_data', - 'permitted_params' - ], - required: [ - 'id' - ], - dependent: { - }, - objectArrays: { - field: { - property: 'fields', - required: false - } - } -} - -const fieldProperties = { - basic: { - id: null, - label: null, - image: null, - description: null, - required: null, - key: null, - type: null - }, - types: { - text: { - min_length: null - }, - textarea: { - min_length: null - }, - composer: { - min_length: null - }, - text_only: { - }, - number: { - }, - checkbox: { - }, - url: { - min_length: null - }, - upload: { - file_types: '.jpg,.png' - }, - dropdown: { - prefill: null, - content: null - }, - tag: { - limit: null, - prefill: null, - content: null - }, - category: { - limit: 1, - property: 'id', - prefill: null, - content: null - }, - group: { - prefill: null, - content: null - }, - user_selector: { - }, - event: { - }, - location: { - } - }, - mapped: [ - 'prefill', - 'content' - ], - advanced: [ - 'property', - 'key' - ], - required: [ - 'id', - 'type' - ], - dependent: { - }, - objectArrays: { - } -} - -const actionProperties = { - basic: { - id: null, - run_after: 'wizard_completion', - type: null - }, - types: { - create_topic: { - title: null, - post: null, - post_builder: null, - post_template: null, - category: null, - tags: null, - custom_fields: null, - skip_redirect: null - }, - send_message: { - title: null, - post: null, - post_builder: null, - post_template: null, - skip_redirect: null, - custom_fields: null, - required: null, - recipient: null - }, - open_composer: { - title: null, - post: null, - post_builder: null, - post_template: null, - category: null, - tags: null, - custom_fields: null - }, - update_profile: { - profile_updates: null, - custom_fields: null - }, - add_to_group: { - group: null - }, - route_to: { - url: null, - code: null - } - }, - mapped: [ - 'title', - 'category', - 'tags', - 'custom_fields', - 'required', - 'recipient', - 'profile_updates', - 'group' - ], - advanced: [ - 'code', - 'custom_fields', - 'skip_redirect', - 'required' - ], - required: [ - 'id', - 'type' - ], - dependent: { - }, - objectArrays: { - } -} - -if (Discourse.SiteSettings.wizard_apis_enabled) { - actionProperties.types.send_to_api = { - api: null, - api_endpoint: null, - api_body: null - } -} - -const schema = { - wizard: wizardProperties, - step: stepProperties, - field: fieldProperties, - action: actionProperties -} - function listProperties(type, objectType = null) { - let properties = Object.keys(schema[type].basic); + let properties = Object.keys(wizardSchema[type].basic); - if (schema[type].types && objectType) { - properties = properties.concat(Object.keys(schema[type].types[objectType])); + if (wizardSchema[type].types && objectType) { + properties = properties.concat(Object.keys(wizardSchema[type].types[objectType])); } return properties; @@ -328,7 +92,6 @@ export { generateId, camelCase, snakeCase, - schema, userProperties, listProperties, wizardFieldList diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6 index e65ad169..de3c2d80 100644 --- a/assets/javascripts/discourse/models/custom-wizard.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard.js.es6 @@ -1,7 +1,8 @@ import { ajax } from 'discourse/lib/ajax'; import EmberObject from "@ember/object"; import { buildProperties, present, mapped } from '../lib/wizard-json'; -import { schema, listProperties, camelCase, snakeCase } from '../lib/wizard'; +import { listProperties, camelCase, snakeCase } from '../lib/wizard'; +import wizardSchema from '../lib/wizard-schema'; import { Promise } from "rsvp"; const CustomWizard = EmberObject.extend({ @@ -38,7 +39,7 @@ const CustomWizard = EmberObject.extend({ buildJson(object, type, result = {}) { let objectType = object.type || null; - if (schema[type].types) { + if (wizardSchema[type].types) { if (!objectType) { result.error = { type: 'required', @@ -67,8 +68,8 @@ const CustomWizard = EmberObject.extend({ }; if (!result.error) { - for (let arrayObjectType of Object.keys(schema[type].objectArrays)) { - let arraySchema = schema[type].objectArrays[arrayObjectType]; + for (let arrayObjectType of Object.keys(wizardSchema[type].objectArrays)) { + let arraySchema = wizardSchema[type].objectArrays[arrayObjectType]; let objectArray = object.get(arraySchema.property); if (arraySchema.required && !present(objectArray)) { @@ -98,14 +99,14 @@ const CustomWizard = EmberObject.extend({ }, validateValue(property, value, object, type, result) { - if (schema[type].required.indexOf(property) > -1 && !value) { + if (wizardSchema[type].required.indexOf(property) > -1 && !value) { result.error = { type: 'required', params: { type, property } } } - let dependent = schema[type].dependent[property]; + let dependent = wizardSchema[type].dependent[property]; if (dependent && value && !object[dependent]) { result.error = { type: 'dependent', 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 d2e7ff2f..69245453 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 @@ -24,7 +24,7 @@ export default DiscourseRoute.extend({ controller.setProperties({ wizardList: parentModel.wizard_list, - fieldTypes: selectKitContent(parentModel.field_types), + fieldTypes: selectKitContent(Object.keys(parentModel.field_types)), userFields: parentModel.userFields, apis: parentModel.apis, themes: parentModel.themes, diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard.js.es6 index a4efdd8c..5f2ba5af 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-wizard.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-wizard.js.es6 @@ -1,5 +1,6 @@ import DiscourseRoute from "discourse/routes/discourse"; import { userProperties, generateName } from '../lib/wizard'; +import { buildFieldTypes } from '../lib/wizard-schema'; import { set } from "@ember/object"; import { all } from "rsvp"; import { ajax } from 'discourse/lib/ajax'; @@ -10,6 +11,8 @@ export default DiscourseRoute.extend({ }, afterModel(model) { + buildFieldTypes(model.field_types); + return all([ this._getThemes(model), this._getApis(model), @@ -63,12 +66,10 @@ export default DiscourseRoute.extend({ }, setupController(controller, model) { - let props = { + controller.setProperties({ wizardList: model.wizard_list, wizardId: this.currentWizard() - } - - controller.setProperties(props); + }); }, actions: { diff --git a/lib/custom_wizard/field.rb b/lib/custom_wizard/field.rb index e32f9f76..77838cd0 100644 --- a/lib/custom_wizard/field.rb +++ b/lib/custom_wizard/field.rb @@ -1,15 +1,55 @@ class CustomWizard::Field def self.types - @types ||= ['text', 'textarea', 'composer', 'text_only', 'number', 'checkbox', 'url', 'upload', 'dropdown', 'tag', 'category', 'group', 'user_selector'] + @types ||= { + text: { + min_length: nil + }, + textarea: { + min_length: nil + }, + composer: { + min_length: nil + }, + text_only: {}, + number: {}, + checkbox: {}, + url: { + min_length: nil + }, + upload: { + file_types: '.jpg,.png' + }, + dropdown: { + prefill: nil, + content: nil + }, + tag: { + limit: nil, + prefill: nil, + content: nil + }, + category: { + limit: 1, + property: 'id', + prefill: nil, + content: nil + }, + group: { + prefill: nil, + content: nil + }, + user_selector: {} + } end def self.require_assets @require_assets ||= {} end - def self.add_assets(type, plugin = nil, asset_paths = []) + def self.add_assets(type, plugin = nil, asset_paths = [], opts={}) if type - types.push(*type) + types[type] ||= {} + types[type] = opts[:type_opts] if opts[:type_opts].present? end if plugin && asset_paths