0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-11-15 22:32:54 +01:00

Merge branch 'master' into mapper_documentation

Dieser Commit ist enthalten in:
Faizaan Gagan 2021-04-26 01:51:11 +05:30
Commit 3d4c24d362
72 geänderte Dateien mit 1575 neuen und 610 gelöschten Zeilen

Datei anzeigen

@ -107,6 +107,40 @@ export default Component.extend(UndoChanges, {
return this.setupTypeOutput(fieldType, options); return this.setupTypeOutput(fieldType, options);
}, },
@discourseComputed("step.index")
fieldConditionOptions(stepIndex) {
const options = {
inputTypes: "validation",
context: "field",
textSelection: "value",
userFieldSelection: true,
groupSelection: true,
};
if (stepIndex > 0) {
options.wizardFieldSelection = true;
options.wizardActionSelection = true;
}
return options;
},
@discourseComputed("step.index")
fieldIndexOptions(stepIndex) {
const options = {
context: "field",
userFieldSelection: true,
groupSelection: true,
};
if (stepIndex > 0) {
options.wizardFieldSelection = true;
options.wizardActionSelection = true;
}
return options;
},
actions: { actions: {
imageUploadDone(upload) { imageUploadDone(upload) {
this.set("field.image", upload.url); this.set("field.image", upload.url);

Datei anzeigen

@ -1,8 +1,27 @@
import Component from "@ember/component"; import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({ export default Component.extend({
classNames: "wizard-custom-step", classNames: "wizard-custom-step",
@discourseComputed("step.index")
stepConditionOptions(stepIndex) {
const options = {
inputTypes: "validation",
context: "step",
textSelection: "value",
userFieldSelection: true,
groupSelection: true,
};
if (stepIndex > 0) {
options["wizardFieldSelection"] = true;
options["wizardActionSelection"] = true;
}
return options;
},
actions: { actions: {
bannerUploadDone(upload) { bannerUploadDone(upload) {
this.set("step.banner", upload.url); this.set("step.banner", upload.url);

Datei anzeigen

@ -38,6 +38,7 @@ export default Component.extend({
const items = this.items; const items = this.items;
const item = items.findBy("id", itemId); const item = items.findBy("id", itemId);
items.removeObject(item); items.removeObject(item);
item.set("index", newIndex);
items.insertAt(newIndex, item); items.insertAt(newIndex, item);
scheduleOnce("afterRender", this, () => this.applySortable()); scheduleOnce("afterRender", this, () => this.applySortable());
}, },
@ -90,22 +91,14 @@ export default Component.extend({
params.isNew = true; params.isNew = true;
let next = 1; let index = 0;
if (items.length) { if (items.length) {
next = index = items.length;
Math.max.apply(
Math,
items.map((i) => {
let parts = i.id.split("_");
let lastPart = parts[parts.length - 1];
return isNaN(lastPart) ? 0 : lastPart;
})
) + 1;
} }
let id = `${itemType}_${next}`; params.index = index;
let id = `${itemType}_${index + 1}`;
if (itemType === "field") { if (itemType === "field") {
id = `${this.parentId}_${id}`; id = `${this.parentId}_${id}`;
} }

Datei anzeigen

@ -23,54 +23,69 @@ function castCase(property, value) {
return property.indexOf("_type") > -1 ? camelCase(value) : value; return property.indexOf("_type") > -1 ? camelCase(value) : value;
} }
function buildProperty(json, property, type) { function buildMappedProperty(value) {
let value = json[property]; let inputs = [];
if (mapped(property, type) && present(value) && value.constructor === Array) { value.forEach((inputJson) => {
let inputs = []; let input = {};
value.forEach((inputJson) => { Object.keys(inputJson).forEach((inputKey) => {
let input = {}; if (inputKey === "pairs") {
let pairs = [];
let pairCount = inputJson.pairs.length;
Object.keys(inputJson).forEach((inputKey) => { inputJson.pairs.forEach((pairJson) => {
if (inputKey === "pairs") { let pair = {};
let pairs = [];
let pairCount = inputJson.pairs.length;
inputJson.pairs.forEach((pairJson) => { Object.keys(pairJson).forEach((pairKey) => {
let pair = {}; pair[pairKey] = castCase(pairKey, pairJson[pairKey]);
Object.keys(pairJson).forEach((pairKey) => {
pair[pairKey] = castCase(pairKey, pairJson[pairKey]);
});
pair.pairCount = pairCount;
pairs.push(EmberObject.create(pair));
}); });
input.pairs = pairs; pair.pairCount = pairCount;
} else {
input[inputKey] = castCase(inputKey, inputJson[inputKey]);
}
});
inputs.push(EmberObject.create(input)); pairs.push(EmberObject.create(pair));
});
input.pairs = pairs;
} else {
input[inputKey] = castCase(inputKey, inputJson[inputKey]);
}
}); });
return A(inputs); inputs.push(EmberObject.create(input));
} else { });
return value;
} return A(inputs);
} }
function buildObject(json, type) { function buildProperty(json, property, type, objectIndex) {
let value = json[property];
if (
property === "index" &&
(value === null || value === undefined) &&
(objectIndex !== null || objectIndex !== undefined)
) {
return objectIndex;
}
if (
!mapped(property, type) ||
!present(value) ||
!value.constructor === Array
) {
return value;
}
return buildMappedProperty(value);
}
function buildObject(json, type, objectIndex) {
let props = { let props = {
isNew: false, isNew: false,
}; };
Object.keys(json).forEach((prop) => { Object.keys(json).forEach((prop) => {
props[prop] = buildProperty(json, prop, type); props[prop] = buildProperty(json, prop, type, objectIndex);
}); });
return EmberObject.create(props); return EmberObject.create(props);
@ -80,8 +95,8 @@ function buildObjectArray(json, type) {
let array = A(); let array = A();
if (present(json)) { if (present(json)) {
json.forEach((objJson) => { json.forEach((objJson, objectIndex) => {
let object = buildObject(objJson, type); let object = buildObject(objJson, type, objectIndex);
if (hasAdvancedProperties(object, type)) { if (hasAdvancedProperties(object, type)) {
object.set("showAdvanced", true); object.set("showAdvanced", true);
@ -94,9 +109,9 @@ function buildObjectArray(json, type) {
return array; return array;
} }
function buildBasicProperties(json, type, props) { function buildBasicProperties(json, type, props, objectIndex = null) {
listProperties(type).forEach((p) => { listProperties(type).forEach((p) => {
props[p] = buildProperty(json, p, type); props[p] = buildProperty(json, p, type, objectIndex);
if (hasAdvancedProperties(json, type)) { if (hasAdvancedProperties(json, type)) {
props.showAdvanced = true; props.showAdvanced = true;
@ -142,12 +157,17 @@ function buildProperties(json) {
props = buildBasicProperties(json, "wizard", props); props = buildBasicProperties(json, "wizard", props);
if (present(json.steps)) { if (present(json.steps)) {
json.steps.forEach((stepJson) => { json.steps.forEach((stepJson, objectIndex) => {
let stepProps = { let stepProps = {
isNew: false, isNew: false,
}; };
stepProps = buildBasicProperties(stepJson, "step", stepProps); stepProps = buildBasicProperties(
stepJson,
"step",
stepProps,
objectIndex
);
stepProps.fields = buildObjectArray(stepJson.fields, "field"); stepProps.fields = buildObjectArray(stepJson.fields, "field");
props.steps.pushObject(EmberObject.create(stepProps)); props.steps.pushObject(EmberObject.create(stepProps));

Datei anzeigen

@ -37,6 +37,7 @@ const wizard = {
const step = { const step = {
basic: { basic: {
id: null, id: null,
index: null,
title: null, title: null,
key: null, key: null,
banner: null, banner: null,
@ -44,9 +45,11 @@ const step = {
required_data: null, required_data: null,
required_data_message: null, required_data_message: null,
permitted_params: null, permitted_params: null,
condition: null,
force_final: false,
}, },
mapped: ["required_data", "permitted_params"], mapped: ["required_data", "permitted_params", "condition", "index"],
advanced: ["required_data", "permitted_params"], advanced: ["required_data", "permitted_params", "condition", "index"],
required: ["id"], required: ["id"],
dependent: {}, dependent: {},
objectArrays: { objectArrays: {
@ -60,16 +63,18 @@ const step = {
const field = { const field = {
basic: { basic: {
id: null, id: null,
index: null,
label: null, label: null,
image: null, image: null,
description: null, description: null,
required: null, required: null,
key: null, key: null,
type: null, type: null,
condition: null,
}, },
types: {}, types: {},
mapped: ["prefill", "content"], mapped: ["prefill", "content", "condition", "index"],
advanced: ["property", "key"], advanced: ["property", "key", "condition", "index"],
required: ["id", "type"], required: ["id", "type"],
dependent: {}, dependent: {},
objectArrays: {}, objectArrays: {},

Datei anzeigen

@ -131,11 +131,15 @@ const CustomWizard = EmberObject.extend({
return result; return result;
}, },
buildMappedJson(inputs) { buildMappedJson(value) {
if (!inputs || !inputs.length) { if (typeof value === "string" || Number.isInteger(value)) {
return value;
}
if (!value || !value.length) {
return false; return false;
} }
let inputs = value;
let result = []; let result = [];
inputs.forEach((inpt) => { inputs.forEach((inpt) => {

Datei anzeigen

@ -188,6 +188,30 @@
{{#if field.showAdvanced}} {{#if field.showAdvanced}}
<div class="advanced-settings"> <div class="advanced-settings">
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.condition"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=field.condition
options=fieldConditionOptions}}
</div>
</div>
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.index"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=field.index
options=fieldIndexOptions}}
</div>
</div>
{{#if isCategory}} {{#if isCategory}}
<div class="setting"> <div class="setting">
<div class="setting-label"> <div class="setting-label">

Datei anzeigen

@ -38,6 +38,27 @@
{{#if step.showAdvanced}} {{#if step.showAdvanced}}
<div class="advanced-settings"> <div class="advanced-settings">
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.condition"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=step.condition
options=stepConditionOptions}}
</div>
</div>
<div class="setting full">
<div class="setting-label"></div>
<div class="setting-value force-final">
<h4>{{i18n "admin.wizard.step.force_final.label"}}</h4>
{{input type="checkbox" checked=step.force_final}}
<span>{{i18n "admin.wizard.step.force_final.description"}}</span>
</div>
</div>
<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.step.required_data.label"}}</label> <label>{{i18n "admin.wizard.step.required_data.label"}}</label>
@ -92,7 +113,6 @@
placeholderKey="admin.wizard.translation_placeholder"}} placeholderKey="admin.wizard.translation_placeholder"}}
</div> </div>
</div> </div>
</div> </div>
{{/if}} {{/if}}
@ -105,6 +125,7 @@
{{#each step.fields as |field|}} {{#each step.fields as |field|}}
{{wizard-custom-field {{wizard-custom-field
field=field field=field
step=step
currentFieldId=currentField.id currentFieldId=currentField.id
fieldTypes=fieldTypes fieldTypes=fieldTypes
removeField="removeField" removeField="removeField"

Datei anzeigen

@ -4,14 +4,15 @@ import getUrl from "discourse-common/lib/get-url";
export default StepController.extend({ export default StepController.extend({
actions: { actions: {
goNext(response) { goNext(response) {
const next = this.get("step.next"); let nextStepId = response["next_step_id"];
if (response.redirect_on_next) { if (response.redirect_on_next) {
window.location.href = response.redirect_on_next; window.location.href = response.redirect_on_next;
} else if (response.refresh_required) { } else if (response.refresh_required) {
const id = this.get("wizard.id"); const wizardId = this.get("wizard.id");
window.location.href = getUrl(`/w/${id}/steps/${next}`); window.location.href = getUrl(`/w/${wizardId}/steps/${nextStepId}`);
} else { } else {
this.transitionToRoute("custom.step", next); this.transitionToRoute("custom.step", nextStepId);
} }
}, },

Datei anzeigen

@ -8,6 +8,9 @@ export default {
const CustomWizard = requirejs( const CustomWizard = requirejs(
"discourse/plugins/discourse-custom-wizard/wizard/models/custom" "discourse/plugins/discourse-custom-wizard/wizard/models/custom"
).default; ).default;
const updateCachedWizard = requirejs(
"discourse/plugins/discourse-custom-wizard/wizard/models/custom"
).updateCachedWizard;
const StepModel = requirejs("wizard/models/step").default; const StepModel = requirejs("wizard/models/step").default;
const StepComponent = requirejs("wizard/components/wizard-step").default; const StepComponent = requirejs("wizard/components/wizard-step").default;
const ajax = requirejs("wizard/lib/ajax").ajax; const ajax = requirejs("wizard/lib/ajax").ajax;
@ -18,6 +21,7 @@ export default {
"discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite" "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite"
).cook; ).cook;
const { schedule } = requirejs("@ember/runloop"); const { schedule } = requirejs("@ember/runloop");
const { alias, not } = requirejs("@ember/object/computed");
StepModel.reopen({ StepModel.reopen({
save() { save() {
@ -155,12 +159,17 @@ export default {
this.sendAction("showMessage", message); this.sendAction("showMessage", message);
}.observes("step.message"), }.observes("step.message"),
showNextButton: not("step.final"),
showDoneButton: alias("step.final"),
advance() { advance() {
this.set("saving", true); this.set("saving", true);
this.get("step") this.get("step")
.save() .save()
.then((response) => { .then((response) => {
if (this.get("finalStep")) { updateCachedWizard(CustomWizard.build(response["wizard"]));
if (response["final"]) {
CustomWizard.finished(response); CustomWizard.finished(response);
} else { } else {
this.sendAction("goNext", response); this.sendAction("goNext", response);
@ -178,7 +187,6 @@ export default {
}, },
done() { done() {
this.set("finalStep", true);
this.send("nextStep"); this.send("nextStep");
}, },

Datei anzeigen

@ -31,63 +31,45 @@ CustomWizard.reopenClass({
} }
window.location.href = getUrl(url); window.location.href = getUrl(url);
}, },
});
export function findCustomWizard(wizardId, params = {}) { build(wizardJson) {
let url = `/w/${wizardId}`; if (!wizardJson) {
let paramKeys = Object.keys(params).filter((k) => {
if (k === "wizard_id") {
return false;
}
return !!params[k];
});
if (paramKeys.length) {
url += "?";
paramKeys.forEach((k, i) => {
if (i > 0) {
url += "&";
}
url += `${k}=${params[k]}`;
});
}
return ajax({ url, cache: false, dataType: "json" }).then((result) => {
const wizard = result;
if (!wizard) {
return null; return null;
} }
if (!wizard.completed) { if (!wizardJson.completed && wizardJson.steps) {
wizard.steps = wizard.steps.map((step) => { wizardJson.steps = wizardJson.steps
const stepObj = Step.create(step); .map((step) => {
const stepObj = Step.create(step);
stepObj.fields.sort((a, b) => { stepObj.fields.sort((a, b) => {
return parseFloat(a.number) - parseFloat(b.number); return parseFloat(a.number) - parseFloat(b.number);
});
let tabindex = 1;
stepObj.fields.forEach((f) => {
f.tabindex = tabindex;
if (["date_time"].includes(f.type)) {
tabindex = tabindex + 2;
} else {
tabindex++;
}
});
stepObj.fields = stepObj.fields.map((f) => WizardField.create(f));
return stepObj;
})
.sort((a, b) => {
return parseFloat(a.index) - parseFloat(b.index);
}); });
let tabindex = 1;
stepObj.fields.forEach((f) => {
f.tabindex = tabindex;
if (["date_time"].includes(f.type)) {
tabindex = tabindex + 2;
} else {
tabindex++;
}
});
stepObj.fields = stepObj.fields.map((f) => WizardField.create(f));
return stepObj;
});
} }
if (wizard.categories) { if (wizardJson.categories) {
let subcatMap = {}; let subcatMap = {};
let categoriesById = {}; let categoriesById = {};
let categories = wizard.categories.map((c) => { let categories = wizardJson.categories.map((c) => {
if (c.parent_category_id) { if (c.parent_category_id) {
subcatMap[c.parent_category_id] = subcatMap[c.parent_category_id] =
subcatMap[c.parent_category_id] || []; subcatMap[c.parent_category_id] || [];
@ -116,12 +98,47 @@ export function findCustomWizard(wizardId, params = {}) {
Discourse.Site.currentProp("categoriesById", categoriesById); Discourse.Site.currentProp("categoriesById", categoriesById);
Discourse.Site.currentProp( Discourse.Site.currentProp(
"uncategorized_category_id", "uncategorized_category_id",
wizard.uncategorized_category_id wizardJson.uncategorized_category_id
); );
} }
return CustomWizard.create(wizard); return CustomWizard.create(wizardJson);
},
});
export function findCustomWizard(wizardId, params = {}) {
let url = `/w/${wizardId}`;
let paramKeys = Object.keys(params).filter((k) => {
if (k === "wizard_id") {
return false;
}
return !!params[k];
});
if (paramKeys.length) {
url += "?";
paramKeys.forEach((k, i) => {
if (i > 0) {
url += "&";
}
url += `${k}=${params[k]}`;
});
}
return ajax({ url, cache: false, dataType: "json" }).then((result) => {
return CustomWizard.build(result);
}); });
} }
let _wizard_store;
export function updateCachedWizard(wizard) {
_wizard_store = wizard;
}
export function getCachedWizard() {
return _wizard_store;
}
export default CustomWizard; export default CustomWizard;

Datei anzeigen

@ -1,22 +1,19 @@
import { getCachedWizard } from "../models/custom";
export default Ember.Route.extend({ export default Ember.Route.extend({
beforeModel() { beforeModel() {
const appModel = this.modelFor("custom"); const wizard = getCachedWizard();
if ( if (wizard && wizard.permitted && !wizard.completed && wizard.start) {
appModel && this.replaceWith("custom.step", wizard.start);
appModel.permitted &&
!appModel.completed &&
appModel.start
) {
this.replaceWith("custom.step", appModel.start);
} }
}, },
model() { model() {
return this.modelFor("custom"); return getCachedWizard();
}, },
setupController(controller, model) { setupController(controller, model) {
if (model) { if (model && model.id) {
const completed = model.get("completed"); const completed = model.get("completed");
const permitted = model.get("permitted"); const permitted = model.get("permitted");
const wizardId = model.get("id"); const wizardId = model.get("id");

Datei anzeigen

@ -1,28 +1,33 @@
import WizardI18n from "../lib/wizard-i18n"; import WizardI18n from "../lib/wizard-i18n";
import { getCachedWizard } from "../models/custom";
export default Ember.Route.extend({ export default Ember.Route.extend({
model(params) { beforeModel() {
const appModel = this.modelFor("custom"); this.set("wizard", getCachedWizard());
const allSteps = appModel.steps; },
if (allSteps) {
const step = allSteps.findBy("id", params.step_id);
return step ? step : allSteps[0];
}
return appModel; model(params) {
const wizard = this.wizard;
if (wizard && wizard.steps) {
const step = wizard.steps.findBy("id", params.step_id);
return step ? step : wizard.steps[0];
} else {
return wizard;
}
}, },
afterModel(model) { afterModel(model) {
if (model.completed) { if (model.completed) {
return this.transitionTo("index"); return this.transitionTo("index");
} }
return model.set("wizardId", this.modelFor("custom").id); return model.set("wizardId", this.wizard.id);
}, },
setupController(controller, model) { setupController(controller, model) {
let props = { let props = {
step: model, step: model,
wizard: this.modelFor("custom"), wizard: this.wizard,
}; };
if (!model.permitted) { if (!model.permitted) {

Datei anzeigen

@ -1,6 +1,6 @@
/* eslint no-undef: 0*/ /* eslint no-undef: 0*/
import { findCustomWizard } from "../models/custom"; import { findCustomWizard, updateCachedWizard } from "../models/custom";
import { ajax } from "wizard/lib/ajax"; import { ajax } from "wizard/lib/ajax";
export default Ember.Route.extend({ export default Ember.Route.extend({
@ -12,7 +12,9 @@ export default Ember.Route.extend({
return findCustomWizard(params.wizard_id, this.get("queryParams")); return findCustomWizard(params.wizard_id, this.get("queryParams"));
}, },
afterModel() { afterModel(model) {
updateCachedWizard(model);
return ajax({ return ajax({
url: `/site/settings`, url: `/site/settings`,
type: "GET", type: "GET",
@ -25,11 +27,11 @@ export default Ember.Route.extend({
const background = model ? model.get("background") : "AliceBlue"; const background = model ? model.get("background") : "AliceBlue";
Ember.run.scheduleOnce("afterRender", this, function () { Ember.run.scheduleOnce("afterRender", this, function () {
$("body.custom-wizard").css("background", background); $("body.custom-wizard").css("background", background);
if (model) {
$("#custom-wizard-main").addClass(model.get("id").dasherize()); if (model && model.id) {
$("#custom-wizard-main").addClass(model.id.dasherize());
} }
}); });
controller.setProperties({ controller.setProperties({
customWizard: true, customWizard: true,
logoUrl: Wizard.SiteSettings.logo_small, logoUrl: Wizard.SiteSettings.logo_small,

Datei anzeigen

@ -303,6 +303,16 @@
max-width: 250px !important; max-width: 250px !important;
min-width: 250px !important; min-width: 250px !important;
} }
&.force-final {
padding: 1em;
background-color: var(--primary-very-low);
label,
span {
font-size: 1em;
}
}
} }
&.full, &.full,

Datei anzeigen

@ -50,6 +50,11 @@ textarea {
border-color: var(--danger); border-color: var(--danger);
box-shadow: shadow("focus-danger"); box-shadow: shadow("focus-danger");
} }
&[type="checkbox"] {
margin-bottom: 0;
margin-right: 10px;
}
} }
.spinner { .spinner {

Datei anzeigen

@ -56,6 +56,8 @@ en:
undo: "Undo" undo: "Undo"
clear: "Clear" clear: "Clear"
select_type: "Select a type" select_type: "Select a type"
condition: "Condition"
index: "Index"
message: message:
wizard: wizard:
@ -157,6 +159,9 @@ en:
not_permitted_message: "Message shown when required data not present" not_permitted_message: "Message shown when required data not present"
permitted_params: permitted_params:
label: "Params" label: "Params"
force_final:
label: "Conditional Final Step"
description: "Display this step as the final step if conditions on later steps have not passed when the user reaches this step."
field: field:
header: "Fields" header: "Fields"

Datei anzeigen

@ -41,7 +41,7 @@ class CustomWizard::AdminManagerController < CustomWizard::AdminController
end end
begin begin
template_json = JSON.parse file template_json = JSON.parse(file)
rescue JSON::ParserError rescue JSON::ParserError
return render_error(I18n.t('wizard.import.error.invalid_json')) return render_error(I18n.t('wizard.import.error.invalid_json'))
end end

Datei anzeigen

@ -37,7 +37,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
wizard_id = template.save(create: params[:create]) wizard_id = template.save(create: params[:create])
if template.errors.any? if template.errors.any?
render json: failed_json.merge(errors: result.errors.full_messages) render json: failed_json.merge(errors: template.errors.full_messages)
else else
render json: success_json.merge(wizard_id: wizard_id) render json: success_json.merge(wizard_id: wizard_id)
end end
@ -83,15 +83,19 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
permitted: mapped_params, permitted: mapped_params,
steps: [ steps: [
:id, :id,
:index,
:title, :title,
:key, :key,
:banner, :banner,
:raw_description, :raw_description,
:required_data_message, :required_data_message,
:force_final,
required_data: mapped_params, required_data: mapped_params,
permitted_params: mapped_params, permitted_params: mapped_params,
condition: mapped_params,
fields: [ fields: [
:id, :id,
:index,
:label, :label,
:image, :image,
:description, :description,
@ -107,6 +111,8 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
:property, :property,
prefill: mapped_params, prefill: mapped_params,
content: mapped_params, content: mapped_params,
condition: mapped_params,
index: mapped_params,
validations: {}, validations: {},
] ]
], ],

Datei anzeigen

@ -4,31 +4,67 @@ class CustomWizard::StepsController < ::ApplicationController
before_action :ensure_can_update before_action :ensure_can_update
def update def update
params.require(:step_id)
params.require(:wizard_id)
wizard = @builder.build
step = wizard.steps.select { |s| s.id == update_params[:step_id] }.first
raise Discourse::InvalidParameters.new(:step_id) if !step
update = update_params.to_h update = update_params.to_h
update[:fields] = {} update[:fields] = {}
if params[:fields] if params[:fields]
field_ids = step.fields.map(&:id) field_ids = @step_template['fields'].map { |f| f['id'] }
params[:fields].each do |k, v| params[:fields].each do |k, v|
update[:fields][k] = v if field_ids.include? k update[:fields][k] = v if field_ids.include? k
end end
end end
updater = wizard.create_updater(update[:step_id], update[:fields]) @builder.build
updater = @builder.wizard.create_updater(update[:step_id], update[:fields])
updater.update updater.update
@result = updater.result
if updater.success? if updater.success?
result = success_json wizard_id = update_params[:wizard_id]
result.merge!(updater.result) if updater.result builder = CustomWizard::Builder.new(wizard_id, current_user)
@wizard = builder.build
current_step = @wizard.find_step(update[:step_id])
current_submission = @wizard.current_submission
result = {}
if current_step.conditional_final_step && !current_step.last_step
current_step.force_final = true
end
if current_step.final?
builder.template.actions.each do |action_template|
if action_template['run_after'] === 'wizard_completion'
CustomWizard::Action.new(
action: action_template,
wizard: @wizard,
data: current_submission
).perform
end
end
@wizard.save_submission(current_submission)
if redirect = get_redirect
updater.result[:redirect_on_complete] = redirect
end
@wizard.final_cleanup!
result[:final] = true
else
result[:final] = false
result[:next_step_id] = current_step.next.id
end
result.merge!(updater.result) if updater.result.present?
result[:refresh_required] = true if updater.refresh_required? result[:refresh_required] = true if updater.refresh_required?
result[:wizard] = ::CustomWizard::WizardSerializer.new(
@wizard,
scope: Guardian.new(current_user),
root: false
).as_json
render json: result render json: result
else else
@ -43,21 +79,31 @@ class CustomWizard::StepsController < ::ApplicationController
private private
def ensure_can_update def ensure_can_update
@builder = CustomWizard::Builder.new( @builder = CustomWizard::Builder.new(update_params[:wizard_id], current_user)
update_params[:wizard_id].underscore, raise Discourse::InvalidParameters.new(:wizard_id) if @builder.template.nil?
current_user raise Discourse::InvalidAccess.new if !@builder.wizard || !@builder.wizard.can_access?
)
if @builder.nil? @step_template = @builder.template.steps.select do |s|
raise Discourse::InvalidParameters.new(:wizard_id) s['id'] == update_params[:step_id]
end end.first
raise Discourse::InvalidParameters.new(:step_id) if !@step_template
if !@builder.wizard || !@builder.wizard.can_access? raise Discourse::InvalidAccess.new if !@builder.check_condition(@step_template)
raise Discourse::InvalidAccess.new
end
end end
def update_params def update_params
params.permit(:wizard_id, :step_id) @update_params || begin
params.require(:step_id)
params.require(:wizard_id)
params.permit(:wizard_id, :step_id).transform_values { |v| v.underscore }
end
end
def get_redirect
return @result[:redirect_on_next] if @result[:redirect_on_next].present?
current_submission = @wizard.current_submission
return nil unless current_submission.present?
## route_to set by actions, redirect_on_complete set by actions, redirect_to set at wizard entry
current_submission[:route_to] || current_submission[:redirect_on_complete] || current_submission[:redirect_to]
end end
end end

Datei anzeigen

@ -65,10 +65,7 @@ class CustomWizard::WizardController < ::ApplicationController
result.merge!(redirect_to: submission['redirect_to']) result.merge!(redirect_to: submission['redirect_to'])
end end
if user.custom_fields['redirect_to_wizard'] === wizard.id wizard.final_cleanup!
user.custom_fields.delete('redirect_to_wizard')
user.save_custom_fields(true)
end
end end
render json: result render json: result

Datei anzeigen

@ -1,5 +1,5 @@
{ {
"result": { "result": {
"line": 89.56 "line": 90.52
} }
} }

Datei anzeigen

@ -1,40 +0,0 @@
# frozen_string_literal: true
module CustomWizardFieldExtension
attr_reader :raw,
:label,
:description,
:image,
:key,
:validations,
:min_length,
:max_length,
:char_counter,
:file_types,
:format,
:limit,
:property,
:content,
:number
def initialize(attrs)
super
@raw = attrs || {}
@description = attrs[:description]
@image = attrs[:image]
@key = attrs[:key]
@validations = attrs[:validations]
@min_length = attrs[:min_length]
@max_length = attrs[:max_length]
@char_counter = attrs[:char_counter]
@file_types = attrs[:file_types]
@format = attrs[:format]
@limit = attrs[:limit]
@property = attrs[:property]
@content = attrs[:content]
@number = attrs[:number]
end
def label
@label ||= PrettyText.cook(@raw[:label])
end
end

Datei anzeigen

@ -1,4 +0,0 @@
# frozen_string_literal: true
module CustomWizardStepExtension
attr_accessor :title, :description, :key, :permitted, :permitted_message
end

Datei anzeigen

@ -6,12 +6,12 @@ class CustomWizard::Action
:guardian, :guardian,
:result :result
def initialize(params) def initialize(opts)
@wizard = params[:wizard] @wizard = opts[:wizard]
@action = params[:action] @action = opts[:action]
@user = params[:user] @user = @wizard.user
@guardian = Guardian.new(@user) @guardian = Guardian.new(@user)
@data = params[:data] @data = opts[:data]
@log = [] @log = []
@result = CustomWizard::ActionResult.new @result = CustomWizard::ActionResult.new
end end
@ -89,11 +89,13 @@ class CustomWizard::Action
return return
end end
params[:target_group_names] = []
params[:target_usernames] = []
targets.each do |target| targets.each do |target|
if Group.find_by(name: target) if Group.find_by(name: target)
params[:target_group_names] = target params[:target_group_names] << target
elsif User.find_by_username(target) elsif User.find_by_username(target)
params[:target_usernames] = target params[:target_usernames] << target
else else
# #
end end
@ -497,7 +499,13 @@ class CustomWizard::Action
).perform ).perform
params[:raw] = action['post_builder'] ? params[:raw] = action['post_builder'] ?
mapper.interpolate(action['post_template']) : mapper.interpolate(
action['post_template'],
user: true,
value: true,
wizard: true,
template: true
) :
data[action['post']] data[action['post']]
params[:import_mode] = ActiveRecord::Type::Boolean.new.cast(action['suppress_notifications']) params[:import_mode] = ActiveRecord::Type::Boolean.new.cast(action['suppress_notifications'])

Datei anzeigen

@ -1,15 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
class CustomWizard::Builder class CustomWizard::Builder
attr_accessor :wizard, :updater, :submissions attr_accessor :wizard, :updater, :template
def initialize(wizard_id, user = nil) def initialize(wizard_id, user = nil)
template = CustomWizard::Template.find(wizard_id) @template = CustomWizard::Template.create(wizard_id)
return nil if template.blank? return nil if @template.nil?
@wizard = CustomWizard::Wizard.new(template.data, user)
@wizard = CustomWizard::Wizard.new(template, user)
@steps = template['steps'] || []
@actions = template['actions'] || []
@submissions = @wizard.submissions
end end
def self.sorted_handlers def self.sorted_handlers
@ -28,7 +24,7 @@ class CustomWizard::Builder
def mapper def mapper
CustomWizard::Mapper.new( CustomWizard::Mapper.new(
user: @wizard.user, user: @wizard.user,
data: @submissions.last data: @wizard.current_submission
) )
end end
@ -38,103 +34,38 @@ class CustomWizard::Builder
build_opts[:reset] = build_opts[:reset] || @wizard.restart_on_revisit build_opts[:reset] = build_opts[:reset] || @wizard.restart_on_revisit
@steps.each do |step_template| @template.steps.each do |step_template|
next if !check_condition(step_template)
@wizard.append_step(step_template['id']) do |step| @wizard.append_step(step_template['id']) do |step|
step.permitted = true step = check_if_permitted(step, step_template)
next if !step.permitted
if step_template['required_data'] save_permitted_params(step_template, params)
step = ensure_required_data(step, step_template) step = add_step_attributes(step, step_template)
end step = append_step_fields(step, step_template, build_opts)
if !step.permitted
if step_template['required_data_message']
step.permitted_message = step_template['required_data_message']
end
next
end
step.title = step_template['title'] if step_template['title']
step.banner = step_template['banner'] if step_template['banner']
step.key = step_template['key'] if step_template['key']
if step_template['description']
step.description = mapper.interpolate(
step_template['description'],
user: true,
value: true
)
end
if permitted_params = step_template['permitted_params']
save_permitted_params(permitted_params, params)
end
if step_template['fields'] && step_template['fields'].length
step_template['fields'].each_with_index do |field_template, index|
append_field(step, step_template, field_template, build_opts, index)
end
end
step.on_update do |updater| step.on_update do |updater|
@updater = updater @updater = updater
user = @wizard.user @submission = (@wizard.current_submission || {})
.merge(@updater.submission)
.with_indifferent_access
updater.validate @updater.validate
next if @updater.errors.any?
next if updater.errors.any? apply_step_handlers
next if @updater.errors.any?
CustomWizard::Builder.step_handlers.each do |handler| run_step_actions
if handler[:wizard_id] == @wizard.id
handler[:block].call(self)
end
end
next if updater.errors.any? if @updater.errors.empty?
if route_to = @submission['route_to']
submission = updater.submission @submission.delete('route_to')
if current_submission = @wizard.current_submission
submission = current_submission.merge(submission)
end
final_step = updater.step.next.nil?
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(
wizard: @wizard,
action: action,
user: user,
data: submission
).perform
end
end
end
if updater.errors.empty?
if route_to = submission['route_to']
submission.delete('route_to')
end end
if @wizard.save_submissions @wizard.save_submission(@submission)
save_submissions(submission, final_step) @updater.result[:redirect_on_next] = route_to if route_to
end
if final_step
if @wizard.id == @wizard.user.custom_fields['redirect_to_wizard']
@wizard.user.custom_fields.delete('redirect_to_wizard')
@wizard.user.save_custom_fields(true)
end
redirect_url = route_to || submission['redirect_on_complete'] || submission["redirect_to"]
updater.result[:redirect_on_complete] = redirect_url
elsif route_to
updater.result[:redirect_on_next] = route_to
end
true true
else else
@ -144,25 +75,21 @@ class CustomWizard::Builder
end end
end end
@wizard.update_step_order!
@wizard @wizard
end end
def append_field(step, step_template, field_template, build_opts, index) def append_field(step, step_template, field_template, build_opts)
params = { params = {
id: field_template['id'], id: field_template['id'],
type: field_template['type'], type: field_template['type'],
required: field_template['required'], required: field_template['required']
number: index + 1
} }
params[:label] = field_template['label'] if field_template['label'] %w(label description image key validations min_length max_length char_counter).each do |key|
params[:description] = field_template['description'] if field_template['description'] params[key.to_sym] = field_template[key] if field_template[key]
params[:image] = field_template['image'] if field_template['image'] end
params[:key] = field_template['key'] if field_template['key']
params[:validations] = field_template['validations'] if field_template['validations']
params[:min_length] = field_template['min_length'] if field_template['min_length']
params[:max_length] = field_template['max_length'] if field_template['max_length']
params[:char_counter] = field_template['char_counter'] if field_template['char_counter']
params[:value] = prefill_field(field_template, step_template) params[:value] = prefill_field(field_template, step_template)
if !build_opts[:reset] && (submission = @wizard.current_submission) if !build_opts[:reset] && (submission = @wizard.current_submission)
@ -209,7 +136,7 @@ class CustomWizard::Builder
content = CustomWizard::Mapper.new( content = CustomWizard::Mapper.new(
inputs: content_inputs, inputs: content_inputs,
user: @wizard.user, user: @wizard.user,
data: @submissions.last, data: @wizard.current_submission,
opts: { opts: {
with_type: true with_type: true
} }
@ -240,6 +167,26 @@ class CustomWizard::Builder
end end
end end
if field_template['index'].present?
index = CustomWizard::Mapper.new(
inputs: field_template['index'],
user: @wizard.user,
data: @wizard.current_submission
).perform
params[:index] = index.to_i unless index.nil?
end
if field_template['description'].present?
params[:description] = mapper.interpolate(
field_template['description'],
user: true,
value: true,
wizard: true,
template: true
)
end
field = step.add_field(params) field = step.add_field(params)
end end
@ -248,41 +195,99 @@ class CustomWizard::Builder
CustomWizard::Mapper.new( CustomWizard::Mapper.new(
inputs: prefill, inputs: prefill,
user: @wizard.user, user: @wizard.user,
data: @submissions.last data: @wizard.current_submission
).perform ).perform
end end
end end
def check_condition(template)
if template['condition'].present?
result = CustomWizard::Mapper.new(
inputs: template['condition'],
user: @wizard.user,
data: @wizard.current_submission,
opts: {
multiple: true
}
).perform
result.any?
else
true
end
end
def check_if_permitted(step, step_template)
step.permitted = true
if step_template['required_data']
step = ensure_required_data(step, step_template)
end
if !step.permitted
if step_template['required_data_message']
step.permitted_message = step_template['required_data_message']
end
end
step
end
def add_step_attributes(step, step_template)
%w(index title banner key force_final).each do |attr|
step.send("#{attr}=", step_template[attr]) if step_template[attr]
end
if step_template['description']
step.description = mapper.interpolate(
step_template['description'],
user: true,
value: true,
wizard: true,
template: true
)
step.description = PrettyText.cook(step.description)
end
step
end
def append_step_fields(step, step_template, build_opts)
if step_template['fields'] && step_template['fields'].length
step_template['fields'].each do |field_template|
next if !check_condition(field_template)
append_field(step, step_template, field_template, build_opts)
end
end
step.update_field_order!
step
end
def standardise_boolean(value) def standardise_boolean(value)
ActiveRecord::Type::Boolean.new.cast(value) ActiveRecord::Type::Boolean.new.cast(value)
end end
def save_submissions(submission, final_step) def save_permitted_params(step_template, params)
if final_step return unless step_template['permitted_params'].present?
submission['submitted_at'] = Time.now.iso8601
end
if submission.present? permitted_params = step_template['permitted_params']
@submissions.pop(1) if @wizard.unfinished?
@submissions.push(submission)
@wizard.set_submissions(@submissions)
end
end
def save_permitted_params(permitted_params, params)
permitted_data = {} permitted_data = {}
submission_key = nil
params_key = nil
submission = @wizard.current_submission || {}
permitted_params.each do |pp| permitted_params.each do |pp|
pair = pp['pairs'].first pair = pp['pairs'].first
params_key = pair['key'].to_sym params_key = pair['key'].to_sym
submission_key = pair['value'].to_sym submission_key = pair['value'].to_sym
permitted_data[submission_key] = params[params_key] if params[params_key]
if submission_key && params_key
submission[submission_key] = params[params_key]
end
end end
if permitted_data.present? @wizard.save_submission(submission)
current_data = @submissions.last || {}
save_submissions(current_data.merge(permitted_data), false)
end
end end
def ensure_required_data(step, step_template) def ensure_required_data(step, step_template)
@ -291,13 +296,13 @@ class CustomWizard::Builder
pair['key'].present? && pair['value'].present? pair['key'].present? && pair['value'].present?
end end
if pairs.any? && !@submissions.last if pairs.any? && !@wizard.current_submission
step.permitted = false step.permitted = false
break break
end end
pairs.each do |pair| pairs.each do |pair|
pair['key'] = @submissions.last[pair['key']] pair['key'] = @wizard.current_submission[pair['key']]
end end
if !mapper.validate_pairs(pairs) if !mapper.validate_pairs(pairs)
@ -308,4 +313,26 @@ class CustomWizard::Builder
step step
end end
def apply_step_handlers
CustomWizard::Builder.step_handlers.each do |handler|
if handler[:wizard_id] == @wizard.id
handler[:block].call(self)
end
end
end
def run_step_actions
if @template.actions.present?
@template.actions.each do |action_template|
if action_template['run_after'] === updater.step.id
CustomWizard::Action.new(
action: action_template,
wizard: @wizard,
data: @submission
).perform
end
end
end
end
end end

Datei anzeigen

@ -1,5 +1,55 @@
# frozen_string_literal: true # frozen_string_literal: true
class CustomWizard::Field class CustomWizard::Field
include ActiveModel::SerializerSupport
attr_reader :raw,
:id,
:type,
:required,
:value,
:label,
:description,
:image,
:key,
:validations,
:min_length,
:max_length,
:char_counter,
:file_types,
:format,
:limit,
:property,
:content
attr_accessor :index,
:step
def initialize(attrs)
@raw = attrs || {}
@id = attrs[:id]
@index = attrs[:index]
@type = attrs[:type]
@required = !!attrs[:required]
@value = attrs[:value]
@description = attrs[:description]
@image = attrs[:image]
@key = attrs[:key]
@validations = attrs[:validations]
@min_length = attrs[:min_length]
@max_length = attrs[:max_length]
@char_counter = attrs[:char_counter]
@file_types = attrs[:file_types]
@format = attrs[:format]
@limit = attrs[:limit]
@property = attrs[:property]
@content = attrs[:content]
end
def label
@label ||= PrettyText.cook(@raw[:label])
end
def self.types def self.types
@types ||= { @types ||= {
text: { text: {

56
lib/custom_wizard/step.rb Normale Datei
Datei anzeigen

@ -0,0 +1,56 @@
# frozen_string_literal: true
class CustomWizard::Step
include ActiveModel::SerializerSupport
attr_reader :id,
:updater
attr_accessor :index,
:title,
:description,
:key,
:permitted,
:permitted_message,
:fields,
:next,
:previous,
:banner,
:disabled,
:description_vars,
:last_step,
:force_final,
:conditional_final_step,
:wizard
def initialize(id)
@id = id
@fields = []
end
def add_field(attrs)
field = ::CustomWizard::Field.new(attrs)
field.index = (@fields.size == 1 ? 0 : @fields.size) if field.index.nil?
field.step = self
@fields << field
field
end
def has_fields?
@fields.present?
end
def on_update(&block)
@updater = block
end
def update_field_order!
@fields.sort_by!(&:index)
end
def final?
return true if force_final && conditional_final_step
return true if last_step
false
end
end

Datei anzeigen

@ -2,14 +2,15 @@
class CustomWizard::StepUpdater class CustomWizard::StepUpdater
include ActiveModel::Model include ActiveModel::Model
attr_accessor :refresh_required, :submission, :result, :step attr_accessor :refresh_required, :result
attr_reader :step, :submission
def initialize(current_user, wizard, step, submission) def initialize(current_user, wizard, step, submission)
@current_user = current_user @current_user = current_user
@wizard = wizard @wizard = wizard
@step = step @step = step
@refresh_required = false @refresh_required = false
@submission = submission.to_h.with_indifferent_access @submission = submission.with_indifferent_access
@result = {} @result = {}
end end

Datei anzeigen

@ -4,10 +4,14 @@ class CustomWizard::Template
include HasErrors include HasErrors
attr_reader :data, attr_reader :data,
:opts :opts,
:steps,
:actions
def initialize(data) def initialize(data)
@data = data @data = data
@steps = data['steps'] || []
@actions = data['actions'] || []
end end
def save(opts = {}) def save(opts = {})
@ -31,6 +35,14 @@ class CustomWizard::Template
new(data).save(opts) new(data).save(opts)
end end
def self.create(wizard_id)
if data = find(wizard_id)
new(data)
else
nil
end
end
def self.find(wizard_id) def self.find(wizard_id)
PluginStore.get(CustomWizard::PLUGIN_NAME, wizard_id) PluginStore.get(CustomWizard::PLUGIN_NAME, wizard_id)
end end
@ -86,7 +98,13 @@ class CustomWizard::Template
def prepare_data def prepare_data
@data[:steps].each do |step| @data[:steps].each do |step|
if step[:raw_description] if step[:raw_description]
step[:description] = PrettyText.cook(step[:raw_description]) step[:description] = step[:raw_description]
end
remove_non_mapped_index(step)
step[:fields].each do |field|
remove_non_mapped_index(field)
end end
end end
end end
@ -118,4 +136,10 @@ class CustomWizard::Template
end end
end end
end end
def remove_non_mapped_index(object)
if !object[:index].is_a?(Array)
object.delete(:index)
end
end
end end

Datei anzeigen

@ -26,9 +26,10 @@ class CustomWizard::Wizard
:needs_groups, :needs_groups,
:steps, :steps,
:step_ids, :step_ids,
:first_step,
:start,
:actions, :actions,
:user, :user
:first_step
def initialize(attrs = {}, user = nil) def initialize(attrs = {}, user = nil)
@user = user @user = user
@ -68,8 +69,8 @@ class CustomWizard::Wizard
val.nil? ? false : ActiveRecord::Type::Boolean.new.cast(val) val.nil? ? false : ActiveRecord::Type::Boolean.new.cast(val)
end end
def create_step(step_name) def create_step(step_id)
::Wizard::Step.new(step_name) ::CustomWizard::Step.new(step_id)
end end
def append_step(step) def append_step(step)
@ -77,37 +78,58 @@ class CustomWizard::Wizard
yield step if block_given? yield step if block_given?
last_step = steps.last
steps << step steps << step
step.wizard = self
step.index = (steps.size == 1 ? 0 : steps.size) if step.index.nil?
end
if steps.size == 1 def update_step_order!
@first_step = step steps.sort_by!(&:index)
step.index = 0
elsif last_step.present? steps.each_with_index do |step, index|
last_step.next = step if index === 0
step.previous = last_step @first_step = step
step.index = last_step.index + 1 @start = step.id
else
last_step = steps[index - 1]
last_step.next = step
step.previous = last_step
end
step.index = index
if index === (steps.length - 1)
step.conditional_final_step = true
end
if index === (step_ids.length - 1)
step.last_step = true
end
if step.previous && step.previous.id === last_completed_step_id
@start = step.id
end
end end
end end
def start def last_completed_step_id
return nil if !user if user && unfinished? && last_completed_step = ::UserHistory.where(
if unfinished? && last_completed_step = ::UserHistory.where(
acting_user_id: user.id, acting_user_id: user.id,
action: ::UserHistory.actions[:custom_wizard_step], action: ::UserHistory.actions[:custom_wizard_step],
context: id, context: id,
subject: steps.map(&:id) subject: step_ids
).order("created_at").last ).order("created_at").last
step_id = last_completed_step.subject last_completed_step.subject
last_index = steps.index { |s| s.id == step_id }
steps[last_index + 1]
else else
@first_step nil
end end
end end
def find_step(step_id)
steps.select { |step| step.id === step_id }.first
end
def create_updater(step_id, submission) def create_updater(step_id, submission)
step = @steps.find { |s| s.id == step_id } step = @steps.find { |s| s.id == step_id }
wizard = self wizard = self
@ -200,12 +222,13 @@ class CustomWizard::Wizard
end end
def submissions def submissions
Array.wrap(PluginStore.get("#{id}_submissions", user.id)) return nil unless user.present?
@submissions ||= Array.wrap(PluginStore.get("#{id}_submissions", user.id))
end end
def current_submission def current_submission
if submissions.present? && !submissions.last.key?("submitted_at") if submissions.present? && submissions.last.present? && !submissions.last.key?("submitted_at")
submissions.last submissions.last.with_indifferent_access
else else
nil nil
end end
@ -213,6 +236,27 @@ class CustomWizard::Wizard
def set_submissions(submissions) def set_submissions(submissions)
PluginStore.set("#{id}_submissions", user.id, Array.wrap(submissions)) PluginStore.set("#{id}_submissions", user.id, Array.wrap(submissions))
@submissions = nil
end
def save_submission(submission)
return nil unless save_submissions
submissions.pop(1) if unfinished?
submissions.push(submission)
set_submissions(submissions)
end
def final_cleanup!
if id == user.custom_fields['redirect_to_wizard']
user.custom_fields.delete('redirect_to_wizard')
user.save_custom_fields(true)
end
if submission = current_submission
submission['submitted_at'] = Time.now.iso8601
save_submission(submission)
end
end end
def self.submissions(wizard_id, user) def self.submissions(wizard_id, user)
@ -276,7 +320,7 @@ class CustomWizard::Wizard
end end
def self.set_submission_redirect(user, wizard_id, url) def self.set_submission_redirect(user, wizard_id, url)
PluginStore.set("#{wizard_id.underscore}_submissions", user.id, [{ redirect_to: url }]) set_submissions(wizard_id, user, [{ redirect_to: url }])
end end
def self.set_wizard_redirect(wizard_id, user) def self.set_wizard_redirect(wizard_id, user)

Datei anzeigen

@ -66,6 +66,7 @@ after_initialize do
../lib/custom_wizard/mapper.rb ../lib/custom_wizard/mapper.rb
../lib/custom_wizard/log.rb ../lib/custom_wizard/log.rb
../lib/custom_wizard/step_updater.rb ../lib/custom_wizard/step_updater.rb
../lib/custom_wizard/step.rb
../lib/custom_wizard/template.rb ../lib/custom_wizard/template.rb
../lib/custom_wizard/wizard.rb ../lib/custom_wizard/wizard.rb
../lib/custom_wizard/api/api.rb ../lib/custom_wizard/api/api.rb
@ -89,8 +90,6 @@ after_initialize do
../extensions/extra_locales_controller.rb ../extensions/extra_locales_controller.rb
../extensions/invites_controller.rb ../extensions/invites_controller.rb
../extensions/users_controller.rb ../extensions/users_controller.rb
../extensions/wizard_field.rb
../extensions/wizard_step.rb
../extensions/custom_field/preloader.rb ../extensions/custom_field/preloader.rb
../extensions/custom_field/serializer.rb ../extensions/custom_field/serializer.rb
].each do |path| ].each do |path|
@ -172,8 +171,6 @@ after_initialize do
::ExtraLocalesController.prepend ExtraLocalesControllerCustomWizard ::ExtraLocalesController.prepend ExtraLocalesControllerCustomWizard
::InvitesController.prepend InvitesControllerCustomWizard ::InvitesController.prepend InvitesControllerCustomWizard
::UsersController.prepend CustomWizardUsersController ::UsersController.prepend CustomWizardUsersController
::Wizard::Field.prepend CustomWizardFieldExtension
::Wizard::Step.prepend CustomWizardStepExtension
full_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets/stylesheets/wizard/wizard_custom.scss" full_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets/stylesheets/wizard/wizard_custom.scss"
if Stylesheet::Importer.respond_to?(:plugin_assets) if Stylesheet::Importer.respond_to?(:plugin_assets)

Datei anzeigen

@ -1,8 +1,16 @@
# frozen_string_literal: true # frozen_string_literal: true
class CustomWizard::FieldSerializer < ::WizardFieldSerializer class CustomWizard::FieldSerializer < ::ApplicationSerializer
attributes :image, attributes :id,
:index,
:type,
:required,
:value,
:label,
:placeholder,
:description,
:image,
:file_types, :file_types,
:format, :format,
:limit, :limit,
@ -10,19 +18,54 @@ class CustomWizard::FieldSerializer < ::WizardFieldSerializer
:content, :content,
:validations, :validations,
:max_length, :max_length,
:char_counter, :char_counter
:number
def id
object.id
end
def index
object.index
end
def type
object.type
end
def required
object.required
end
def value
object.value
end
def include_value?
object.value.present?
end
def i18n_key
@i18n_key ||= "wizard.step.#{object.step.id}.fields.#{object.id}".underscore
end
def label def label
return object.label if object.label.present? return object.label if object.label.present?
I18n.t("#{object.key || i18n_key}.label", default: '') I18n.t("#{object.key || i18n_key}.label", default: '')
end end
def include_label?
label.present?
end
def description def description
return object.description if object.description.present? return object.description if object.description.present?
I18n.t("#{object.key || i18n_key}.description", default: '', base_url: Discourse.base_url) I18n.t("#{object.key || i18n_key}.description", default: '', base_url: Discourse.base_url)
end end
def include_description?
description.present?
end
def image def image
object.image object.image
end end
@ -35,6 +78,10 @@ class CustomWizard::FieldSerializer < ::WizardFieldSerializer
I18n.t("#{object.key || i18n_key}.placeholder", default: '') I18n.t("#{object.key || i18n_key}.placeholder", default: '')
end end
def include_placeholder?
placeholder.present?
end
def file_types def file_types
object.file_types object.file_types
end end
@ -55,10 +102,6 @@ class CustomWizard::FieldSerializer < ::WizardFieldSerializer
object.content object.content
end end
def include_choices?
object.choices.present?
end
def validations def validations
validations = {} validations = {}
object.validations&.each do |type, props| object.validations&.each do |type, props|
@ -77,8 +120,4 @@ class CustomWizard::FieldSerializer < ::WizardFieldSerializer
def char_counter def char_counter
object.char_counter object.char_counter
end end
def number
object.number
end
end end

Datei anzeigen

@ -30,7 +30,7 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer
end end
def start def start
object.start.id object.start
end end
def include_start? def include_start?

Datei anzeigen

@ -1,20 +1,74 @@
# frozen_string_literal: true # frozen_string_literal: true
class CustomWizard::StepSerializer < ::WizardStepSerializer class CustomWizard::StepSerializer < ::ApplicationSerializer
attributes :id,
:index,
:next,
:previous,
:description,
:title,
:banner,
:permitted,
:permitted_message,
:final
attributes :permitted, :permitted_message
has_many :fields, serializer: ::CustomWizard::FieldSerializer, embed: :objects has_many :fields, serializer: ::CustomWizard::FieldSerializer, embed: :objects
def id
object.id
end
def index
object.index
end
def next
object.next.id if object.next.present?
end
def include_next?
object.next.present?
end
def previous
object.previous.id if object.previous.present?
end
def include_previous?
object.previous.present?
end
def i18n_key
@i18n_key ||= "wizard.step.#{object.id}".underscore
end
def title def title
return PrettyText.cook(object.title) if object.title return PrettyText.cook(object.title) if object.title
PrettyText.cook(I18n.t("#{object.key || i18n_key}.title", default: '')) PrettyText.cook(I18n.t("#{object.key || i18n_key}.title", default: ''))
end end
def include_title?
title.present?
end
def description def description
return object.description if object.description return object.description if object.description
PrettyText.cook(I18n.t("#{object.key || i18n_key}.description", default: '', base_url: Discourse.base_url)) PrettyText.cook(I18n.t("#{object.key || i18n_key}.description", default: '', base_url: Discourse.base_url))
end end
def include_description?
description.present?
end
def banner
object.banner
end
def include_banner?
object.banner.present?
end
def permitted def permitted
object.permitted object.permitted
end end
@ -22,4 +76,8 @@ class CustomWizard::StepSerializer < ::WizardStepSerializer
def permitted_message def permitted_message
object.permitted_message object.permitted_message
end end
def final
object.final?
end
end end

Datei anzeigen

@ -6,19 +6,25 @@ describe CustomWizard::Action do
fab!(:category) { Fabricate(:category, name: 'cat1', slug: 'cat-slug') } fab!(:category) { Fabricate(:category, name: 'cat1', slug: 'cat-slug') }
fab!(:group) { Fabricate(:group) } fab!(:group) { Fabricate(:group) }
let(:wizard_template) {
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read
)
}
let(:open_composer) { let(:open_composer) {
JSON.parse(File.open( JSON.parse(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/actions/open_composer.json" File.open(
).read) "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/actions/open_composer.json"
).read
)
} }
before do before do
Group.refresh_automatic_group!(:trust_level_2) Group.refresh_automatic_group!(:trust_level_2)
CustomWizard::Template.save( CustomWizard::Template.save(wizard_template, skip_jobs: true)
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read),
skip_jobs: true)
@template = CustomWizard::Template.find('super_mega_fun_wizard') @template = CustomWizard::Template.find('super_mega_fun_wizard')
end end
@ -68,26 +74,52 @@ describe CustomWizard::Action do
end end
end end
it 'sends a message' do context 'sending a message' do
User.create(username: 'angus1', email: "angus1@email.com") it 'works' do
User.create(username: 'angus1', email: "angus1@email.com")
wizard = CustomWizard::Builder.new(@template[:id], user).build wizard = CustomWizard::Builder.new(@template[:id], user).build
wizard.create_updater(wizard.steps[0].id, {}).update wizard.create_updater(wizard.steps[0].id, {}).update
wizard.create_updater(wizard.steps[1].id, {}).update wizard.create_updater(wizard.steps[1].id, {}).update
topic = Topic.where( topic = Topic.where(
archetype: Archetype.private_message, archetype: Archetype.private_message,
title: "Message title" title: "Message title"
) )
post = Post.where( post = Post.where(
topic_id: topic.pluck(:id), topic_id: topic.pluck(:id),
raw: "I will interpolate some wizard fields" raw: "I will interpolate some wizard fields"
) )
expect(topic.exists?).to eq(true) expect(topic.exists?).to eq(true)
expect(topic.first.topic_allowed_users.first.user.username).to eq('angus1') expect(topic.first.topic_allowed_users.first.user.username).to eq('angus1')
expect(post.exists?).to eq(true) expect(post.exists?).to eq(true)
end
it 'allows using multiple PM targets' do
User.create(username: 'angus1', email: "angus1@email.com")
User.create(username: 'faiz', email: "faiz@email.com")
Group.create(name: "cool_group")
Group.create(name: 'cool_group_1')
wizard = CustomWizard::Builder.new(@template[:id], user).build
wizard.create_updater(wizard.steps[0].id, {}).update
wizard.create_updater(wizard.steps[1].id, {}).update
topic = Topic.where(
archetype: Archetype.private_message,
title: "Multiple Recipients title"
)
post = Post.where(
topic_id: topic.pluck(:id),
raw: "I will interpolate some wizard fields"
)
expect(topic.exists?).to eq(true)
expect(topic.first.all_allowed_users.map(&:username)).to include('angus1', 'faiz')
expect(topic.first.allowed_groups.map(&:name)).to include('cool_group', 'cool_group_1')
expect(post.exists?).to eq(true)
end
end end
it 'updates a profile' do it 'updates a profile' do
@ -130,7 +162,6 @@ describe CustomWizard::Action do
action = CustomWizard::Action.new( action = CustomWizard::Action.new(
wizard: wizard, wizard: wizard,
action: open_composer, action: open_composer,
user: user,
data: {} data: {}
) )
action.perform action.perform
@ -153,8 +184,7 @@ describe CustomWizard::Action do
it 'creates a group' do it 'creates a group' do
wizard = CustomWizard::Builder.new(@template[:id], user).build wizard = CustomWizard::Builder.new(@template[:id], user).build
step_id = wizard.steps[0].id wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update
updater = wizard.create_updater(step_id, step_1_field_1: "Text input").update
expect(Group.where(name: wizard.current_submission['action_9']).exists?).to eq(true) expect(Group.where(name: wizard.current_submission['action_9']).exists?).to eq(true)
end end
@ -184,6 +214,6 @@ describe CustomWizard::Action do
wizard = CustomWizard::Builder.new(@template[:id], user).build wizard = CustomWizard::Builder.new(@template[:id], user).build
updater = wizard.create_updater(wizard.steps.last.id, {}) updater = wizard.create_updater(wizard.steps.last.id, {})
updater.update updater.update
expect(updater.result[:redirect_on_complete]).to eq("https://google.com") expect(updater.result[:redirect_on_next]).to eq("https://google.com")
end end
end end

Datei anzeigen

@ -16,21 +16,35 @@ describe CustomWizard::Builder do
fab!(:group) { Fabricate(:group) } fab!(:group) { Fabricate(:group) }
let(:required_data_json) { let(:required_data_json) {
JSON.parse(File.open( JSON.parse(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/required_data.json" File.open(
).read) "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/required_data.json"
).read
)
} }
let(:permitted_json) { let(:permitted_json) {
JSON.parse(File.open( JSON.parse(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json" File.open(
).read) "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json"
).read
)
} }
let(:permitted_param_json) { let(:permitted_param_json) {
JSON.parse(File.open( JSON.parse(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/permitted_params.json" File.open(
).read) "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/permitted_params.json"
).read
)
}
let(:user_condition_json) {
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/condition/user_condition.json"
).read
)
} }
before do before do
@ -263,6 +277,23 @@ describe CustomWizard::Builder do
expect(wizard.current_submission['saved_param']).to eq('param_value') expect(wizard.current_submission['saved_param']).to eq('param_value')
end end
end end
context "with condition" do
before do
@template[:steps][0][:condition] = user_condition_json['condition']
CustomWizard::Template.save(@template.as_json)
end
it "adds step when condition is passed" do
wizard = CustomWizard::Builder.new(@template[:id], trusted_user).build
expect(wizard.steps.first.id).to eq(@template[:steps][0]['id'])
end
it "does not add step when condition is not passed" do
wizard = CustomWizard::Builder.new(@template[:id], user).build
expect(wizard.steps.first.id).to eq(@template[:steps][1]['id'])
end
end
end end
context 'building field' do context 'building field' do
@ -284,6 +315,23 @@ describe CustomWizard::Builder do
.fields.length .fields.length
).to eq(4) ).to eq(4)
end end
context "with condition" do
before do
@template[:steps][0][:fields][0][:condition] = user_condition_json['condition']
CustomWizard::Template.save(@template.as_json)
end
it "adds field when condition is passed" do
wizard = CustomWizard::Builder.new(@template[:id], trusted_user).build
expect(wizard.steps.first.fields.first.id).to eq(@template[:steps][0][:fields][0]['id'])
end
it "does not add field when condition is not passed" do
wizard = CustomWizard::Builder.new(@template[:id], user).build
expect(wizard.steps.first.fields.first.id).to eq(@template[:steps][0][:fields][1]['id'])
end
end
end end
context 'on update' do context 'on update' do

Datei anzeigen

@ -2,6 +2,12 @@
require_relative '../../plugin_helper' require_relative '../../plugin_helper'
describe CustomWizard::Field do describe CustomWizard::Field do
let(:field_hash) do
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/field/field.json"
).read).with_indifferent_access
end
before do before do
CustomWizard::Field.register( CustomWizard::Field.register(
'location', 'location',
@ -13,6 +19,19 @@ describe CustomWizard::Field do
) )
end end
it "initialize custom field attributes" do
field = CustomWizard::Field.new(field_hash)
expect(field.id).to eq("field_id")
expect(field.index).to eq(0)
expect(field.label).to eq("<p>Field Label</p>")
expect(field.image).to eq("field_image_url.png")
expect(field.description).to eq("Field description")
expect(field.required).to eq(true)
expect(field.key).to eq("field.locale.key")
expect(field.type).to eq("field_type")
expect(field.content).to eq([])
end
it "registers custom field types" do it "registers custom field types" do
expect(CustomWizard::Field.types[:location].present?).to eq(true) expect(CustomWizard::Field.types[:location].present?).to eq(true)
end end

Datei anzeigen

@ -130,21 +130,63 @@ describe CustomWizard::Mapper do
end end
end end
it "validates valid data" do context "conditional validation" do
expect(CustomWizard::Mapper.new( it "validates valid data" do
inputs: inputs['validation'], expect(CustomWizard::Mapper.new(
data: data, inputs: inputs['validation'],
user: user1 data: data,
).perform).to eq(true) user: user1
end ).perform).to eq(true)
end
it "does not validate invalid data" do it "does not validate invalid data" do
data["input_2"] = "value 3" data["input_2"] = "value 3"
expect(CustomWizard::Mapper.new( expect(CustomWizard::Mapper.new(
inputs: inputs['validation'], inputs: inputs['validation'],
data: data, data: data,
user: user1 user: user1
).perform).to eq(false) ).perform).to eq(false)
end
context "using or condition" do
it "validates the data when all of the conditions are met" do
expect(CustomWizard::Mapper.new(
inputs: inputs['validation_multiple_pairs'],
data: data,
user: user1,
opts: {
multiple: true
}
).perform.any?).to eq(true)
end
it "validates the data when one of the conditions are met" do
custom_data = data.dup
custom_data['input_1'] = 'value 3'
expect(CustomWizard::Mapper.new(
inputs: inputs['validation_multiple_pairs'],
data: custom_data,
user: user1,
opts: {
multiple: true
}
).perform.any?).to eq(true)
end
it "doesn't validate the data when none of the conditions are met" do
custom_data = data.dup
custom_data['input_1'] = 'value 3'
custom_data['input_2'] = 'value 4'
expect(CustomWizard::Mapper.new(
inputs: inputs['validation_multiple_pairs'],
data: custom_data,
user: user1,
opts: {
multiple: true
}
).perform.any?).to eq(false)
end
end
end end
it "maps text fields" do it "maps text fields" do

Datei anzeigen

@ -0,0 +1,36 @@
# frozen_string_literal: true
require_relative '../../plugin_helper'
describe CustomWizard::Step do
let(:step_hash) do
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/step.json"
).read
).with_indifferent_access
end
let(:field_hash) do
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/field/field.json"
).read
).with_indifferent_access
end
before do
@step = CustomWizard::Step.new(step_hash[:id])
end
it "adds fields" do
@step.add_field(field_hash)
expect(@step.fields.size).to eq(1)
expect(@step.fields.first.index).to eq(0)
end
it "adds fields with custom indexes" do
field_hash[:index] = 2
@step.add_field(field_hash)
expect(@step.fields.first.index).to eq(2)
end
end

Datei anzeigen

@ -1,4 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative '../../plugin_helper' require_relative '../../plugin_helper'
describe CustomWizard::Wizard do describe CustomWizard::Wizard do
@ -7,27 +8,33 @@ describe CustomWizard::Wizard do
fab!(:admin_user) { Fabricate(:user, admin: true) } fab!(:admin_user) { Fabricate(:user, admin: true) }
let(:template_json) { let(:template_json) {
JSON.parse(File.open( JSON.parse(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" File.open(
).read) "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read
)
} }
let(:permitted_json) { let(:permitted_json) {
JSON.parse(File.open( JSON.parse(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json" File.open(
).read) "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json"
).read
)
} }
before do before do
Group.refresh_automatic_group!(:trust_level_3) Group.refresh_automatic_group!(:trust_level_3)
@permitted_template = template_json.dup @permitted_template = template_json.dup
@permitted_template["permitted"] = permitted_json["permitted"] @permitted_template["permitted"] = permitted_json["permitted"]
@wizard = CustomWizard::Wizard.new(template_json, user) @wizard = CustomWizard::Wizard.new(template_json, user)
end
def append_steps
template_json['steps'].each do |step_template| template_json['steps'].each do |step_template|
@wizard.append_step(step_template['id']) @wizard.append_step(step_template['id'])
end end
@wizard.update_step_order!
end end
def progress_step(step_id, acting_user: user, wizard: @wizard) def progress_step(step_id, acting_user: user, wizard: @wizard)
@ -37,16 +44,48 @@ describe CustomWizard::Wizard do
context: wizard.id, context: wizard.id,
subject: step_id subject: step_id
) )
@wizard.update_step_order!
end end
it "appends steps from a template" do it "appends steps" do
append_steps
expect(@wizard.steps.length).to eq(3) expect(@wizard.steps.length).to eq(3)
end end
it "appends steps with indexes" do
append_steps
expect(@wizard.steps.first.index).to eq(0)
expect(@wizard.steps.last.index).to eq(2)
end
it "appends steps with custom indexes" do
template_json['steps'][0]['index'] = 2
template_json['steps'][1]['index'] = 1
template_json['steps'][2]['index'] = 0
template_json['steps'].each do |step_template|
@wizard.append_step(step_template['id']) do |step|
step.index = step_template['index'] if step_template['index']
end
end
expect(@wizard.steps.first.index).to eq(2)
expect(@wizard.steps.last.index).to eq(0)
@wizard.update_step_order!
expect(@wizard.steps.first.id).to eq("step_3")
expect(@wizard.steps.last.id).to eq("step_1")
expect(@wizard.steps.first.next.id).to eq("step_2")
expect(@wizard.steps.last.next).to eq(nil)
end
it "determines the user's current step" do it "determines the user's current step" do
expect(@wizard.start.id).to eq('step_1') append_steps
expect(@wizard.start).to eq('step_1')
progress_step('step_1') progress_step('step_1')
expect(@wizard.start.id).to eq('step_2') expect(@wizard.start).to eq('step_2')
end end
it "creates a step updater" do it "creates a step updater" do
@ -57,6 +96,7 @@ describe CustomWizard::Wizard do
end end
it "determines whether a wizard is unfinished" do it "determines whether a wizard is unfinished" do
append_steps
expect(@wizard.unfinished?).to eq(true) expect(@wizard.unfinished?).to eq(true)
progress_step("step_1") progress_step("step_1")
expect(@wizard.unfinished?).to eq(true) expect(@wizard.unfinished?).to eq(true)
@ -67,6 +107,7 @@ describe CustomWizard::Wizard do
end end
it "determines whether a wizard has been completed by a user" do it "determines whether a wizard has been completed by a user" do
append_steps
expect(@wizard.completed?).to eq(false) expect(@wizard.completed?).to eq(false)
progress_step("step_1") progress_step("step_1")
progress_step("step_2") progress_step("step_2")
@ -75,6 +116,8 @@ describe CustomWizard::Wizard do
end end
it "is not completed if steps submitted before after time" do it "is not completed if steps submitted before after time" do
append_steps
progress_step("step_1") progress_step("step_1")
progress_step("step_2") progress_step("step_2")
progress_step("step_3") progress_step("step_3")
@ -83,7 +126,6 @@ describe CustomWizard::Wizard do
template_json['after_time_scheduled'] = Time.now + 3.hours template_json['after_time_scheduled'] = Time.now + 3.hours
wizard = CustomWizard::Wizard.new(template_json, user) wizard = CustomWizard::Wizard.new(template_json, user)
expect(wizard.completed?).to eq(false) expect(wizard.completed?).to eq(false)
end end
@ -125,6 +167,8 @@ describe CustomWizard::Wizard do
end end
it "lets a permitted user access a complete wizard with multiple submissions" do it "lets a permitted user access a complete wizard with multiple submissions" do
append_steps
progress_step("step_1", acting_user: trusted_user) progress_step("step_1", acting_user: trusted_user)
progress_step("step_2", acting_user: trusted_user) progress_step("step_2", acting_user: trusted_user)
progress_step("step_3", acting_user: trusted_user) progress_step("step_3", acting_user: trusted_user)
@ -135,6 +179,8 @@ describe CustomWizard::Wizard do
end end
it "does not let an unpermitted user access a complete wizard without multiple submissions" do it "does not let an unpermitted user access a complete wizard without multiple submissions" do
append_steps
progress_step("step_1", acting_user: trusted_user) progress_step("step_1", acting_user: trusted_user)
progress_step("step_2", acting_user: trusted_user) progress_step("step_2", acting_user: trusted_user)
progress_step("step_3", acting_user: trusted_user) progress_step("step_3", acting_user: trusted_user)

Datei anzeigen

@ -1,22 +0,0 @@
# frozen_string_literal: true
require_relative '../plugin_helper'
describe CustomWizardFieldExtension do
let(:field_hash) do
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/field/field.json"
).read).with_indifferent_access
end
it "adds custom field attributes" do
field = Wizard::Field.new(field_hash)
expect(field.id).to eq("field_id")
expect(field.label).to eq("<p>Field Label</p>")
expect(field.image).to eq("field_image_url.png")
expect(field.description).to eq("Field description")
expect(field.required).to eq(true)
expect(field.key).to eq("field.locale.key")
expect(field.type).to eq("field_type")
expect(field.content).to eq([])
end
end

Datei anzeigen

@ -1,24 +0,0 @@
# frozen_string_literal: true
require_relative '../plugin_helper'
describe CustomWizardStepExtension do
let(:step_hash) do
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/step.json"
).read).with_indifferent_access
end
it "adds custom step attributes" do
step = Wizard::Step.new(step_hash[:id])
[
:title,
:description,
:key,
:permitted,
:permitted_message
].each do |attr|
step.send("#{attr.to_s}=", step_hash[attr])
expect(step.send(attr)).to eq(step_hash[attr])
end
end
end

17
spec/fixtures/condition/user_condition.json gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,17 @@
{
"condition": [
{
"type": "validation",
"pairs": [
{
"index": 0,
"key": "username",
"key_type": "user_field",
"value": "angus",
"value_type": "text",
"connector": "equal"
}
]
}
]
}

Datei anzeigen

@ -0,0 +1,17 @@
{
"condition": [
{
"type": "validation",
"pairs": [
{
"index": 0,
"key": "step_1_field_1",
"key_type": "wizard_field",
"value": "Condition will pass",
"value_type": "text",
"connector": "equal"
}
]
}
]
}

Datei anzeigen

@ -1,5 +1,6 @@
{ {
"id": "field_id", "id": "field_id",
"index": 0,
"label": "Field Label", "label": "Field Label",
"image": "field_image_url.png", "image": "field_image_url.png",
"description": "Field description", "description": "Field description",

Datei anzeigen

@ -260,5 +260,34 @@
} }
] ]
} }
],
"validation_multiple_pairs": [
{
"type": "validation",
"pairs": [
{
"index": 0,
"key": "input_1",
"key_type": "wizard_field",
"value": "value 1",
"value_type": "text",
"connector": "equal"
}
]
},
{
"type": "validation",
"connector": "or",
"pairs": [
{
"index": 0,
"key": "input_2",
"key_type": "wizard_field",
"value": "value 2",
"value_type": "text",
"connector": "equal"
}
]
}
] ]
} }

Datei anzeigen

@ -1,5 +1,6 @@
{ {
"id": "step_1", "id": "step_1",
"index": 0,
"title": "Text", "title": "Text",
"description": "Step description", "description": "Step description",
"image": "step_image_url.png", "image": "step_image_url.png",

Datei anzeigen

@ -46,7 +46,7 @@
"type": "text_only" "type": "text_only"
} }
], ],
"description": "<p>Text inputs!</p>" "description": "Text inputs!"
}, },
{ {
"id": "step_2", "id": "step_2",
@ -93,7 +93,7 @@
"file_types": ".jpg,.jpeg,.png" "file_types": ".jpg,.jpeg,.png"
} }
], ],
"description": "<p>Because I couldnt think of another name for this step <img src=\"/images/emoji/twitter/slight_smile.png?v=9\" title=\":slight_smile:\" class=\"emoji\" alt=\":slight_smile:\"></p>" "description": "Because I couldn't think of another name for this step :)"
}, },
{ {
"id": "step_3", "id": "step_3",
@ -160,7 +160,7 @@
"type": "user_selector" "type": "user_selector"
} }
], ],
"description": "<p>Unfortunately not the edible type <img src=\"/images/emoji/twitter/sushi.png?v=9\" title=\":sushi:\" class=\"emoji\" alt=\":sushi:\"></p>" "description": "Unfortunately not the edible type :sushi: "
} }
], ],
"actions": [ "actions": [
@ -464,6 +464,34 @@
} }
] ]
}, },
{
"id": "action_11",
"run_after": "step_2",
"type": "send_message",
"post_builder": true,
"post_template": "I will interpolate some wizard fields w{step_1_field_1} w{step_1_field_2}",
"title": [
{
"type": "assignment",
"output": "Multiple Recipients title",
"output_type": "text",
"output_connector": "set"
}
],
"recipient": [
{
"type": "assignment",
"output_type": "user",
"output_connector": "set",
"output": [
"angus1",
"faiz",
"cool_group",
"cool_group_1"
]
}
]
},
{ {
"id": "action_3", "id": "action_3",
"run_after": "step_2", "run_after": "step_2",
@ -507,7 +535,7 @@
}, },
{ {
"id": "action_10", "id": "action_10",
"run_after": "wizard_completion", "run_after": "step_3",
"type": "route_to", "type": "route_to",
"url": [ "url": [
{ {

Datei anzeigen

@ -11,12 +11,49 @@ describe CustomWizard::StepsController do
) )
} }
before do fab!(:user2) {
CustomWizard::Template.save( Fabricate(
JSON.parse(File.open( :user,
username: 'bob',
email: "bob@email.com",
trust_level: TrustLevel[2]
)
}
let(:wizard_template) {
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read), ).read
skip_jobs: true) )
}
let(:wizard_field_condition_template) {
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/condition/wizard_field_condition.json"
).read
)
}
let(:user_condition_template) {
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/condition/user_condition.json"
).read
)
}
let(:permitted_json) {
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json"
).read
)
}
before do
CustomWizard::Template.save(wizard_template, skip_jobs: true)
sign_in(user) sign_in(user)
end end
@ -27,17 +64,189 @@ describe CustomWizard::StepsController do
} }
} }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_2")
wizard = CustomWizard::Builder.new("super_mega_fun_wizard", user).build wizard_id = response.parsed_body['wizard']['id']
expect(wizard.current_submission['step_1_field_1']).to eq("Text input") wizard = CustomWizard::Wizard.create(wizard_id, user)
expect(wizard.start.id).to eq("step_2") expect(wizard.submissions.last['step_1_field_1']).to eq("Text input")
end
context "raises an error" do
it "when the wizard doesnt exist" do
put '/w/not-super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(400)
end
it "when the user cant access the wizard" do
new_template = wizard_template.dup
new_template["permitted"] = permitted_json["permitted"]
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(403)
end
it "when the step doesnt exist" do
put '/w/super-mega-fun-wizard/steps/step_10.json'
expect(response.status).to eq(400)
end
it "when user cant see the step due to conditions" do
sign_in(user2)
new_wizard_template = wizard_template.dup
new_wizard_template['steps'][0]['condition'] = user_condition_template['condition']
CustomWizard::Template.save(new_wizard_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(403)
end
end end
it "works if the step has no fields" do it "works if the step has no fields" do
put '/w/super-mega-fun-wizard/steps/step_1.json' put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_2")
end
wizard = CustomWizard::Builder.new("super_mega_fun_wizard", user).build it "returns an updated wizard when condition passes" do
expect(wizard.start.id).to eq("step_2") new_template = wizard_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will pass"
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_2")
end
it "returns an updated wizard when condition doesnt pass" do
new_template = wizard_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition wont pass"
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_3")
end
it "runs completion actions if user has completed wizard" do
new_template = wizard_template.dup
## route_to action
new_template['actions'].last['run_after'] = 'wizard_completion'
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition wont pass"
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['redirect_on_complete']).to eq("https://google.com")
end
it "saves results of completion actions if user has completed wizard" do
new_template = wizard_template.dup
## Create group action
new_template['actions'].first['run_after'] = 'wizard_completion'
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "My cool group"
}
}
expect(response.status).to eq(200)
put '/w/super-mega-fun-wizard/steps/step_3.json'
expect(response.status).to eq(200)
wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user)
group_name = wizard.submissions.last['action_9']
group = Group.find_by(name: group_name)
expect(group.full_name).to eq("My cool group")
end
it "returns a final step without conditions" do
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_2.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_3.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
it "returns the correct final step when the conditional final step and last step are the same" do
new_template = wizard_template.dup
new_template['steps'][0]['condition'] = user_condition_template['condition']
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will pass"
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_2.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_3.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
it "returns the correct final step when the conditional final step and last step are different" do
new_template = wizard_template.dup
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will not pass"
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_2.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
it "returns the correct final step when the conditional final step is determined in the same action" do
new_template = wizard_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will not pass"
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end end
end end

Datei anzeigen

@ -20,9 +20,11 @@ describe CustomWizard::FieldSerializer do
each_serializer: CustomWizard::FieldSerializer, each_serializer: CustomWizard::FieldSerializer,
scope: Guardian.new(user) scope: Guardian.new(user)
).as_json ).as_json
expect(json_array.length).to eq(4)
expect(json_array.size).to eq(4)
expect(json_array[0][:label]).to eq("<p>Text</p>") expect(json_array[0][:label]).to eq("<p>Text</p>")
expect(json_array[0][:description]).to eq("Text field description.") expect(json_array[0][:description]).to eq("Text field description.")
expect(json_array[3][:index]).to eq(3)
end end
it "should return optional field attributes" do it "should return optional field attributes" do
@ -32,7 +34,6 @@ describe CustomWizard::FieldSerializer do
scope: Guardian.new(user) scope: Guardian.new(user)
).as_json ).as_json
expect(json_array[0][:format]).to eq("YYYY-MM-DD") expect(json_array[0][:format]).to eq("YYYY-MM-DD")
expect(json_array[3][:number]).to eq(4)
expect(json_array[6][:file_types]).to eq(".jpg,.jpeg,.png") expect(json_array[6][:file_types]).to eq(".jpg,.jpeg,.png")
end end
end end

Datei anzeigen

@ -1,59 +0,0 @@
# frozen_string_literal: true
require_relative '../../plugin_helper'
describe CustomWizard::StepSerializer do
fab!(:user) { Fabricate(:user) }
let(:required_data_json) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/required_data.json"
).read)
}
before do
CustomWizard::Template.save(
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read),
skip_jobs: true)
@wizard = CustomWizard::Builder.new("super_mega_fun_wizard", user).build
end
it 'should return basic step attributes' do
json_array = ActiveModel::ArraySerializer.new(
@wizard.steps,
each_serializer: CustomWizard::StepSerializer,
scope: Guardian.new(user)
).as_json
expect(json_array[0][:wizard_step][:title]).to eq("Text")
expect(json_array[0][:wizard_step][:description]).to eq("Text inputs!")
end
it 'should return fields' do
json_array = ActiveModel::ArraySerializer.new(
@wizard.steps,
each_serializer: CustomWizard::StepSerializer,
scope: Guardian.new(user)
).as_json
expect(json_array[0][:wizard_step][:fields].length).to eq(4)
end
context 'with required data' do
before do
@template[:steps][0][:required_data] = required_data_json['required_data']
@template[:steps][0][:required_data_message] = required_data_json['required_data_message']
CustomWizard::Template.save(@template.as_json)
end
it 'should return permitted attributes' do
json_array = ActiveModel::ArraySerializer.new(
@wizard.steps,
each_serializer: CustomWizard::StepSerializer,
scope: Guardian.new(user)
).as_json
expect(json_array[0][:wizard_step][:permitted]).to eq(false)
expect(json_array[0][:wizard_step][:permitted_message]).to eq("Missing required data")
end
end
end

Datei anzeigen

@ -0,0 +1,67 @@
# frozen_string_literal: true
require_relative '../../plugin_helper'
describe CustomWizard::StepSerializer do
fab!(:user) { Fabricate(:user) }
let(:wizard_template) {
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read
)
}
let(:required_data_json) {
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/required_data.json"
).read
)
}
before do
CustomWizard::Template.save(wizard_template, skip_jobs: true)
@wizard = CustomWizard::Builder.new("super_mega_fun_wizard", user).build
end
it 'should return basic step attributes' do
json_array = ActiveModel::ArraySerializer.new(
@wizard.steps,
each_serializer: described_class,
scope: Guardian.new(user)
).as_json
expect(json_array[0][:title]).to eq("<p>Text</p>")
expect(json_array[0][:description]).to eq("<p>Text inputs!</p>")
expect(json_array[1][:index]).to eq(1)
end
it 'should return fields' do
json_array = ActiveModel::ArraySerializer.new(
@wizard.steps,
each_serializer: described_class,
scope: Guardian.new(user)
).as_json
expect(json_array[0][:fields].length).to eq(4)
end
context 'with required data' do
before do
wizard_template['steps'][0]['required_data'] = required_data_json['required_data']
wizard_template['steps'][0]['required_data_message'] = required_data_json['required_data_message']
CustomWizard::Template.save(wizard_template)
@wizard = CustomWizard::Builder.new("super_mega_fun_wizard", user).build
end
it 'should return permitted attributes' do
json_array = ActiveModel::ArraySerializer.new(
@wizard.steps,
each_serializer: described_class,
scope: Guardian.new(user)
).as_json
expect(json_array[0][:permitted]).to eq(false)
expect(json_array[0][:permitted_message]).to eq("Missing required data")
end
end
end