From d8fd5cb258a940830d8b42c319a276204bf8bbd1 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Sat, 11 Apr 2020 16:22:12 +1000 Subject: [PATCH] various --- .../components/wizard-custom-action.js.es6 | 11 - .../components/wizard-custom-field.js.es6 | 11 - .../discourse/components/wizard-links.js.es6 | 54 +++-- .../components/wizard-mapper-connector.js.es6 | 9 +- .../components/wizard-mapper-selector.js.es6 | 47 +++-- .../discourse/controllers/admin-wizard.js.es6 | 31 ++- .../discourse/lib/wizard-json.js.es6 | 3 +- .../discourse/lib/wizard-mapper.js.es6 | 7 + .../javascripts/discourse/lib/wizard.js.es6 | 16 +- .../discourse/models/custom-wizard.js.es6 | 8 +- .../discourse/templates/admin-wizard.hbs | 5 + .../templates/components/wizard-links.hbs | 16 +- .../components/wizard-mapper-connector.hbs | 3 +- .../components/wizard-mapper-selector.hbs | 30 +-- .../wizard/initializers/custom.js.es6 | 10 +- assets/stylesheets/common/wizard-admin.scss | 47 +++-- assets/stylesheets/common/wizard-mapper.scss | 45 +++- config/locales/client.en.yml | 1 + controllers/custom_wizard/admin.rb | 47 +++-- lib/custom_wizard/builder.rb | 2 +- lib/custom_wizard/field.rb | 2 +- spec/components/custom_wizard/action_spec.rb | 92 ++++++++ spec/components/custom_wizard/builder_spec.rb | 197 +----------------- spec/components/custom_wizard/mapper_spec.rb | 14 ++ 24 files changed, 384 insertions(+), 324 deletions(-) create mode 100644 spec/components/custom_wizard/action_spec.rb create mode 100644 spec/components/custom_wizard/mapper_spec.rb diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index 37b76f83..8f5a6103 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -21,17 +21,6 @@ export default Component.extend({ publicTopicFields: or('createTopic', 'openComposer'), showSkipRedirect: or('createTopic', 'sendMessage'), - @observes('action.type') - setupDefaults() { - const defaultProperties = schema.action.types[this.action.type]; - - Object.keys(defaultProperties).forEach(property => { - if (defaultProperties[property]) { - this.set(`action.${property}`, defaultProperties[property]); - } - }); - }, - @discourseComputed('wizard.steps') runAfterContent(steps) { let content = steps.map(function(step) { diff --git a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 index b0903ae0..3c54748a 100644 --- a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 @@ -19,17 +19,6 @@ export default Component.extend({ showMinLength: or('isText', 'isTextarea', 'isUrl', 'isComposer'), categoryPropertyTypes: selectKitContent(['id', 'slug']), - @observes('field.type') - setupDefaults() { - const defaultProperties = schema.field.types[this.field.type]; - - Object.keys(defaultProperties).forEach(property => { - if (defaultProperties[property]) { - this.set(`field.${property}`, defaultProperties[property]); - } - }); - }, - @observes('field.type') clearMapped() { schema.field.mapped.forEach(property => { diff --git a/assets/javascripts/discourse/components/wizard-links.js.es6 b/assets/javascripts/discourse/components/wizard-links.js.es6 index fedbc273..5f7be87b 100644 --- a/assets/javascripts/discourse/components/wizard-links.js.es6 +++ b/assets/javascripts/discourse/components/wizard-links.js.es6 @@ -18,11 +18,11 @@ export default Component.extend({ }, applySortable() { - $(this.element).find("ul").sortable({tolerance: 'pointer'}).on('sortupdate', (e, ui) => { - const itemId = ui.item.data('id'); - const index = ui.item.index(); - bind(this, this.updateItemOrder(itemId, index)); - }); + $(this.element).find("ul") + .sortable({ tolerance: 'pointer' }) + .on('sortupdate', (e, ui) => { + this.updateItemOrder(ui.item.data('id'), ui.item.index()); + }); }, updateItemOrder(itemId, newIndex) { @@ -51,7 +51,7 @@ export default Component.extend({ label = generateName(item.type); } - link.label = label; + link.label = `${label} (${item.id})`; let classes = 'btn'; if (current && item.id === current.id) { @@ -77,22 +77,30 @@ export default Component.extend({ actions: { add() { const items = this.items; - const itemType = this.itemType; + const itemType = this.itemType; + let next = 1; + if (items.length) { + next = Math.max.apply(Math, items.map((i) => (i.id.split('_')[1]))) + 1; + } + let params = { - id: `${itemType}_${items.length + 1}`, + id: `${itemType}_${next}`, isNew: true }; - if (schema[itemType].objectArrays) { - Object.keys(schema[itemType].objectArrays).forEach(objectType => { + let objectArrays = schema[itemType].objectArrays; + if (objectArrays) { + Object.keys(objectArrays).forEach(objectType => { params[objectArrays[objectType].property] = A(); }); }; params = this.setDefaults(schema[itemType].basic, params); - if (schema[itemType].types) { - params = this.setDefaults(schema[itemType].types[params.type], params); + + let types = schema[itemType].types; + if (types && params.type) { + params = this.setDefaults(types[params.type], params); } const newItem = EmberObject.create(params); @@ -107,8 +115,26 @@ export default Component.extend({ remove(itemId) { const items = this.items; - items.removeObject(items.findBy('id', itemId)); - this.set('current', items[items.length - 1]); + let item; + let index; + + items.forEach((it, ind) => { + if (it.id === itemId) { + item = it; + index = ind; + } + }); + + let nextIndex; + if (this.current.id === itemId) { + nextIndex = index < (items.length-2) ? (index+1) : (index-1); + } + + items.removeObject(item); + + if (nextIndex) { + this.set('current', items[nextIndex]); + } } } }); diff --git a/assets/javascripts/discourse/components/wizard-mapper-connector.js.es6 b/assets/javascripts/discourse/components/wizard-mapper-connector.js.es6 index ee6c38b2..aaf555b4 100644 --- a/assets/javascripts/discourse/components/wizard-mapper-connector.js.es6 +++ b/assets/javascripts/discourse/components/wizard-mapper-connector.js.es6 @@ -1,6 +1,7 @@ import Component from "@ember/component"; import { gt } from '@ember/object/computed'; import { computed } from "@ember/object"; +import { removeMapperClasses } from '../lib/wizard-mapper'; export default Component.extend({ classNameBindings: [':mapper-connector', ':mapper-block', 'hasMultiple::single'], @@ -9,5 +10,11 @@ export default Component.extend({ let key = this.connector; let path = this.inputTypes ? `input.${key}.name` : `connector.${key}`; return I18n.t(`admin.wizard.${path}`); - }) + }), + + actions: { + onOpen() { + removeMapperClasses(this); + } + } }); \ 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 bd8408b2..22ad9ef2 100644 --- a/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 +++ b/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 @@ -2,13 +2,13 @@ import { alias, or, gt } from "@ember/object/computed"; import { computed } from "@ember/object"; import { default as discourseComputed, observes, on } from "discourse-common/utils/decorators"; import { getOwner } from 'discourse-common/lib/get-owner'; -import { defaultSelectionType, selectionTypes } from '../lib/wizard-mapper'; +import { defaultSelectionType, selectionTypes, removeMapperClasses } from '../lib/wizard-mapper'; import { snakeCase } from '../lib/wizard'; import Component from "@ember/component"; import { bind } from "@ember/runloop"; export default Component.extend({ - classNames: 'mapper-selector', + classNameBindings: [':mapper-selector', 'activeType'], groups: alias('site.groups'), categories: alias('site.categories'), showText: computed('activeType', function() { return this.showInput('text') }), @@ -30,7 +30,6 @@ export default Component.extend({ userEnabled: computed('options.userSelection', 'inputType', function() { return this.optionEnabled('userSelection') }), listEnabled: computed('options.listSelection', 'inputType', function() { return this.optionEnabled('listSelection') }), hasTypes: gt('selectorTypes.length', 1), - showTypes: false, didInsertElement() { $(document).on("click", bind(this, this.documentClick)); @@ -41,14 +40,16 @@ export default Component.extend({ }, documentClick(e) { - let $element = $(this.element); + if (this._state == "destroying") return; + let $target = $(e.target); - - if (!$target.hasClass('type-selector-icon') && - $target.closest($element).length < 1 && - this._state !== "destroying") { - - this.set("showTypes", false); + + if (!$target.parents('.wizard-mapper .input').length) { + this.send('disableActive'); + } + + if (!$target.parents('.type-selector').length) { + this.send('hideTypes'); } }, @@ -147,14 +148,34 @@ export default Component.extend({ return this.activeType === type && this[`${type}Enabled`]; }, + removeClasses() { + removeMapperClasses(this); + }, + actions: { toggleType(type) { this.set('activeType', type); - this.set('showTypes', false); + this.send('hideTypes'); }, - toggleTypes() { - this.toggleProperty('showTypes') + // jquery is used here to ensure other selectors and types disable properly + + showTypes() { + this.removeClasses(); + $(this.element).find('.selector-types').addClass('show'); + }, + + hideTypes() { + $(this.element).find('.selector-types').removeClass('show'); + }, + + enableActive() { + this.removeClasses(); + $(this.element).addClass('active'); + }, + + disableActive() { + $(this.element).removeClass('active'); } } }) \ No newline at end of file diff --git a/assets/javascripts/discourse/controllers/admin-wizard.js.es6 b/assets/javascripts/discourse/controllers/admin-wizard.js.es6 index b5d4b3b6..42c3d534 100644 --- a/assets/javascripts/discourse/controllers/admin-wizard.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizard.js.es6 @@ -7,6 +7,7 @@ import { dasherize } from "@ember/string"; import EmberObject from "@ember/object"; import { scheduleOnce, later } from "@ember/runloop"; import Controller from "@ember/controller"; +import copyText from "discourse/lib/copy-text"; export default Controller.extend({ hasName: notEmpty('model.name'), @@ -58,7 +59,7 @@ export default Controller.extend({ let stepFields = s.fields.map((f) => { return EmberObject.create({ id: f.id, - label: `${f.label} (${s.id})`, + label: `${f.label} (${s.id}, ${f.id})`, type: f.type }); }); @@ -94,14 +95,16 @@ export default Controller.extend({ } }).catch((result) => { this.set('saving', false); - - let error = true; + + let errorType = 'failed'; + let errorParams = {}; + if (result.error) { - let type = result.error.type; - let params = result.error.params || {}; - error = I18n.t(`admin.wizard.error.${type}`, params); + errorType = result.error.type; + errorParams = result.error.params; } - this.set('error', error); + + this.set('error', I18n.t(`admin.wizard.error.${errorType}`, errorParams)); later(() => this.set('error', null), 10000); }); @@ -127,6 +130,20 @@ export default Controller.extend({ toggleAdvanced() { this.toggleProperty('model.showAdvanced'); + }, + + copyUrl() { + const $copyRange = $('

'); + $copyRange.html(this.wizardUrl); + + $(document.body).append($copyRange); + + if (copyText(this.wizardUrl, $copyRange[0])) { + this.set("copiedUrl", true); + later(() => this.set("copiedUrl", false), 2000); + } + + $copyRange.remove(); } } }); diff --git a/assets/javascripts/discourse/lib/wizard-json.js.es6 b/assets/javascripts/discourse/lib/wizard-json.js.es6 index 070fdc6e..3ef519b1 100644 --- a/assets/javascripts/discourse/lib/wizard-json.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-json.js.es6 @@ -134,7 +134,8 @@ function actionPatch(json) { function buildProperties(json) { let props = { - steps: A() + steps: A(), + actions: A() }; if (present(json)) { diff --git a/assets/javascripts/discourse/lib/wizard-mapper.js.es6 b/assets/javascripts/discourse/lib/wizard-mapper.js.es6 index 3d3ad034..533d852d 100644 --- a/assets/javascripts/discourse/lib/wizard-mapper.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-mapper.js.es6 @@ -22,6 +22,12 @@ function inputTypesContent(options = {}) { mapInputTypes(selectableInputTypes); } +function removeMapperClasses(ctx) { + const $mapper = $(ctx.element).parents('.wizard-mapper'); + $mapper.find('.selector-types').removeClass('show'); + $mapper.find('.mapper-selector').removeClass('active'); +} + // Connectors const connectors = { @@ -154,6 +160,7 @@ export { defaultInputType, defaultSelectionType, defaultConnector, + removeMapperClasses, connectorContent, inputTypesContent, selectionTypes, diff --git a/assets/javascripts/discourse/lib/wizard.js.es6 b/assets/javascripts/discourse/lib/wizard.js.es6 index 7734c450..2f252fbe 100644 --- a/assets/javascripts/discourse/lib/wizard.js.es6 +++ b/assets/javascripts/discourse/lib/wizard.js.es6 @@ -126,7 +126,7 @@ const fieldProperties = { description: null, required: null, key: null, - type: 'text' + type: null }, types: { text: { @@ -138,15 +138,15 @@ const fieldProperties = { composer: { min_length: null }, + text_only: { + }, number: { }, + checkbox: { + }, url: { min_length: null }, - 'text-only': { - }, - 'user-selector': { - }, upload: { file_types: '.jpg,.png' }, @@ -168,6 +168,8 @@ const fieldProperties = { group: { prefill: null, content: null + }, + user_selector: { } }, mapped: [ @@ -193,7 +195,7 @@ const actionProperties = { basic: { id: null, run_after: 'wizard_completion', - type: 'create_topic' + type: null }, types: { create_topic: { @@ -280,7 +282,7 @@ const schema = { function listProperties(type, objectType = null) { let properties = Object.keys(schema[type].basic); - + if (schema[type].types && objectType) { properties = properties.concat(Object.keys(schema[type].types[objectType])); } diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6 index 3b789676..38768eb6 100644 --- a/assets/javascripts/discourse/models/custom-wizard.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard.js.es6 @@ -44,7 +44,7 @@ const CustomWizard = EmberObject.extend({ for (let property of listProperties(type, objectType)) { let value = object.get(property); - result = this.validateValue(property, value, type, result); + result = this.validateValue(property, value, object, type, result); if (result.error) { break; @@ -90,7 +90,7 @@ const CustomWizard = EmberObject.extend({ return result; }, - validateValue(property, value, type, result) { + validateValue(property, value, object, type, result) { if (schema[type].required.indexOf(property) > -1 && !value) { result.error = { type: 'required', @@ -129,6 +129,10 @@ const CustomWizard = EmberObject.extend({ let input = { type: inpt.type, }; + + if (inpt.connector) { + input.connector = inpt.connector; + } if (present(inpt.output)) { input.output = inpt.output; diff --git a/assets/javascripts/discourse/templates/admin-wizard.hbs b/assets/javascripts/discourse/templates/admin-wizard.hbs index 7029b9e0..ce813f66 100644 --- a/assets/javascripts/discourse/templates/admin-wizard.hbs +++ b/assets/javascripts/discourse/templates/admin-wizard.hbs @@ -8,6 +8,11 @@
{{#if model.name}} {{wizardUrl}} + {{#if copiedUrl}} + {{d-button class="btn-hover pull-right" icon="copy" label="ip_lookup.copied"}} + {{else}} + {{d-button action=(action "copyUrl") class="pull-right no-text" icon="copy"}} + {{/if}} {{/if}}
diff --git a/assets/javascripts/discourse/templates/components/wizard-links.hbs b/assets/javascripts/discourse/templates/components/wizard-links.hbs index 7ef66b76..b24b5083 100644 --- a/assets/javascripts/discourse/templates/components/wizard-links.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-links.hbs @@ -1,12 +1,14 @@
{{{i18n header}}}
-{{#if anyLinks}} - -{{/if}} -{{d-button action='add' label='admin.wizard.add' icon='plus'}} + {{/if}} + {{d-button action='add' label='admin.wizard.add' icon='plus'}} + + diff --git a/assets/javascripts/discourse/templates/components/wizard-mapper-connector.hbs b/assets/javascripts/discourse/templates/components/wizard-mapper-connector.hbs index 9ac7ddfe..ea1a365d 100644 --- a/assets/javascripts/discourse/templates/components/wizard-mapper-connector.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-mapper-connector.hbs @@ -2,7 +2,8 @@ {{combo-box value=connector content=connectors - onChange=(action (mut connector))}} + onChange=(action (mut connector)) + onOpen=(action "onOpen")}} {{else}} {{connectorLabel}} diff --git a/assets/javascripts/discourse/templates/components/wizard-mapper-selector.hbs b/assets/javascripts/discourse/templates/components/wizard-mapper-selector.hbs index b0c0d8fd..dd4e06df 100644 --- a/assets/javascripts/discourse/templates/components/wizard-mapper-selector.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-mapper-selector.hbs @@ -1,19 +1,17 @@
{{#if hasTypes}} - + {{activeTypeLabel}} - {{#if showTypes}} -
- {{#each selectorTypes as |item|}} - {{wizard-mapper-selector-type - activeType=activeType - item=item - toggle=(action 'toggleType')}} - {{/each}} -
- {{/if}} +
+ {{#each selectorTypes as |item|}} + {{wizard-mapper-selector-type + activeType=activeType + item=item + toggle=(action 'toggleType')}} + {{/each}} +
{{else}} {{activeTypeLabel}} {{/if}} @@ -24,6 +22,7 @@ {{input type="text" value=value + click=(action 'enableActive') placeholder=(i18n placeholderKey)}} {{/if}} @@ -32,6 +31,8 @@ value=value content=comboBoxContent onChange=(action (mut value)) + onOpen=(action "enableActive") + onClick=(action 'enableActive') options=(hash none=placeholderKey )}} @@ -42,6 +43,8 @@ content=multiSelectContent value=value onChange=(action (mut value)) + onOpen=(action "enableActive") + onClose=(action "disableActive") options=multiSelectOptions}} {{/if}} @@ -55,6 +58,8 @@ {{tag-chooser tags=value filterable=true + onOpen=(action "enableActive") + onClose=(action "disableActive") options=(hash none=placeholderKey )}} @@ -65,6 +70,7 @@ includeMessageableGroups='true' placeholderKey=placeholderKey usernames=value - autocomplete="discourse"}} + autocomplete="discourse" + click=(action "enableActive")}} {{/if}}
\ No newline at end of file diff --git a/assets/javascripts/wizard/initializers/custom.js.es6 b/assets/javascripts/wizard/initializers/custom.js.es6 index 7b0ccf63..d879e14e 100644 --- a/assets/javascripts/wizard/initializers/custom.js.es6 +++ b/assets/javascripts/wizard/initializers/custom.js.es6 @@ -161,7 +161,7 @@ export default { const fields = {}; this.get('fields').forEach(f => { - if (f.type !== 'text-only') { + if (f.type !== 'text_only') { fields[f.id] = f.value; } }); @@ -218,8 +218,8 @@ export default { inputComponentName: function() { const type = this.get('field.type'); const id = this.get('field.id'); - if (['text-only'].includes(type)) return false; - return (type === 'component') ? dasherize(id) : `wizard-field-${type}`; + if (['text_only'].includes(type)) return false; + return dasherize((type === 'component') ? id : `wizard-field-${type}`); }.property('field.type', 'field.id') }); @@ -230,8 +230,8 @@ export default { 'dropdown', 'tag', 'image', - 'user-selector', - 'text-only', + 'user_selector', + 'text_only', 'composer', 'category', 'group' diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index 6353f790..75cddd97 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -2,18 +2,21 @@ @import 'wizard-transfer'; @import 'wizard-api'; -$setting-background: dark-light-diff($primary, $secondary, 96%, -65%); - body.admin-wizard { + .admin-container > .row { + display: flex; + } + .boxed.white { background-color: initial; } } .wizard-list { - float: left; width: 250px; + min-width: 250px; margin-top: 10px; + float: none; } .wizard-settings-parent { @@ -46,7 +49,7 @@ body.admin-wizard { .wizard-custom-field { position: relative; background: transparent; - background-color: $setting-background; + background-color: dark-light-diff($primary, $secondary, 96%, -65%); padding: 20px; } @@ -93,9 +96,11 @@ body.admin-wizard { font-size: 1.5em; min-height: 31px; margin-bottom: 30px; + display: flex; input { margin-bottom: 0; + width: 350px; } } @@ -113,11 +118,18 @@ body.admin-wizard { } .wizard-url { - display: inline-block; - font-size: 1rem; + display: inline-flex; margin-left: 20px; - background-color: $setting-background; - padding: 2px 6px; + + a { + padding: 6px 12px; + font-size: 1rem; + background-color: $primary-low; + } + + button { + font-size: 1rem; + } } } @@ -125,10 +137,6 @@ body.admin-wizard { margin-top: 20px; } -.content-list + .content { - overflow: hidden; -} - .admin-wizard.settings { margin-top: 10px; margin-left: 30px; @@ -358,16 +366,19 @@ body.admin-wizard { display: inline-block; width: 100%; - ul { + .link-list { margin: 0; padding: 0; list-style: none; - display: inline-block; + display: inline-flex; + align-items: flex-start; + flex-flow: wrap; - li { - display: inline-block; - margin-bottom: 7px; - margin-right: 7px; + > div { + display: flex; + align-items: center; + margin-bottom: 10px; + margin-right: 10px; } } diff --git a/assets/stylesheets/common/wizard-mapper.scss b/assets/stylesheets/common/wizard-mapper.scss index 3b67ac36..2903aeb6 100644 --- a/assets/stylesheets/common/wizard-mapper.scss +++ b/assets/stylesheets/common/wizard-mapper.scss @@ -100,13 +100,44 @@ } } +.mapper-input.assignment, +.mapper-input.validation, +.mapper-input.association { + .mapper-selector { + max-width: 250px; + min-width: 250px; + + > input, .select-kit, .ac-wrap, .autocomplete.ac-user { + width: 250px !important; + } + } +} + +.mapper-input.conditional { + .mapper-selector { + max-width: 170px; + min-width: 170px; + + &:not(.text).active .input { + width: 250px; + box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.2); + position: absolute; + z-index: 300; + + .select-kit, .ac-wrap, .autocomplete.ac-user, .select-kit-wrapper { + width: 250px !important; + } + } + } +} + .mapper-selector { width: 100%; - max-width: 150px; - min-width: 150px; + position: relative; - input, .select-kit, .ac-wrap, .autocomplete.ac-user { - width: 150px !important; + .input { + width: 100%; + transition: all 0.1s; } .type-selector { @@ -139,12 +170,16 @@ .selector-types { box-shadow: shadow('dropdown'); position: absolute; + display: none; background: $secondary; z-index: 200; padding: 5px 7px; - display: flex; flex-direction: column; border: 1px solid $primary-low; + + &.show { + display: flex; + } } .value-list .remove-value-btn { diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 0e139be0..1b62de49 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -95,6 +95,7 @@ en: list: "Enter item" error: + failed: "failed to save wizard" required: "{{type}} requires {{property}}" invalid: "{{property}} is invalid" dependent: "{{property}} is dependent on {{dependent}}" diff --git a/controllers/custom_wizard/admin.rb b/controllers/custom_wizard/admin.rb index d9ba5ba5..5cbdf737 100644 --- a/controllers/custom_wizard/admin.rb +++ b/controllers/custom_wizard/admin.rb @@ -18,16 +18,17 @@ class CustomWizard::AdminController < ::ApplicationController else wizard = result[:wizard] existing_wizard = result[:existing_wizard] + after_time = result[:after_time] ActiveRecord::Base.transaction do PluginStore.set('custom_wizard', wizard["id"], wizard) - if wizard['after_time'] && result[:new_after_time] + if after_time[:enabled] Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard['id']) - Jobs.enqueue_at(after_time_scheduled, :set_after_time_wizard, wizard_id: wizard['id']) + Jobs.enqueue_at(after_time[:scheduled], :set_after_time_wizard, wizard_id: wizard['id']) end - if existing_wizard && existing_wizard['after_time'] && !wizard['after_time'] + if existing_wizard && existing_wizard['after_time'] && !after_time[:enabled] Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard['id']) Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard['id']) end @@ -181,19 +182,24 @@ class CustomWizard::AdminController < ::ApplicationController def validate_after_time(wizard, existing_wizard) new = false error = nil + enabled = false + scheduled = nil if wizard["after_time"] + enabled = true + if !wizard["after_time_scheduled"] && !existing_wizard["after_time_scheduled"] error = 'after_time_need_time' else - after_time_scheduled = Time.parse(wizard["after_time_scheduled"]).utc - - new = existing_wizard['after_time_scheduled'] ? - after_time_scheduled != Time.parse(existing_wizard['after_time_scheduled']).utc : - true + scheduled = Time.parse(wizard["after_time_scheduled"]).utc + new = false + + if existing_wizard['after_time_scheduled'] + new = scheduled != Time.parse(existing_wizard['after_time_scheduled']).utc + end begin - error = 'after_time_invalid' if new && after_time_scheduled < Time.now.utc + error = 'after_time_invalid' if new && scheduled < Time.now.utc rescue ArgumentError error = 'after_time_invalid' end @@ -203,7 +209,11 @@ class CustomWizard::AdminController < ::ApplicationController if error { error: { type: error } } else - { new: new } + { + new: new, + scheduled: scheduled, + enabled: enabled + } end end @@ -214,19 +224,24 @@ class CustomWizard::AdminController < ::ApplicationController validation = validate_wizard(wizard) return validation if validation[:error] - after_time_validation = validate_after_time(wizard, existing_wizard) - return after_time_validation if after_time_validation[:error] + after_time = validate_after_time(wizard, existing_wizard) + return after_time if after_time[:error] wizard['steps'].each do |step| if step['raw_description'] step['description'] = PrettyText.cook(step['raw_description']) end end - - { + + result = { wizard: wizard, - existing_wizard: existing_wizard, - new_after_time: after_time_validation[:new] + existing_wizard: existing_wizard } + + if after_time[:enabled] + result[:after_time] = after_time + end + + result end end diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index a70fdad6..fa5d8c96 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -124,7 +124,7 @@ class CustomWizard::Builder if step_template['fields'] && step_template['fields'].length step_template['fields'].each do |field| - validate_field(field, updater, step_template) if field['type'] != 'text-only' + validate_field(field, updater, step_template) if field['type'] != 'text_only' end end diff --git a/lib/custom_wizard/field.rb b/lib/custom_wizard/field.rb index 7fe1f25d..e32f9f76 100644 --- a/lib/custom_wizard/field.rb +++ b/lib/custom_wizard/field.rb @@ -1,6 +1,6 @@ class CustomWizard::Field def self.types - @types ||= ['checkbox', 'composer', 'dropdown', 'tag', 'category', 'group', 'text', 'textarea', 'text-only', 'number', 'upload', 'user-selector', 'url'] + @types ||= ['text', 'textarea', 'composer', 'text_only', 'number', 'checkbox', 'url', 'upload', 'dropdown', 'tag', 'category', 'group', 'user_selector'] end def self.require_assets diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb new file mode 100644 index 00000000..6437c8c4 --- /dev/null +++ b/spec/components/custom_wizard/action_spec.rb @@ -0,0 +1,92 @@ +describe CustomWizard::Action do + let(:create_topic_action) {{"id":"create_topic","type":"create_topic","title":"text","post":"textarea"}} + let(:send_message_action) {{"id":"send_message","type":"send_message","title":"text","post":"textarea","username":"angus"}} + let(:route_to_action) {{"id":"route_to","type":"route_to","url":"https://google.com"}} + let(:open_composer_action) {{"id":"open_composer","type":"open_composer","title":"text","post":"textarea"}} + let(:add_to_group_action) {{"id":"add_to_group","type":"add_to_group","group_id":"dropdown_groups"}} + + it 'creates a topic' do + template['steps'][0]['fields'] = [text_field, textarea_field] + template['steps'][0]["actions"] = [create_topic_action] + updater = run_update(template, nil, + text: "Topic Title", + textarea: "topic body" + ) + topic = Topic.where(title: "Topic Title") + + expect(topic.exists?).to eq(true) + expect(Post.where( + topic_id: topic.pluck(:id), + raw: "topic body" + ).exists?).to eq(true) + end + + it 'sends a message' do + fields = [text_field, textarea_field] + + if extra_field + fields.push(extra_field) + end + + template['steps'][0]['fields'] = fields + template['steps'][0]["actions"] = [send_message_action.merge(extra_action_opts)] + + run_update(template, nil, + text: "Message Title", + textarea: "message body" + ) + + topic = Topic.where( + archetype: Archetype.private_message, + title: "Message Title" + ) + + expect(topic.exists?).to eq(true) + expect( + topic.first.topic_allowed_users.first.user.username + ).to eq('angus') + expect(Post.where( + topic_id: topic.pluck(:id), + raw: "message body" + ).exists?).to eq(true) + end + + it 'updates a profile' do + run_update(template, template['steps'][1]['id'], name: "Sally") + expect(user.name).to eq('Sally') + end + + it 'opens a composer' do + template['steps'][0]['fields'] = [text_field, textarea_field] + template['steps'][0]["actions"] = [open_composer_action] + + updater = run_update(template, nil, + text: "Topic Title", + textarea: "topic body" + ) + + expect(updater.result.blank?).to eq(true) + + updater = run_update(template, template['steps'][1]['id']) + + expect(updater.result[:redirect_on_complete]).to eq( + "/new-topic?title=Topic%20Title&body=topic%20body" + ) + end + + it 'adds a user to a group' do + template['steps'][0]['fields'] = [dropdown_groups_field] + template['steps'][0]["actions"] = [add_to_group_action] + + updater = run_update(template, nil, dropdown_groups: group.id) + expect(group.users.first.username).to eq('angus') + end + + it 're-routes a user' do + template['steps'][0]["actions"] = [route_to_action] + updater = run_update(template, nil, {}) + expect(updater.result[:redirect_on_next]).to eq( + "https://google.com" + ) + end +end diff --git a/spec/components/custom_wizard/builder_spec.rb b/spec/components/custom_wizard/builder_spec.rb index 4bd31127..cbba78ae 100644 --- a/spec/components/custom_wizard/builder_spec.rb +++ b/spec/components/custom_wizard/builder_spec.rb @@ -15,27 +15,6 @@ describe CustomWizard::Builder do ).read) end - let(:permitted_params) {[{"key":"param_key","value":"submission_param_key"}]} - let(:required_data) {[{"key":"nickname","connector":"equals","value":"name"}]} - let(:required_data_message) {"Nickname is required to match your name"} - - let(:checkbox_field) {{"id":"checkbox","type":"checkbox","label":"Checkbox"}} - let(:composer_field) {{"id": "composer","label":"Composer","type":"composer"}} - let(:tag_field) {{"id": "tag","type": "tag","label": "Tag","limit": "2"}} - let(:category_field) {{"id": "category","type": "category","limit": "1","label": "Category"}} - let(:image_field) {{"id": "image","type": "image","label": "Image"}} - let(:text_field) {{"id": "text","type": "text","label": "Text"}} - let(:textarea_field) {{"id": "textarea","type": "textarea","label": "Textarea"}} - let(:text_only_field) {{"id": "text_only","type": "text-only","label": "Text only"}} - let(:upload_field) {{"id": "upload","type": "upload","file_types": ".jpg,.png,.pdf","label": "Upload"}} - let(:user_selector_field) {{"id": "user_selector","type": "user-selector","label": "User selector"}} - - let(:create_topic_action) {{"id":"create_topic","type":"create_topic","title":"text","post":"textarea"}} - let(:send_message_action) {{"id":"send_message","type":"send_message","title":"text","post":"textarea","username":"angus"}} - let(:route_to_action) {{"id":"route_to","type":"route_to","url":"https://google.com"}} - let(:open_composer_action) {{"id":"open_composer","type":"open_composer","title":"text","post":"textarea"}} - let(:add_to_group_action) {{"id":"add_to_group","type":"add_to_group","group_id":"dropdown_groups"}} - def build_wizard(t = template, u = user, build_opts = {}, params = {}) CustomWizard::Wizard.add_wizard(t) CustomWizard::Builder.new(u, 'welcome').build(build_opts, params) @@ -59,22 +38,6 @@ describe CustomWizard::Builder do updater end - def send_message(extra_field = nil, extra_action_opts = {}) - fields = [text_field, textarea_field] - - if extra_field - fields.push(extra_field) - end - - template['steps'][0]['fields'] = fields - template['steps'][0]["actions"] = [send_message_action.merge(extra_action_opts)] - - run_update(template, nil, - text: "Message Title", - textarea: "message body" - ) - end - context 'disabled' do before do SiteSetting.custom_wizard_enabled = false @@ -101,7 +64,7 @@ describe CustomWizard::Builder do expect(build_wizard.steps.length).to eq(2) end - it 'returns no steps if the multiple submissions are disabled and user has completed it' do + it 'returns no steps if multiple submissions are disabled and user has completed' do history_params = { action: UserHistory.actions[:custom_wizard_step], acting_user_id: user.id, @@ -114,12 +77,12 @@ describe CustomWizard::Builder do expect(build_wizard(template).steps.length).to eq(0) end - it 'returns no steps if has min trust and user does not meet it' do + it 'returns no steps if user is not permitted' do template["min_trust"] = 3 expect(build_wizard(template).steps.length).to eq(0) end - it 'returns steps if it has min trust and user meets it' do + it 'returns steps if user is permitted' do template["min_trust"] = 3 expect(build_wizard(template, trusted_user).steps.length).to eq(2) end @@ -156,12 +119,6 @@ describe CustomWizard::Builder do expect(build_wizard(template, user).steps[0].permitted).to eq(false) end - it "is not permitted if required data is not present" do - template['steps'][0]['required_data'] = required_data - add_submission_data(nickname: "John") - expect(build_wizard(template, user).steps[0].permitted).to eq(false) - end - it 'it shows required data message if required data has message' do template['steps'][0]['required_data'] = required_data template['steps'][0]['required_data_message'] = required_data_message @@ -217,151 +174,9 @@ describe CustomWizard::Builder do end end - context 'actions' do - it 'runs actions attached to a step' do - run_update(template, template['steps'][1]['id'], name: "Gus") - expect(user.name).to eq('Gus') - end - - it 'interpolates user data correctly' do - user.name = "Angus" - user.save! - - expect( - CustomWizard::Builder.fill_placeholders( - "My name is u{name}", - user, - {} - ) - ).to eq('My name is Angus') - end - - it 'creates a topic' do - template['steps'][0]['fields'] = [text_field, textarea_field] - template['steps'][0]["actions"] = [create_topic_action] - updater = run_update(template, nil, - text: "Topic Title", - textarea: "topic body" - ) - topic = Topic.where(title: "Topic Title") - - expect(topic.exists?).to eq(true) - expect(Post.where( - topic_id: topic.pluck(:id), - raw: "topic body" - ).exists?).to eq(true) - end - - it 'creates a topic with a custom title' do - user.name = "Angus" - user.save! - - template['steps'][0]['fields'] = [text_field, textarea_field] - - create_topic_action['custom_title_enabled'] = true - create_topic_action['custom_title'] = "u{name}' Topic Title" - template['steps'][0]["actions"] = [create_topic_action] - - run_update(template, nil, textarea: "topic body") - - topic = Topic.where(title: "Angus' Topic Title") - - expect(topic.exists?).to eq(true) - expect(Post.where( - topic_id: topic.pluck(:id), - raw: "topic body" - ).exists?).to eq(true) - end - - it 'creates a topic with a custom post' do - user.name = "Angus" - user.save! - - template['steps'][0]['fields'] = [text_field, textarea_field] - - create_topic_action['post_builder'] = true - create_topic_action['post_template'] = "u{name}' w{textarea}" - template['steps'][0]["actions"] = [create_topic_action] - - run_update(template, nil, - text: "Topic Title", - textarea: "topic body" - ) - - topic = Topic.where(title: "Topic Title") - - expect(topic.exists?).to eq(true) - expect(Post.where( - topic_id: topic.pluck(:id), - raw: "Angus' topic body" - ).exists?).to eq(true) - end - - it 'sends a message' do - send_message - - topic = Topic.where( - archetype: Archetype.private_message, - title: "Message Title" - ) - - expect(topic.exists?).to eq(true) - expect( - topic.first.topic_allowed_users.first.user.username - ).to eq('angus') - expect(Post.where( - topic_id: topic.pluck(:id), - raw: "message body" - ).exists?).to eq(true) - end - - it 'doesnt sent a message if the required data is not present' do - send_message(user_selector_field, required: "user_selector") - topic = Topic.where( - archetype: Archetype.private_message, - title: "Message Title" - ) - expect(topic.exists?).to eq(false) - end - - it 'updates a profile' do - run_update(template, template['steps'][1]['id'], name: "Sally") - expect(user.name).to eq('Sally') - end - - it 'opens a composer' do - template['steps'][0]['fields'] = [text_field, textarea_field] - template['steps'][0]["actions"] = [open_composer_action] - - updater = run_update(template, nil, - text: "Topic Title", - textarea: "topic body" - ) - - expect(updater.result.blank?).to eq(true) - - updater = run_update(template, template['steps'][1]['id']) - - expect(updater.result[:redirect_on_complete]).to eq( - "/new-topic?title=Topic%20Title&body=topic%20body" - ) - end - - it 'adds a user to a group' do - template['steps'][0]['fields'] = [dropdown_groups_field] - template['steps'][0]["actions"] = [add_to_group_action] - - updater = run_update(template, nil, dropdown_groups: group.id) - expect(group.users.first.username).to eq('angus') - end - - it 're-routes a user' do - template['steps'][0]["actions"] = [route_to_action] - updater = run_update(template, nil, {}) - expect(updater.result[:redirect_on_next]).to eq( - "https://google.com" - ) - end + it 'runs actions attached to a step' do + run_update(template, template['steps'][1]['id'], name: "Gus") + expect(user.name).to eq('Gus') end end end diff --git a/spec/components/custom_wizard/mapper_spec.rb b/spec/components/custom_wizard/mapper_spec.rb new file mode 100644 index 00000000..4fb8a10e --- /dev/null +++ b/spec/components/custom_wizard/mapper_spec.rb @@ -0,0 +1,14 @@ +describe CustomWizard::Mapper do + +it 'interpolates user data' do + user.name = "Angus" + user.save! + + expect( + CustomWizard::Builder.fill_placeholders( + "My name is u{name}", + user, + {} + ) + ).to eq('My name is Angus') +end \ No newline at end of file