0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-09-19 23:31:11 +02:00
Dieser Commit ist enthalten in:
Angus McLeod 2020-04-11 16:22:12 +10:00
Ursprung ae87e383d2
Commit d8fd5cb258
24 geänderte Dateien mit 384 neuen und 324 gelöschten Zeilen

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -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 = $('<p id="copy-range"></p>');
$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();
}
}
});

Datei anzeigen

@ -134,7 +134,8 @@ function actionPatch(json) {
function buildProperties(json) {
let props = {
steps: A()
steps: A(),
actions: A()
};
if (present(json)) {

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -8,6 +8,11 @@
<div class="wizard-url">
{{#if model.name}}
<a href="{{wizardUrl}}" target="_blank">{{wizardUrl}}</a>
{{#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}}
</div>
</div>

Datei anzeigen

@ -1,12 +1,14 @@
<div class="wizard-header medium">{{{i18n header}}}</div>
{{#if anyLinks}}
<ul>
<div class="link-list">
{{#if anyLinks}}
{{#each links as |l|}}
<li data-id='{{l.id}}'>
<div data-id='{{l.id}}'>
{{d-button action="change" actionParam=l.id translatedLabel=l.label class=l.classes}}
{{d-button action='remove' actionParam=l.id icon='times' class='remove'}}
</li>
</div>
{{/each}}
</ul>
{{/if}}
{{d-button action='add' label='admin.wizard.add' icon='plus'}}
{{/if}}
{{d-button action='add' label='admin.wizard.add' icon='plus'}}
</div>

Datei anzeigen

@ -2,7 +2,8 @@
{{combo-box
value=connector
content=connectors
onChange=(action (mut connector))}}
onChange=(action (mut connector))
onOpen=(action "onOpen")}}
{{else}}
<span class="connector-single">
{{connectorLabel}}

Datei anzeigen

@ -1,19 +1,17 @@
<div class="type-selector">
{{#if hasTypes}}
<a {{action "toggleTypes"}} class="active">
<a {{action "showTypes"}} class="active">
{{activeTypeLabel}}
</a>
{{#if showTypes}}
<div class="selector-types">
{{#each selectorTypes as |item|}}
{{wizard-mapper-selector-type
activeType=activeType
item=item
toggle=(action 'toggleType')}}
{{/each}}
</div>
{{/if}}
<div class="selector-types">
{{#each selectorTypes as |item|}}
{{wizard-mapper-selector-type
activeType=activeType
item=item
toggle=(action 'toggleType')}}
{{/each}}
</div>
{{else}}
<span>{{activeTypeLabel}}</span>
{{/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}}
</div>

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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