Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-22 09:20:29 +01:00
various
Dieser Commit ist enthalten in:
Ursprung
5a6afef005
Commit
98f9215d65
14 geänderte Dateien mit 398 neuen und 387 gelöschten Zeilen
|
@ -1,66 +1,43 @@
|
|||
import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators';
|
||||
import { equal, not, empty, or } from "@ember/object/computed";
|
||||
import { actionTypes, generateName, selectKitContent, profileFields } from '../lib/wizard';
|
||||
import { default as discourseComputed, observes } from 'discourse-common/utils/decorators';
|
||||
import { equal, empty, or } from "@ember/object/computed";
|
||||
import { actionTypes, generateName, selectKitContent } from '../lib/wizard';
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: 'wizard-custom-action',
|
||||
types: actionTypes.map(t => ({ id: t, name: generateName(t) })),
|
||||
actionTypes: actionTypes.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'),
|
||||
openComposer: equal('action.type', 'open_composer'),
|
||||
sendToApi: equal('action.type', 'send_to_api'),
|
||||
apiEmpty: empty('action.api'),
|
||||
addToGroup: equal('action.type', 'add_to_group'),
|
||||
routeTo: equal('action.type', 'route_to'),
|
||||
disableId: not('action.isNew'),
|
||||
apiEmpty: empty('action.api'),
|
||||
groupPropertyTypes: selectKitContent(['id', 'name']),
|
||||
hasAdvanced: or('hasCustomFields', 'routeTo'),
|
||||
hasCustomFields: or('basicTopicFields', 'updateProfile'),
|
||||
basicTopicFields: or('createTopic', 'sendMessage', 'openComposer'),
|
||||
publicTopicFields: or('createTopic', 'openComposer'),
|
||||
showSkipRedirect: or('createTopic', 'sendMessage'),
|
||||
|
||||
@on('didInsertElement')
|
||||
@observes('action.type')
|
||||
setLabel() {
|
||||
setupDefaults() {
|
||||
if (this.action.type) {
|
||||
this.set('action.label', generateName(this.action.type));
|
||||
};
|
||||
},
|
||||
|
||||
@discourseComputed('action.type')
|
||||
basicTopicFields(actionType) {
|
||||
return ['create_topic', 'send_message', 'open_composer'].indexOf(actionType) > -1;
|
||||
},
|
||||
|
||||
@discourseComputed('action.type')
|
||||
publicTopicFields(actionType) {
|
||||
return ['create_topic', 'open_composer'].indexOf(actionType) > -1;
|
||||
},
|
||||
|
||||
@discourseComputed('action.type')
|
||||
newTopicFields(actionType) {
|
||||
return ['create_topic', 'send_message'].indexOf(actionType) > -1;
|
||||
},
|
||||
|
||||
@discourseComputed('wizardFields')
|
||||
categoryFields(fields) {
|
||||
return fields.filter(f => f.type == 'category');
|
||||
},
|
||||
|
||||
@discourseComputed('wizardFields')
|
||||
tagFields(fields) {
|
||||
return fields.filter(f => f.type == 'tag');
|
||||
},
|
||||
|
||||
@observes('action.custom_category_wizard_field')
|
||||
toggleCustomCategoryUserField() {
|
||||
if (this.action.custom_category_wizard_field)
|
||||
this.set('action.custom_category_user_field', false);
|
||||
},
|
||||
|
||||
@observes('action.custom_category_user_field')
|
||||
toggleCustomCategoryWizardField() {
|
||||
if (this.action.custom_category_user_field)
|
||||
this.set('action.custom_category_wizard_field', false);
|
||||
@discourseComputed('wizard.steps')
|
||||
runAfterContent(steps) {
|
||||
let content = steps.map(s => ({ id: s.id, name: s.label }));
|
||||
|
||||
content.unshift({
|
||||
id: 'wizard_completion',
|
||||
name: I18n.t('admin.wizard.action.run_after.wizard_completion')
|
||||
});
|
||||
|
||||
return content;
|
||||
},
|
||||
|
||||
@discourseComputed('wizard.apis')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators';
|
||||
import { equal, not, or } from "@ember/object/computed";
|
||||
import { default as discourseComputed, observes } from 'discourse-common/utils/decorators';
|
||||
import { equal, or } from "@ember/object/computed";
|
||||
import { selectKitContent } from '../lib/wizard';
|
||||
import Component from "@ember/component";
|
||||
|
||||
|
@ -10,23 +10,30 @@ export default Component.extend({
|
|||
isCategory: equal('field.type', 'category'),
|
||||
isGroup: equal('field.type', 'group'),
|
||||
isTag: equal('field.type', 'tag'),
|
||||
disableId: not('field.isNew'),
|
||||
isText: equal('field.type', 'text'),
|
||||
isTextarea: equal('field.type', 'textarea'),
|
||||
isUrl: equal('field.type', 'url'),
|
||||
showPrefill: or('isCategory', 'isTag', 'isGroup', 'isDropdown'),
|
||||
showContent: or('isCategory', 'isTag', 'isGroup', 'isDropdown'),
|
||||
showLimit: or('isCategory', 'isTag'),
|
||||
showMinLength: or('isText', 'isTextarea', 'isUrl'),
|
||||
categoryPropertyTypes: selectKitContent(['id', 'slug']),
|
||||
prefillEnabled: or('isCategory', 'isTag', 'isGroup', 'isDropdown'),
|
||||
contentEnabled: or('isCategory', 'isTag', 'isGroup', 'isDropdown'),
|
||||
|
||||
@discourseComputed('field.type')
|
||||
isInput: (type) => type === 'text' || type === 'textarea' || type === 'url',
|
||||
|
||||
@discourseComputed('field.type')
|
||||
isCategoryOrTag: (type) => type === 'tag' || type === 'category',
|
||||
|
||||
@on('didInsertElement')
|
||||
@observes('isUpload')
|
||||
setupFileType() {
|
||||
|
||||
@observes('isUpload', 'isCategory')
|
||||
setupDefaults() {
|
||||
if (this.isUpload && !this.field.file_types) {
|
||||
this.set('field.file_types', '.jpg,.png');
|
||||
}
|
||||
|
||||
if (this.isCategory && !this.field.property) {
|
||||
this.set('field.property', 'id');
|
||||
}
|
||||
},
|
||||
|
||||
@observes('field.type')
|
||||
clearMappedProperties() {
|
||||
this.set('field.content', null);
|
||||
this.set('field.prefill', null);
|
||||
},
|
||||
|
||||
setupTypeOutput(fieldType, options) {
|
||||
|
@ -80,19 +87,6 @@ export default Component.extend({
|
|||
return this.setupTypeOutput(fieldType, options);
|
||||
},
|
||||
|
||||
@observes('field.type')
|
||||
clearInputs() {
|
||||
this.set('field.content', null);
|
||||
this.set('field.prefill', null);
|
||||
},
|
||||
|
||||
@observes('isCategory')
|
||||
setupCategoryType() {
|
||||
if (this.isCategory && !this.field.property) {
|
||||
this.set('field.property', 'id');
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
imageUploadDone(upload) {
|
||||
this.set("field.image", upload.url);
|
||||
|
|
|
@ -1,34 +1,7 @@
|
|||
import { observes, on, default as discourseComputed } from 'discourse-common/utils/decorators';
|
||||
import { not } from "@ember/object/computed";
|
||||
import EmberObject from "@ember/object";
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: 'wizard-custom-step',
|
||||
disableId: not('step.isNew'),
|
||||
|
||||
@discourseComputed('wizardFields', 'wizard.steps')
|
||||
requiredContent(wizardFields, steps) {
|
||||
let content = wizardFields;
|
||||
let actions = [];
|
||||
|
||||
steps.forEach(s => {
|
||||
actions.push(...s.actions);
|
||||
});
|
||||
|
||||
actions.forEach(a => {
|
||||
if (a.type === 'route_to' && a.code) {
|
||||
content.push(
|
||||
EmberObject.create({
|
||||
id: a.code,
|
||||
label: "code (Route To)"
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return content;
|
||||
},
|
||||
|
||||
actions: {
|
||||
bannerUploadDone(upload) {
|
||||
|
|
|
@ -99,7 +99,7 @@ export default Controller.extend({
|
|||
}
|
||||
}).catch((result) => {
|
||||
this.set('saving', false);
|
||||
this.set('error', I18n.t(`admin.wizard.error.${result.error}`));
|
||||
this.set('error', I18n.t(`admin.wizard.error.${result.error}`, result.errorParams || {}));
|
||||
later(() => this.set('error', null), 10000);
|
||||
});
|
||||
},
|
||||
|
|
|
@ -19,132 +19,6 @@ function mapped(property, type) {
|
|||
mappedProperties[type].indexOf(property) > -1;
|
||||
}
|
||||
|
||||
function buildJson(object, type) {
|
||||
let result = {};
|
||||
|
||||
properties[type].forEach((p) => {
|
||||
let value = object.get(p);
|
||||
|
||||
if (mapped(p, type)) {
|
||||
value = buildMappedJson(value);
|
||||
}
|
||||
|
||||
if (value) {
|
||||
result[p] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function buildMappedJson(inputs) {
|
||||
if (!inputs || !inputs.length) return false;
|
||||
|
||||
let result = [];
|
||||
|
||||
inputs.forEach(inpt => {
|
||||
let input = {
|
||||
type: inpt.type,
|
||||
};
|
||||
|
||||
if (present(inpt.output)) {
|
||||
input.output = inpt.output;
|
||||
input.output_type = snakeCase(inpt.output_type);
|
||||
input.output_connector = inpt.output_connector;
|
||||
}
|
||||
|
||||
if (present(inpt.pairs)) {
|
||||
input.pairs = [];
|
||||
|
||||
inpt.pairs.forEach(pr => {
|
||||
if (present(pr.key) && present(pr.value)) {
|
||||
|
||||
let pairParams = {
|
||||
index: pr.index,
|
||||
key: pr.key,
|
||||
key_type: snakeCase(pr.key_type),
|
||||
value: pr.value,
|
||||
value_type: snakeCase(pr.value_type),
|
||||
connector: pr.connector
|
||||
}
|
||||
|
||||
input.pairs.push(pairParams);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ((input.type === 'assignment' && present(input.output)) ||
|
||||
present(input.pairs)) {
|
||||
|
||||
result.push(input);
|
||||
}
|
||||
});
|
||||
|
||||
if (!result.length) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function buildStepJson(object) {
|
||||
let steps = [];
|
||||
let error = null;
|
||||
|
||||
object.some((s) => {
|
||||
let step = buildJson(s, 'step');
|
||||
let fields = s.fields;
|
||||
|
||||
if (fields.length) {
|
||||
step.fields = [];
|
||||
|
||||
fields.some((f) => {
|
||||
if (!f.type) {
|
||||
error = 'type_required';
|
||||
return;
|
||||
}
|
||||
|
||||
step.fields.push(
|
||||
buildJson(f, 'field')
|
||||
);
|
||||
});
|
||||
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
let actions = s.actions;
|
||||
|
||||
if (actions.length) {
|
||||
step.actions = [];
|
||||
|
||||
actions.some((a) => {
|
||||
if (a.api_body) {
|
||||
try {
|
||||
JSON.parse(a.api_body);
|
||||
} catch (e) {
|
||||
error = 'invalid_api_body';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
step.actions.push(
|
||||
buildJson(a, 'action')
|
||||
);
|
||||
});
|
||||
|
||||
if (error) return;
|
||||
}
|
||||
|
||||
steps.push(step);
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return { error };
|
||||
} else {
|
||||
return { steps };
|
||||
};
|
||||
}
|
||||
|
||||
function castCase(property, value) {
|
||||
return property.indexOf('_type') > -1 ? camelCase(value) : value;
|
||||
}
|
||||
|
@ -223,9 +97,9 @@ function objectHasAdvanced(params, type) {
|
|||
}
|
||||
|
||||
function buildProperties(json) {
|
||||
let steps = A();
|
||||
let props = {
|
||||
steps
|
||||
steps: A();
|
||||
action: A();
|
||||
};
|
||||
|
||||
if (present(json)) {
|
||||
|
@ -268,25 +142,23 @@ function buildProperties(json) {
|
|||
});
|
||||
}
|
||||
|
||||
stepParams.actions = A();
|
||||
|
||||
if (present(stepJson.actions)) {
|
||||
stepJson.actions.forEach((a) => {
|
||||
let params = buildObject(a, 'action');
|
||||
|
||||
if (objectHasAdvanced(params, 'action')) {
|
||||
params.showAdvanced = true;
|
||||
}
|
||||
|
||||
stepParams.actions.pushObject(params);
|
||||
});
|
||||
}
|
||||
|
||||
steps.pushObject(
|
||||
EmberObject.create(stepParams)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
if (present(json.actions)) {
|
||||
json.actions.forEach((a) => {
|
||||
let params = buildObject(a, 'action');
|
||||
|
||||
if (objectHasAdvanced(params, 'action')) {
|
||||
params.showAdvanced = true;
|
||||
}
|
||||
|
||||
props.actions.pushObject(params);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
props.id = '';
|
||||
props.name = '';
|
||||
|
@ -299,7 +171,6 @@ function buildProperties(json) {
|
|||
props.prompt_completion = false;
|
||||
props.restart_on_revisit = false;
|
||||
props.permitted = null;
|
||||
props.steps = A();
|
||||
}
|
||||
|
||||
return props;
|
||||
|
|
|
@ -58,7 +58,9 @@ const wizardProperties = [
|
|||
'prompt_completion',
|
||||
'restart_on_revisit',
|
||||
'theme_id',
|
||||
'permitted'
|
||||
'permitted',
|
||||
'steps',
|
||||
'actions'
|
||||
];
|
||||
|
||||
const stepProperties = [
|
||||
|
@ -69,7 +71,8 @@ const stepProperties = [
|
|||
'raw_description',
|
||||
'required_data',
|
||||
'required_data_message',
|
||||
'permitted_params'
|
||||
'permitted_params',
|
||||
'fields'
|
||||
]
|
||||
|
||||
const fieldProperties = [
|
||||
|
@ -91,6 +94,7 @@ const fieldProperties = [
|
|||
const actionProperties = [
|
||||
'id',
|
||||
'type',
|
||||
'run_after',
|
||||
'title',
|
||||
'post',
|
||||
'post_builder',
|
||||
|
@ -117,6 +121,12 @@ const properties = {
|
|||
action: actionProperties
|
||||
}
|
||||
|
||||
const objectArrays = [
|
||||
'steps',
|
||||
'fields',
|
||||
'actions'
|
||||
];
|
||||
|
||||
const mappedProperties = {
|
||||
wizard: [
|
||||
'permitted'
|
||||
|
@ -202,6 +212,7 @@ export {
|
|||
camelCase,
|
||||
snakeCase,
|
||||
properties,
|
||||
objectArrays,
|
||||
wizardProperties,
|
||||
mappedProperties,
|
||||
profileFields,
|
||||
|
|
|
@ -1,32 +1,20 @@
|
|||
import { ajax } from 'discourse/lib/ajax';
|
||||
import EmberObject from "@ember/object";
|
||||
import { buildStepJson, buildJson, buildProperties } from '../lib/wizard-json';
|
||||
import { buildJson, buildProperties, present } from '../lib/wizard-json';
|
||||
import { properties, arrays, camelCase, snakeCase } from '../lib/wizard';
|
||||
import { Promise } from "rsvp";
|
||||
|
||||
const jsonStrings = ['api_body'];
|
||||
const required = ['id', 'steps', 'type'];
|
||||
const dependent = { after_time: 'after_time_scheduled' }
|
||||
|
||||
const CustomWizard = EmberObject.extend({
|
||||
save() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let wizardJson = buildJson(this, 'wizard');
|
||||
let json = this.buildJson(this, 'wizard');
|
||||
|
||||
if (wizardJson.after_time && !wizardJson.after_time_scheduled) {
|
||||
reject({
|
||||
error: 'after_time_need_time'
|
||||
});
|
||||
};
|
||||
|
||||
if (this.steps.length > 0) {
|
||||
let stepsResult = buildStepJson(this.steps);
|
||||
|
||||
if (stepsResult.error ||
|
||||
!stepsResult.steps ||
|
||||
stepsResult.steps.length < 1) {
|
||||
|
||||
reject({
|
||||
error: stepsResult.error || 'steps_required'
|
||||
});
|
||||
} else {
|
||||
wizardJson.steps = stepsResult.steps;
|
||||
}
|
||||
if (json.error) {
|
||||
reject({ eror: json.error });
|
||||
}
|
||||
|
||||
ajax("/admin/wizards/custom/save", {
|
||||
|
@ -44,6 +32,111 @@ const CustomWizard = EmberObject.extend({
|
|||
});
|
||||
},
|
||||
|
||||
buildJson(object, type, result = {}) {
|
||||
for (let property of properties[type]) {
|
||||
let value = object.get(property);
|
||||
|
||||
if (objectArrays[type]) {
|
||||
result[property] = [];
|
||||
|
||||
for (let obj of value) {
|
||||
let obj = this.buildJson(value, property, result);
|
||||
|
||||
if (obj.error) {
|
||||
result.error = r.error;
|
||||
break;
|
||||
} else {
|
||||
result[property].push(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (required[property] && !value) {
|
||||
result.error = 'required'
|
||||
result.errorParams = { type, property };
|
||||
}
|
||||
|
||||
if (dependent[property] && !properties[type][dependent[property]]) {
|
||||
result.error = 'dependent';
|
||||
result.errorParams = {
|
||||
dependentProperty: properties[type][dependent[property]],
|
||||
property
|
||||
}
|
||||
}
|
||||
|
||||
if (jsonStrings[property]) {
|
||||
try {
|
||||
value = JSON.parse(value);
|
||||
} catch (e) {
|
||||
result.error = 'invalid';
|
||||
result.errorParams = { property };
|
||||
}
|
||||
}
|
||||
|
||||
if (mapped(property, type)) {
|
||||
value = this.buildMappedJson(value);
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
break;
|
||||
} else if (value) {
|
||||
result[property] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
buildMappedJson(inputs) {
|
||||
if (!inputs || !inputs.length) return false;
|
||||
|
||||
let result = [];
|
||||
|
||||
inputs.forEach(inpt => {
|
||||
let input = {
|
||||
type: inpt.type,
|
||||
};
|
||||
|
||||
if (present(inpt.output)) {
|
||||
input.output = inpt.output;
|
||||
input.output_type = snakeCase(inpt.output_type);
|
||||
input.output_connector = inpt.output_connector;
|
||||
}
|
||||
|
||||
if (present(inpt.pairs)) {
|
||||
input.pairs = [];
|
||||
|
||||
inpt.pairs.forEach(pr => {
|
||||
if (present(pr.key) && present(pr.value)) {
|
||||
|
||||
let pairParams = {
|
||||
index: pr.index,
|
||||
key: pr.key,
|
||||
key_type: snakeCase(pr.key_type),
|
||||
value: pr.value,
|
||||
value_type: snakeCase(pr.value_type),
|
||||
connector: pr.connector
|
||||
}
|
||||
|
||||
input.pairs.push(pairParams);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ((input.type === 'assignment' && present(input.output)) ||
|
||||
present(input.pairs)) {
|
||||
|
||||
result.push(input);
|
||||
}
|
||||
});
|
||||
|
||||
if (!result.length) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
remove() {
|
||||
return ajax("/admin/wizards/custom/remove", {
|
||||
type: 'DELETE',
|
||||
|
|
|
@ -157,7 +157,16 @@
|
|||
step=currentStep
|
||||
wizard=model
|
||||
currentField=currentField
|
||||
currentAction=currentAction
|
||||
wizardFields=wizardFields}}
|
||||
{{/if}}
|
||||
|
||||
{{wizard-links type="action" current=currentAction items=model.actions}}
|
||||
|
||||
{{#if currentAction}}
|
||||
{{wizard-custom-action
|
||||
action=currentAction
|
||||
wizard=model
|
||||
removeAction="removeAction"
|
||||
wizardFields=wizardFields}}
|
||||
{{/if}}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<div class="setting-value">
|
||||
{{combo-box
|
||||
value=action.type
|
||||
content=types
|
||||
content=actionTypes
|
||||
onChange=(action (mut action.type))
|
||||
options=(hash
|
||||
none="admin.wizard.field.type"
|
||||
|
@ -14,6 +14,19 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting">
|
||||
<div class="setting-label">
|
||||
<label>{{i18n "admin.wizard.run_after"}}</label>
|
||||
</div>
|
||||
|
||||
<div class="setting-value">
|
||||
{{combo-box
|
||||
value=action.run_after
|
||||
content=runAfterContent
|
||||
onChange=(action (mut action.run_after))}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if basicTopicFields}}
|
||||
<div class="setting full field-mapper-setting">
|
||||
<div class="setting-label">
|
||||
|
@ -99,6 +112,7 @@
|
|||
inputs=action.tags
|
||||
options=(hash
|
||||
tagSelection='output'
|
||||
listSelection='output'
|
||||
wizardFieldSelection=true
|
||||
userFieldSelection='key,value'
|
||||
context='action'
|
||||
|
@ -280,7 +294,7 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if newTopicFields}}
|
||||
{{#if showSkipRedirect}}
|
||||
<div class="setting full">
|
||||
<div class="setting-label">
|
||||
<label>{{i18n "admin.wizard.action.skip_redirect.label"}}</label>
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
<div class="setting-value">
|
||||
{{combo-box
|
||||
value=field.type
|
||||
content=types
|
||||
content=fieldTypes
|
||||
onChange=(action (mut field.type))
|
||||
options=(hash
|
||||
none="admin.wizard.field.type"
|
||||
|
@ -57,7 +57,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{#if isInput}}
|
||||
{{#if showMinLength}}
|
||||
<div class="setting">
|
||||
<div class="setting-label">
|
||||
<label>{{i18n 'admin.wizard.field.min_length'}}</label>
|
||||
|
@ -81,7 +81,7 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if isCategoryOrTag}}
|
||||
{{#if showLimit}}
|
||||
<div class="setting">
|
||||
<div class="setting-label">
|
||||
<label>{{i18n 'admin.wizard.field.limit'}}</label>
|
||||
|
@ -116,7 +116,7 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if prefillEnabled}}
|
||||
{{#if showPrefill}}
|
||||
<div class="setting full field-mapper-setting">
|
||||
<div class="setting-label">
|
||||
<label>{{i18n 'admin.wizard.field.prefill'}}</label>
|
||||
|
@ -130,7 +130,7 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if contentEnabled}}
|
||||
{{#if showContent}}
|
||||
<div class="setting full field-mapper-setting">
|
||||
<div class="setting-label">
|
||||
<label>{{i18n 'admin.wizard.field.content'}}</label>
|
||||
|
|
|
@ -103,17 +103,7 @@
|
|||
{{#if currentField}}
|
||||
{{wizard-custom-field
|
||||
field=currentField
|
||||
types=wizard.fieldTypes
|
||||
fieldTypes=wizard.fieldTypes
|
||||
removeField="removeField"
|
||||
wizardFields=wizardFields}}
|
||||
{{/if}}
|
||||
|
||||
{{wizard-links type="action" current=currentAction items=step.actions}}
|
||||
|
||||
{{#if currentAction}}
|
||||
{{wizard-custom-action
|
||||
action=currentAction
|
||||
wizard=wizard
|
||||
removeAction="removeAction"
|
||||
wizardFields=wizardFields}}
|
||||
{{/if}}
|
|
@ -95,13 +95,9 @@ en:
|
|||
list: "Enter item"
|
||||
|
||||
error:
|
||||
name_required: "Wizards must have a name."
|
||||
steps_required: "Wizards must have at least one step."
|
||||
id_required: "All wizards, steps, fields and actions need an id."
|
||||
invalid_api_body: "Request body JSON needs to be a valid JSON."
|
||||
type_required: "All fields need a type."
|
||||
after_time_need_time: "After time is enabled but no time is set."
|
||||
after_time_invalid: "After time is invalid."
|
||||
required: "{{type}} requires {{property}}"
|
||||
invalid: "{{property}} is invalid"
|
||||
dependent: "{{dependentProperty}} is dependent on {{property}}"
|
||||
|
||||
step:
|
||||
header: "Steps"
|
||||
|
@ -154,6 +150,10 @@ en:
|
|||
topic_attr: "Topic Attribute"
|
||||
interpolate_fields: "Insert wizard fields using the field_id in w{}. Insert user fields using field key in u{}."
|
||||
|
||||
run_after:
|
||||
label: "Run After"
|
||||
wizard_completion: "Wizard completes"
|
||||
|
||||
custom_fields:
|
||||
label: "Custom"
|
||||
key: "field"
|
||||
|
|
|
@ -10,93 +10,31 @@ class CustomWizard::AdminController < ::ApplicationController
|
|||
render json: { types: CustomWizard::Field.types }
|
||||
end
|
||||
|
||||
def save
|
||||
params.require(:wizard)
|
||||
def save
|
||||
result = build_wizard
|
||||
|
||||
wizard = ::JSON.parse(params[:wizard])
|
||||
existing = PluginStore.get('custom_wizard', wizard['id']) || {}
|
||||
new_time = false
|
||||
error = nil
|
||||
if result[:error]
|
||||
render json: { error: result[:error] }
|
||||
else
|
||||
wizard = result[:wizard]
|
||||
existing_wizard = result[:existing_wizard]
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
PluginStore.set('custom_wizard', wizard["id"], wizard)
|
||||
|
||||
if wizard['after_time'] && result[:new_after_time]
|
||||
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'])
|
||||
end
|
||||
|
||||
if wizard["id"].blank?
|
||||
error = 'id_required'
|
||||
elsif wizard["name"].blank?
|
||||
error = 'name_required'
|
||||
elsif wizard["steps"].blank?
|
||||
error = 'steps_required'
|
||||
elsif wizard["after_time"]
|
||||
if !wizard["after_time_scheduled"] && !existing["after_time_scheduled"]
|
||||
error = 'after_time_need_time'
|
||||
else
|
||||
after_time_scheduled = Time.parse(wizard["after_time_scheduled"]).utc
|
||||
|
||||
new_time = existing['after_time_scheduled'] ?
|
||||
after_time_scheduled != Time.parse(existing['after_time_scheduled']).utc :
|
||||
true
|
||||
|
||||
begin
|
||||
if new_time && after_time_scheduled < Time.now.utc
|
||||
error = 'after_time_invalid'
|
||||
end
|
||||
rescue ArgumentError
|
||||
error = 'after_time_invalid'
|
||||
if existing_wizard && existing_wizard['after_time'] && !wizard['after_time']
|
||||
Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard['id'])
|
||||
Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard['id'])
|
||||
end
|
||||
end
|
||||
|
||||
render json: success_json.merge(wizard: wizard)
|
||||
end
|
||||
|
||||
return render json: { error: error } if error
|
||||
|
||||
wizard["steps"].each do |s|
|
||||
if s["id"].blank?
|
||||
error = 'id_required'
|
||||
break
|
||||
end
|
||||
|
||||
if s["fields"] && s["fields"].present?
|
||||
s["fields"].each do |f|
|
||||
if f["id"].blank?
|
||||
error = 'id_required'
|
||||
break
|
||||
end
|
||||
|
||||
if f["type"].blank?
|
||||
error = 'type_required'
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if s["actions"] && s["actions"].present?
|
||||
s["actions"].each do |a|
|
||||
if a["id"].blank?
|
||||
error = 'id_required'
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return render json: { error: error } if error
|
||||
|
||||
## end of error checks
|
||||
|
||||
wizard['steps'].each do |s|
|
||||
s['description'] = PrettyText.cook(s['raw_description']) if s['raw_description']
|
||||
end
|
||||
|
||||
if wizard['after_time'] && new_time
|
||||
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'])
|
||||
end
|
||||
|
||||
if existing['after_time'] && !wizard['after_time']
|
||||
Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard['id'])
|
||||
Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard['id'])
|
||||
end
|
||||
|
||||
PluginStore.set('custom_wizard', wizard["id"], wizard)
|
||||
|
||||
render json: success_json.merge(wizard: wizard)
|
||||
end
|
||||
|
||||
def remove
|
||||
|
@ -149,4 +87,139 @@ class CustomWizard::AdminController < ::ApplicationController
|
|||
|
||||
render json: success_json.merge(submissions: all_submissions)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def wizard_params
|
||||
params.require(:wizard)
|
||||
params[:wizard]
|
||||
end
|
||||
|
||||
def required_properties
|
||||
{
|
||||
wizard: ['id', 'name', 'steps'],
|
||||
step: ['id'],
|
||||
field: ['id', 'type'],
|
||||
action: ['id', 'type']
|
||||
}
|
||||
end
|
||||
|
||||
def dependent_properties
|
||||
{
|
||||
after_time: 'after_time_scheduled'
|
||||
}
|
||||
end
|
||||
|
||||
def check_required(object, type, error)
|
||||
object.each do |property, value|
|
||||
required = required_properties[type].include?(property)
|
||||
|
||||
if required && property.blank?
|
||||
error = {
|
||||
type: 'required',
|
||||
params: { property: property }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
error
|
||||
end
|
||||
|
||||
def check_depdendent(object, error)
|
||||
object.each do |property, value|
|
||||
dependent = dependent_properties[property]
|
||||
|
||||
if dependent && object[dependent].blank?
|
||||
error = {
|
||||
type: 'dependent',
|
||||
params: { dependent: dependent, property: property }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
error
|
||||
end
|
||||
|
||||
def validate_wizard(wizard)
|
||||
error = nil
|
||||
|
||||
error = check_required(wizard, :wizard, error)
|
||||
error = check_depdendent(wizard, error)
|
||||
|
||||
wizard['steps'].each do |step|
|
||||
error = check_required(step, :step, error)
|
||||
error = check_depdendent(step, error)
|
||||
break if error.present?
|
||||
|
||||
step['fields'].each do |field|
|
||||
error = check_required(field, :field, error)
|
||||
error = check_depdendent(field, error)
|
||||
break if error.present?
|
||||
end
|
||||
end
|
||||
|
||||
wizard['actions'].each do |action|
|
||||
error = check_required(action, :action, error)
|
||||
error = check_depdendent(action, error)
|
||||
break if error.present?
|
||||
end
|
||||
|
||||
if error
|
||||
{ error: error }
|
||||
else
|
||||
{ success: true }
|
||||
end
|
||||
end
|
||||
|
||||
def validate_after_time(wizard, existing_wizard)
|
||||
new = false
|
||||
error = nil
|
||||
|
||||
if wizard["after_time"]
|
||||
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
|
||||
|
||||
begin
|
||||
error = 'after_time_invalid' if new && after_time_scheduled < Time.now.utc
|
||||
rescue ArgumentError
|
||||
error = 'after_time_invalid'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if error
|
||||
{ error: { type: error } }
|
||||
else
|
||||
{ new: new }
|
||||
end
|
||||
end
|
||||
|
||||
def build_wizard
|
||||
wizard = ::JSON.parse(wizard_params)
|
||||
existing_wizard = PluginStore.get('custom_wizard', wizard['id']) || {}
|
||||
|
||||
validation = validate_wizard(wizard)
|
||||
return validation[:error] if validation[:error]
|
||||
|
||||
after_time_validation = validate_after_time(wizard, existing_wizard)
|
||||
return after_time_validation[:error] if after_time_validation[:error]
|
||||
|
||||
wizard['steps'].each do |step|
|
||||
if s['raw_description']
|
||||
step['description'] = PrettyText.cook(s['raw_description'])
|
||||
end
|
||||
end
|
||||
|
||||
result = {
|
||||
wizard: wizard,
|
||||
existing_wizard: existing_wizard,
|
||||
new_after_time: after_time_validation[:new]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,11 +2,12 @@ class CustomWizard::Builder
|
|||
attr_accessor :wizard, :updater, :submissions
|
||||
|
||||
def initialize(user=nil, wizard_id)
|
||||
data = PluginStore.get('custom_wizard', wizard_id)
|
||||
return if data.blank?
|
||||
template = PluginStore.get('custom_wizard', wizard_id)
|
||||
return if template.blank?
|
||||
|
||||
@steps = data['steps']
|
||||
@wizard = CustomWizard::Wizard.new(user, data)
|
||||
@steps = template['steps']
|
||||
@actions = template['actions']
|
||||
@wizard = CustomWizard::Wizard.new(user, template)
|
||||
@submissions = Array.wrap(PluginStore.get("#{wizard_id}_submissions", user.id)) if user
|
||||
end
|
||||
|
||||
|
@ -144,19 +145,24 @@ class CustomWizard::Builder
|
|||
submission = @submissions.last
|
||||
data = submission.merge(data)
|
||||
end
|
||||
|
||||
final_step = updater.step.next.nil?
|
||||
|
||||
if step_template['actions'] && step_template['actions'].length && data
|
||||
step_template['actions'].each do |action|
|
||||
CustomWizard::Action.new(
|
||||
action: action,
|
||||
user: user,
|
||||
data: data,
|
||||
updater: updater
|
||||
).perform
|
||||
if @actions.present?
|
||||
@actions.each do |action|
|
||||
|
||||
if (action.run_after === updater.step.id) ||
|
||||
(final_step && (!action.run_after || (action.run_after === 'wizard_completion')))
|
||||
|
||||
CustomWizard::Action.new(
|
||||
action: action,
|
||||
user: user,
|
||||
data: data,
|
||||
updater: updater
|
||||
).perform
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
final_step = updater.step.next.nil?
|
||||
|
||||
if route_to = data['route_to']
|
||||
data.delete('route_to')
|
||||
|
|
Laden …
In neuem Issue referenzieren