Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-12 21:22:54 +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 { default as discourseComputed, observes } from 'discourse-common/utils/decorators';
|
||||||
import { equal, not, empty, or } from "@ember/object/computed";
|
import { equal, empty, or } from "@ember/object/computed";
|
||||||
import { actionTypes, generateName, selectKitContent, profileFields } from '../lib/wizard';
|
import { actionTypes, generateName, selectKitContent } from '../lib/wizard';
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
classNames: 'wizard-custom-action',
|
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'),
|
createTopic: equal('action.type', 'create_topic'),
|
||||||
updateProfile: equal('action.type', 'update_profile'),
|
updateProfile: equal('action.type', 'update_profile'),
|
||||||
sendMessage: equal('action.type', 'send_message'),
|
sendMessage: equal('action.type', 'send_message'),
|
||||||
|
openComposer: equal('action.type', 'open_composer'),
|
||||||
sendToApi: equal('action.type', 'send_to_api'),
|
sendToApi: equal('action.type', 'send_to_api'),
|
||||||
apiEmpty: empty('action.api'),
|
|
||||||
addToGroup: equal('action.type', 'add_to_group'),
|
addToGroup: equal('action.type', 'add_to_group'),
|
||||||
routeTo: equal('action.type', 'route_to'),
|
routeTo: equal('action.type', 'route_to'),
|
||||||
disableId: not('action.isNew'),
|
apiEmpty: empty('action.api'),
|
||||||
groupPropertyTypes: selectKitContent(['id', 'name']),
|
groupPropertyTypes: selectKitContent(['id', 'name']),
|
||||||
hasAdvanced: or('hasCustomFields', 'routeTo'),
|
hasAdvanced: or('hasCustomFields', 'routeTo'),
|
||||||
hasCustomFields: or('basicTopicFields', 'updateProfile'),
|
hasCustomFields: or('basicTopicFields', 'updateProfile'),
|
||||||
|
basicTopicFields: or('createTopic', 'sendMessage', 'openComposer'),
|
||||||
|
publicTopicFields: or('createTopic', 'openComposer'),
|
||||||
|
showSkipRedirect: or('createTopic', 'sendMessage'),
|
||||||
|
|
||||||
@on('didInsertElement')
|
|
||||||
@observes('action.type')
|
@observes('action.type')
|
||||||
setLabel() {
|
setupDefaults() {
|
||||||
if (this.action.type) {
|
if (this.action.type) {
|
||||||
this.set('action.label', generateName(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')
|
@discourseComputed('wizard.steps')
|
||||||
categoryFields(fields) {
|
runAfterContent(steps) {
|
||||||
return fields.filter(f => f.type == 'category');
|
let content = steps.map(s => ({ id: s.id, name: s.label }));
|
||||||
},
|
|
||||||
|
content.unshift({
|
||||||
@discourseComputed('wizardFields')
|
id: 'wizard_completion',
|
||||||
tagFields(fields) {
|
name: I18n.t('admin.wizard.action.run_after.wizard_completion')
|
||||||
return fields.filter(f => f.type == 'tag');
|
});
|
||||||
},
|
|
||||||
|
return content;
|
||||||
@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.apis')
|
@discourseComputed('wizard.apis')
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators';
|
import { default as discourseComputed, observes } from 'discourse-common/utils/decorators';
|
||||||
import { equal, not, or } from "@ember/object/computed";
|
import { equal, or } from "@ember/object/computed";
|
||||||
import { selectKitContent } from '../lib/wizard';
|
import { selectKitContent } from '../lib/wizard';
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
|
|
||||||
|
@ -10,23 +10,30 @@ export default Component.extend({
|
||||||
isCategory: equal('field.type', 'category'),
|
isCategory: equal('field.type', 'category'),
|
||||||
isGroup: equal('field.type', 'group'),
|
isGroup: equal('field.type', 'group'),
|
||||||
isTag: equal('field.type', 'tag'),
|
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']),
|
categoryPropertyTypes: selectKitContent(['id', 'slug']),
|
||||||
prefillEnabled: or('isCategory', 'isTag', 'isGroup', 'isDropdown'),
|
|
||||||
contentEnabled: or('isCategory', 'isTag', 'isGroup', 'isDropdown'),
|
@observes('isUpload', 'isCategory')
|
||||||
|
setupDefaults() {
|
||||||
@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() {
|
|
||||||
if (this.isUpload && !this.field.file_types) {
|
if (this.isUpload && !this.field.file_types) {
|
||||||
this.set('field.file_types', '.jpg,.png');
|
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) {
|
setupTypeOutput(fieldType, options) {
|
||||||
|
@ -80,19 +87,6 @@ export default Component.extend({
|
||||||
return this.setupTypeOutput(fieldType, options);
|
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: {
|
actions: {
|
||||||
imageUploadDone(upload) {
|
imageUploadDone(upload) {
|
||||||
this.set("field.image", upload.url);
|
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";
|
import Component from "@ember/component";
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
classNames: 'wizard-custom-step',
|
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: {
|
actions: {
|
||||||
bannerUploadDone(upload) {
|
bannerUploadDone(upload) {
|
||||||
|
|
|
@ -99,7 +99,7 @@ export default Controller.extend({
|
||||||
}
|
}
|
||||||
}).catch((result) => {
|
}).catch((result) => {
|
||||||
this.set('saving', false);
|
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);
|
later(() => this.set('error', null), 10000);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,132 +19,6 @@ function mapped(property, type) {
|
||||||
mappedProperties[type].indexOf(property) > -1;
|
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) {
|
function castCase(property, value) {
|
||||||
return property.indexOf('_type') > -1 ? camelCase(value) : value;
|
return property.indexOf('_type') > -1 ? camelCase(value) : value;
|
||||||
}
|
}
|
||||||
|
@ -223,9 +97,9 @@ function objectHasAdvanced(params, type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildProperties(json) {
|
function buildProperties(json) {
|
||||||
let steps = A();
|
|
||||||
let props = {
|
let props = {
|
||||||
steps
|
steps: A();
|
||||||
|
action: A();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (present(json)) {
|
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(
|
steps.pushObject(
|
||||||
EmberObject.create(stepParams)
|
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 {
|
} else {
|
||||||
props.id = '';
|
props.id = '';
|
||||||
props.name = '';
|
props.name = '';
|
||||||
|
@ -299,7 +171,6 @@ function buildProperties(json) {
|
||||||
props.prompt_completion = false;
|
props.prompt_completion = false;
|
||||||
props.restart_on_revisit = false;
|
props.restart_on_revisit = false;
|
||||||
props.permitted = null;
|
props.permitted = null;
|
||||||
props.steps = A();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return props;
|
return props;
|
||||||
|
|
|
@ -58,7 +58,9 @@ const wizardProperties = [
|
||||||
'prompt_completion',
|
'prompt_completion',
|
||||||
'restart_on_revisit',
|
'restart_on_revisit',
|
||||||
'theme_id',
|
'theme_id',
|
||||||
'permitted'
|
'permitted',
|
||||||
|
'steps',
|
||||||
|
'actions'
|
||||||
];
|
];
|
||||||
|
|
||||||
const stepProperties = [
|
const stepProperties = [
|
||||||
|
@ -69,7 +71,8 @@ const stepProperties = [
|
||||||
'raw_description',
|
'raw_description',
|
||||||
'required_data',
|
'required_data',
|
||||||
'required_data_message',
|
'required_data_message',
|
||||||
'permitted_params'
|
'permitted_params',
|
||||||
|
'fields'
|
||||||
]
|
]
|
||||||
|
|
||||||
const fieldProperties = [
|
const fieldProperties = [
|
||||||
|
@ -91,6 +94,7 @@ const fieldProperties = [
|
||||||
const actionProperties = [
|
const actionProperties = [
|
||||||
'id',
|
'id',
|
||||||
'type',
|
'type',
|
||||||
|
'run_after',
|
||||||
'title',
|
'title',
|
||||||
'post',
|
'post',
|
||||||
'post_builder',
|
'post_builder',
|
||||||
|
@ -117,6 +121,12 @@ const properties = {
|
||||||
action: actionProperties
|
action: actionProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const objectArrays = [
|
||||||
|
'steps',
|
||||||
|
'fields',
|
||||||
|
'actions'
|
||||||
|
];
|
||||||
|
|
||||||
const mappedProperties = {
|
const mappedProperties = {
|
||||||
wizard: [
|
wizard: [
|
||||||
'permitted'
|
'permitted'
|
||||||
|
@ -202,6 +212,7 @@ export {
|
||||||
camelCase,
|
camelCase,
|
||||||
snakeCase,
|
snakeCase,
|
||||||
properties,
|
properties,
|
||||||
|
objectArrays,
|
||||||
wizardProperties,
|
wizardProperties,
|
||||||
mappedProperties,
|
mappedProperties,
|
||||||
profileFields,
|
profileFields,
|
||||||
|
|
|
@ -1,32 +1,20 @@
|
||||||
import { ajax } from 'discourse/lib/ajax';
|
import { ajax } from 'discourse/lib/ajax';
|
||||||
import EmberObject from "@ember/object";
|
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";
|
import { Promise } from "rsvp";
|
||||||
|
|
||||||
|
const jsonStrings = ['api_body'];
|
||||||
|
const required = ['id', 'steps', 'type'];
|
||||||
|
const dependent = { after_time: 'after_time_scheduled' }
|
||||||
|
|
||||||
const CustomWizard = EmberObject.extend({
|
const CustomWizard = EmberObject.extend({
|
||||||
save() {
|
save() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let wizardJson = buildJson(this, 'wizard');
|
let json = this.buildJson(this, 'wizard');
|
||||||
|
|
||||||
if (wizardJson.after_time && !wizardJson.after_time_scheduled) {
|
if (json.error) {
|
||||||
reject({
|
reject({ eror: json.error });
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ajax("/admin/wizards/custom/save", {
|
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() {
|
remove() {
|
||||||
return ajax("/admin/wizards/custom/remove", {
|
return ajax("/admin/wizards/custom/remove", {
|
||||||
type: 'DELETE',
|
type: 'DELETE',
|
||||||
|
|
|
@ -157,7 +157,16 @@
|
||||||
step=currentStep
|
step=currentStep
|
||||||
wizard=model
|
wizard=model
|
||||||
currentField=currentField
|
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}}
|
wizardFields=wizardFields}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<div class="setting-value">
|
<div class="setting-value">
|
||||||
{{combo-box
|
{{combo-box
|
||||||
value=action.type
|
value=action.type
|
||||||
content=types
|
content=actionTypes
|
||||||
onChange=(action (mut action.type))
|
onChange=(action (mut action.type))
|
||||||
options=(hash
|
options=(hash
|
||||||
none="admin.wizard.field.type"
|
none="admin.wizard.field.type"
|
||||||
|
@ -14,6 +14,19 @@
|
||||||
</div>
|
</div>
|
||||||
</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}}
|
{{#if basicTopicFields}}
|
||||||
<div class="setting full field-mapper-setting">
|
<div class="setting full field-mapper-setting">
|
||||||
<div class="setting-label">
|
<div class="setting-label">
|
||||||
|
@ -99,6 +112,7 @@
|
||||||
inputs=action.tags
|
inputs=action.tags
|
||||||
options=(hash
|
options=(hash
|
||||||
tagSelection='output'
|
tagSelection='output'
|
||||||
|
listSelection='output'
|
||||||
wizardFieldSelection=true
|
wizardFieldSelection=true
|
||||||
userFieldSelection='key,value'
|
userFieldSelection='key,value'
|
||||||
context='action'
|
context='action'
|
||||||
|
@ -280,7 +294,7 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if newTopicFields}}
|
{{#if showSkipRedirect}}
|
||||||
<div class="setting full">
|
<div class="setting full">
|
||||||
<div class="setting-label">
|
<div class="setting-label">
|
||||||
<label>{{i18n "admin.wizard.action.skip_redirect.label"}}</label>
|
<label>{{i18n "admin.wizard.action.skip_redirect.label"}}</label>
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
<div class="setting-value">
|
<div class="setting-value">
|
||||||
{{combo-box
|
{{combo-box
|
||||||
value=field.type
|
value=field.type
|
||||||
content=types
|
content=fieldTypes
|
||||||
onChange=(action (mut field.type))
|
onChange=(action (mut field.type))
|
||||||
options=(hash
|
options=(hash
|
||||||
none="admin.wizard.field.type"
|
none="admin.wizard.field.type"
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if isInput}}
|
{{#if showMinLength}}
|
||||||
<div class="setting">
|
<div class="setting">
|
||||||
<div class="setting-label">
|
<div class="setting-label">
|
||||||
<label>{{i18n 'admin.wizard.field.min_length'}}</label>
|
<label>{{i18n 'admin.wizard.field.min_length'}}</label>
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if isCategoryOrTag}}
|
{{#if showLimit}}
|
||||||
<div class="setting">
|
<div class="setting">
|
||||||
<div class="setting-label">
|
<div class="setting-label">
|
||||||
<label>{{i18n 'admin.wizard.field.limit'}}</label>
|
<label>{{i18n 'admin.wizard.field.limit'}}</label>
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if prefillEnabled}}
|
{{#if showPrefill}}
|
||||||
<div class="setting full field-mapper-setting">
|
<div class="setting full field-mapper-setting">
|
||||||
<div class="setting-label">
|
<div class="setting-label">
|
||||||
<label>{{i18n 'admin.wizard.field.prefill'}}</label>
|
<label>{{i18n 'admin.wizard.field.prefill'}}</label>
|
||||||
|
@ -130,7 +130,7 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if contentEnabled}}
|
{{#if showContent}}
|
||||||
<div class="setting full field-mapper-setting">
|
<div class="setting full field-mapper-setting">
|
||||||
<div class="setting-label">
|
<div class="setting-label">
|
||||||
<label>{{i18n 'admin.wizard.field.content'}}</label>
|
<label>{{i18n 'admin.wizard.field.content'}}</label>
|
||||||
|
|
|
@ -103,17 +103,7 @@
|
||||||
{{#if currentField}}
|
{{#if currentField}}
|
||||||
{{wizard-custom-field
|
{{wizard-custom-field
|
||||||
field=currentField
|
field=currentField
|
||||||
types=wizard.fieldTypes
|
fieldTypes=wizard.fieldTypes
|
||||||
removeField="removeField"
|
removeField="removeField"
|
||||||
wizardFields=wizardFields}}
|
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}}
|
{{/if}}
|
|
@ -95,13 +95,9 @@ en:
|
||||||
list: "Enter item"
|
list: "Enter item"
|
||||||
|
|
||||||
error:
|
error:
|
||||||
name_required: "Wizards must have a name."
|
required: "{{type}} requires {{property}}"
|
||||||
steps_required: "Wizards must have at least one step."
|
invalid: "{{property}} is invalid"
|
||||||
id_required: "All wizards, steps, fields and actions need an id."
|
dependent: "{{dependentProperty}} is dependent on {{property}}"
|
||||||
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."
|
|
||||||
|
|
||||||
step:
|
step:
|
||||||
header: "Steps"
|
header: "Steps"
|
||||||
|
@ -154,6 +150,10 @@ en:
|
||||||
topic_attr: "Topic Attribute"
|
topic_attr: "Topic Attribute"
|
||||||
interpolate_fields: "Insert wizard fields using the field_id in w{}. Insert user fields using field key in u{}."
|
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:
|
custom_fields:
|
||||||
label: "Custom"
|
label: "Custom"
|
||||||
key: "field"
|
key: "field"
|
||||||
|
|
|
@ -10,93 +10,31 @@ class CustomWizard::AdminController < ::ApplicationController
|
||||||
render json: { types: CustomWizard::Field.types }
|
render json: { types: CustomWizard::Field.types }
|
||||||
end
|
end
|
||||||
|
|
||||||
def save
|
def save
|
||||||
params.require(:wizard)
|
result = build_wizard
|
||||||
|
|
||||||
wizard = ::JSON.parse(params[:wizard])
|
if result[:error]
|
||||||
existing = PluginStore.get('custom_wizard', wizard['id']) || {}
|
render json: { error: result[:error] }
|
||||||
new_time = false
|
else
|
||||||
error = nil
|
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?
|
if existing_wizard && existing_wizard['after_time'] && !wizard['after_time']
|
||||||
error = 'id_required'
|
Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard['id'])
|
||||||
elsif wizard["name"].blank?
|
Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard['id'])
|
||||||
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'
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
render json: success_json.merge(wizard: wizard)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
def remove
|
def remove
|
||||||
|
@ -149,4 +87,139 @@ class CustomWizard::AdminController < ::ApplicationController
|
||||||
|
|
||||||
render json: success_json.merge(submissions: all_submissions)
|
render json: success_json.merge(submissions: all_submissions)
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -2,11 +2,12 @@ class CustomWizard::Builder
|
||||||
attr_accessor :wizard, :updater, :submissions
|
attr_accessor :wizard, :updater, :submissions
|
||||||
|
|
||||||
def initialize(user=nil, wizard_id)
|
def initialize(user=nil, wizard_id)
|
||||||
data = PluginStore.get('custom_wizard', wizard_id)
|
template = PluginStore.get('custom_wizard', wizard_id)
|
||||||
return if data.blank?
|
return if template.blank?
|
||||||
|
|
||||||
@steps = data['steps']
|
@steps = template['steps']
|
||||||
@wizard = CustomWizard::Wizard.new(user, data)
|
@actions = template['actions']
|
||||||
|
@wizard = CustomWizard::Wizard.new(user, template)
|
||||||
@submissions = Array.wrap(PluginStore.get("#{wizard_id}_submissions", user.id)) if user
|
@submissions = Array.wrap(PluginStore.get("#{wizard_id}_submissions", user.id)) if user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -144,19 +145,24 @@ class CustomWizard::Builder
|
||||||
submission = @submissions.last
|
submission = @submissions.last
|
||||||
data = submission.merge(data)
|
data = submission.merge(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
final_step = updater.step.next.nil?
|
||||||
|
|
||||||
if step_template['actions'] && step_template['actions'].length && data
|
if @actions.present?
|
||||||
step_template['actions'].each do |action|
|
@actions.each do |action|
|
||||||
CustomWizard::Action.new(
|
|
||||||
action: action,
|
if (action.run_after === updater.step.id) ||
|
||||||
user: user,
|
(final_step && (!action.run_after || (action.run_after === 'wizard_completion')))
|
||||||
data: data,
|
|
||||||
updater: updater
|
CustomWizard::Action.new(
|
||||||
).perform
|
action: action,
|
||||||
|
user: user,
|
||||||
|
data: data,
|
||||||
|
updater: updater
|
||||||
|
).perform
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
final_step = updater.step.next.nil?
|
|
||||||
|
|
||||||
if route_to = data['route_to']
|
if route_to = data['route_to']
|
||||||
data.delete('route_to')
|
data.delete('route_to')
|
||||||
|
|
Laden …
In neuem Issue referenzieren