0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-11-26 02:50:28 +01:00

Merge pull request #144 from paviliondev/pro-features

Pro features
Dieser Commit ist enthalten in:
Angus McLeod 2021-09-07 21:58:14 +08:00 committet von GitHub
Commit 13304f5b6d
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
104 geänderte Dateien mit 2464 neuen und 1379 gelöschten Zeilen

Datei anzeigen

@ -1,13 +1,29 @@
import Component from "@ember/component"; import Component from "@ember/component";
import discourseComputed, { observes } from "discourse-common/utils/decorators"; import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { alias, equal, or } from "@ember/object/computed"; import { alias, equal, or } from "@ember/object/computed";
import { computed } from "@ember/object";
import I18n from "I18n"; import I18n from "I18n";
const generateContent = function (array, type) { const klasses = ["topic", "post", "group", "category"];
return array.map((key) => ({ const types = ["string", "boolean", "integer", "json"];
const proTypes = {
klass: ["group", "category"],
type: ["json"],
};
const generateContent = function (array, type, proSubscribed = false) {
return array.reduce((result, key) => {
let proArr = proTypes[type];
let pro = proArr && proArr.includes(key);
if (!pro || proSubscribed) {
result.push({
id: key, id: key,
name: I18n.t(`admin.wizard.custom_field.${type}.${key}`), name: I18n.t(`admin.wizard.custom_field.${type}.${key}`),
})); pro,
});
}
return result;
}, []);
}; };
export default Component.extend({ export default Component.extend({
@ -16,14 +32,12 @@ export default Component.extend({
postSerializers: ["post"], postSerializers: ["post"],
groupSerializers: ["basic_group"], groupSerializers: ["basic_group"],
categorySerializers: ["basic_category"], categorySerializers: ["basic_category"],
klassContent: generateContent( klassContent: computed("proSubscribed", function () {
["topic", "post", "group", "category"], return generateContent(klasses, "klass", this.proSubscribed);
"klass" }),
), typeContent: computed("proSubscribed", function () {
typeContent: generateContent( return generateContent(types, "type", this.proSubscribed);
["string", "boolean", "integer", "json"], }),
"type"
),
showInputs: or("field.new", "field.edit"), showInputs: or("field.new", "field.edit"),
classNames: ["custom-field-input"], classNames: ["custom-field-input"],
loading: or("saving", "destroying"), loading: or("saving", "destroying"),
@ -40,7 +54,7 @@ export default Component.extend({
const serializers = this.get(`${klass}Serializers`); const serializers = this.get(`${klass}Serializers`);
if (serializers) { if (serializers) {
return generateContent(serializers, "serializers"); return generateContent(serializers, "serializers", this.proSubscribed);
} else { } else {
return []; return [];
} }

Datei anzeigen

@ -1,21 +0,0 @@
import { default as discourseComputed } from "discourse-common/utils/decorators";
import Component from "@ember/component";
export default Component.extend({
classNames: "wizard-advanced-toggle",
@discourseComputed("showAdvanced")
toggleClass(showAdvanced) {
let classes = "btn";
if (showAdvanced) {
classes += " btn-primary";
}
return classes;
},
actions: {
toggleAdvanced() {
this.toggleProperty("showAdvanced");
},
},
});

Datei anzeigen

@ -1,8 +1,8 @@
import { default as discourseComputed } from "discourse-common/utils/decorators"; import { default as discourseComputed } from "discourse-common/utils/decorators";
import { and, empty, equal, or } from "@ember/object/computed"; import wizardSchema from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
import { empty, equal, or } from "@ember/object/computed";
import { notificationLevels, selectKitContent } from "../lib/wizard"; import { notificationLevels, selectKitContent } from "../lib/wizard";
import { computed } from "@ember/object"; import { computed } from "@ember/object";
import wizardSchema from "../lib/wizard-schema";
import UndoChanges from "../mixins/undo-changes"; import UndoChanges from "../mixins/undo-changes";
import Component from "@ember/component"; import Component from "@ember/component";
import I18n from "I18n"; import I18n from "I18n";
@ -25,8 +25,6 @@ export default Component.extend(UndoChanges, {
createGroup: equal("action.type", "create_group"), createGroup: equal("action.type", "create_group"),
apiEmpty: empty("action.api"), apiEmpty: empty("action.api"),
groupPropertyTypes: selectKitContent(["id", "name"]), groupPropertyTypes: selectKitContent(["id", "name"]),
hasAdvanced: or("hasCustomFields", "routeTo"),
showAdvanced: and("hasAdvanced", "action.type"),
hasCustomFields: or( hasCustomFields: or(
"basicTopicFields", "basicTopicFields",
"updateProfile", "updateProfile",
@ -36,12 +34,6 @@ export default Component.extend(UndoChanges, {
basicTopicFields: or("createTopic", "sendMessage", "openComposer"), basicTopicFields: or("createTopic", "sendMessage", "openComposer"),
publicTopicFields: or("createTopic", "openComposer"), publicTopicFields: or("createTopic", "openComposer"),
showPostAdvanced: or("createTopic", "sendMessage"), showPostAdvanced: or("createTopic", "sendMessage"),
actionTypes: Object.keys(wizardSchema.action.types).map((type) => {
return {
id: type,
name: I18n.t(`admin.wizard.action.${type}.label`),
};
}),
availableNotificationLevels: notificationLevels.map((type) => { availableNotificationLevels: notificationLevels.map((type) => {
return { return {
id: type, id: type,
@ -101,4 +93,19 @@ export default Component.extend(UndoChanges, {
} }
return apis.find((a) => a.name === api).endpoints; return apis.find((a) => a.name === api).endpoints;
}, },
@discourseComputed("proSubscribed")
actionTypes(proSubscribed) {
return Object.keys(wizardSchema.action.types).reduce((result, type) => {
let pro = wizardSchema.action.proTypes.includes(type);
if (proSubscribed || !pro) {
result.push({
id: type,
name: I18n.t(`admin.wizard.action.${type}.label`),
pro,
});
}
return result;
}, []);
},
}); });

Datei anzeigen

@ -1,5 +1,5 @@
import { default as discourseComputed } from "discourse-common/utils/decorators"; import { default as discourseComputed } from "discourse-common/utils/decorators";
import { alias, equal, or } from "@ember/object/computed"; import { equal, or } from "@ember/object/computed";
import { computed } from "@ember/object"; import { computed } from "@ember/object";
import { selectKitContent } from "../lib/wizard"; import { selectKitContent } from "../lib/wizard";
import UndoChanges from "../mixins/undo-changes"; import UndoChanges from "../mixins/undo-changes";
@ -27,7 +27,6 @@ export default Component.extend(UndoChanges, {
isTextType: or("isText", "isTextarea", "isComposer"), isTextType: or("isText", "isTextarea", "isComposer"),
isComposerPreview: equal("field.type", "composer_preview"), isComposerPreview: equal("field.type", "composer_preview"),
categoryPropertyTypes: selectKitContent(["id", "slug"]), categoryPropertyTypes: selectKitContent(["id", "slug"]),
showAdvanced: alias("field.type"),
messageUrl: "https://thepavilion.io/t/2809", messageUrl: "https://thepavilion.io/t/2809",
@discourseComputed("field.type") @discourseComputed("field.type")

Datei anzeigen

@ -6,6 +6,7 @@ import I18n from "I18n";
const icons = { const icons = {
error: "times-circle", error: "times-circle",
success: "check-circle", success: "check-circle",
warn: "exclamation-circle",
info: "info-circle", info: "info-circle",
}; };

Datei anzeigen

@ -0,0 +1,18 @@
import SingleSelectComponent from "select-kit/components/single-select";
export default SingleSelectComponent.extend({
classNames: ["combo-box", "wizard-pro-selector"],
selectKitOptions: {
autoFilterable: false,
filterable: false,
showFullTitle: true,
headerComponent: "wizard-pro-selector/wizard-pro-selector-header",
caretUpIcon: "caret-up",
caretDownIcon: "caret-down",
},
modifyComponentForRow() {
return "wizard-pro-selector/wizard-pro-selector-row";
},
});

Datei anzeigen

@ -0,0 +1,17 @@
import SingleSelectHeaderComponent from "select-kit/components/select-kit/single-select-header";
import { computed } from "@ember/object";
import { reads } from "@ember/object/computed";
export default SingleSelectHeaderComponent.extend({
classNames: ["combo-box-header", "wizard-pro-selector-header"],
caretUpIcon: reads("selectKit.options.caretUpIcon"),
caretDownIcon: reads("selectKit.options.caretDownIcon"),
caretIcon: computed(
"selectKit.isExpanded",
"caretUpIcon",
"caretDownIcon",
function () {
return this.selectKit.isExpanded ? this.caretUpIcon : this.caretDownIcon;
}
),
});

Datei anzeigen

@ -0,0 +1,3 @@
import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row";
export default SelectKitRowComponent.extend();

Datei anzeigen

@ -0,0 +1,53 @@
import Component from "@ember/component";
import CustomWizardPro from "../models/custom-wizard-pro";
import { notEmpty } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n";
export default Component.extend({
classNameBindings: [
":custom-wizard-pro-subscription",
"subscription.active:active:inactive",
],
subscribed: notEmpty("subscription"),
@discourseComputed("subscription.type")
title(type) {
return type
? I18n.t(`admin.wizard.pro.subscription.title.${type}`)
: I18n.t("admin.wizard.pro.not_subscribed");
},
@discourseComputed("subscription.active")
stateClass(active) {
return active ? "active" : "inactive";
},
@discourseComputed("stateClass")
stateLabel(stateClass) {
return I18n.t(`admin.wizard.pro.subscription.status.${stateClass}`);
},
actions: {
update() {
this.set("updating", true);
CustomWizardPro.update_subscription()
.then((result) => {
if (result.success) {
this.setProperties({
updateIcon: "check",
subscription: result.subscription,
});
} else {
this.set("updateIcon", "times");
}
})
.finally(() => {
this.set("updating", false);
setTimeout(() => {
this.set("updateIcon", null);
}, 7000);
});
},
},
});

Datei anzeigen

@ -6,7 +6,8 @@ import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n"; import I18n from "I18n";
export default Component.extend({ export default Component.extend({
classNames: ["realtime-validations"], classNames: ["realtime-validations", "setting", "full", "pro"],
@discourseComputed @discourseComputed
timeUnits() { timeUnits() {
return ["days", "weeks", "months", "years"].map((unit) => { return ["days", "weeks", "months", "years"].map((unit) => {

Datei anzeigen

@ -0,0 +1,62 @@
import Controller from "@ember/controller";
import discourseComputed from "discourse-common/utils/decorators";
import CustomWizardPro from "../models/custom-wizard-pro";
import { alias } from "@ember/object/computed";
export default Controller.extend({
messageUrl: "https://thepavilion.io/t/3652",
messageType: "info",
messageKey: null,
showSubscription: alias("model.authentication.active"),
setup() {
const authentication = this.get("model.authentication");
const subscription = this.get("model.subscription");
const subscribed = subscription && subscription.active;
const authenticated = authentication && authentication.active;
if (!subscribed) {
this.set("messageKey", authenticated ? "not_subscribed" : "authorize");
} else {
this.set(
"messageKey",
!authenticated
? "subscription_expiring"
: subscribed
? "subscription_active"
: "subscription_inactive"
);
}
},
@discourseComputed("model.server")
messageOpts(server) {
return { server };
},
actions: {
unauthorize() {
this.set("unauthorizing", true);
CustomWizardPro.unauthorize()
.then((result) => {
if (result.success) {
this.setProperties({
messageKey: "unauthorized",
messageType: "warn",
"model.authentication": null,
"model.subscription": null,
});
} else {
this.setProperties({
messageKey: "unauthorize_failed",
messageType: "error",
});
}
})
.finally(() => {
this.set("unauthorizing", false);
});
},
},
});

Datei anzeigen

@ -77,7 +77,11 @@ export default Controller.extend({
wizard wizard
.save(opts) .save(opts)
.then((result) => { .then((result) => {
if (result.wizard_id) {
this.send("afterSave", result.wizard_id); this.send("afterSave", result.wizard_id);
} else if (result.errors) {
this.set("error", result.errors.join(", "));
}
}) })
.catch((result) => { .catch((result) => {
let errorType = "failed"; let errorType = "failed";
@ -114,10 +118,6 @@ export default Controller.extend({
controller.setup(); controller.setup();
}, },
toggleAdvanced() {
this.toggleProperty("wizard.showAdvanced");
},
copyUrl() { copyUrl() {
const $copyRange = $('<p id="copy-range"></p>'); const $copyRange = $('<p id="copy-range"></p>');
$copyRange.html(this.wizardUrl); $copyRange.html(this.wizardUrl);

Datei anzeigen

@ -43,12 +43,20 @@ export default {
} }
); );
this.route("adminWizardsLogs", { path: "/logs", resetNamespace: true }); this.route("adminWizardsLogs", {
path: "/logs",
resetNamespace: true,
});
this.route("adminWizardsManager", { this.route("adminWizardsManager", {
path: "/manager", path: "/manager",
resetNamespace: true, resetNamespace: true,
}); });
this.route("adminWizardsPro", {
path: "/pro",
resetNamespace: true,
});
} }
); );
}, },

Datei anzeigen

@ -97,11 +97,6 @@ function buildObjectArray(json, type) {
if (present(json)) { if (present(json)) {
json.forEach((objJson, objectIndex) => { json.forEach((objJson, objectIndex) => {
let object = buildObject(objJson, type, objectIndex); let object = buildObject(objJson, type, objectIndex);
if (hasAdvancedProperties(object, type)) {
object.set("showAdvanced", true);
}
array.pushObject(object); array.pushObject(object);
}); });
} }
@ -112,21 +107,11 @@ function buildObjectArray(json, type) {
function buildBasicProperties(json, type, props, objectIndex = null) { function buildBasicProperties(json, type, props, objectIndex = null) {
listProperties(type).forEach((p) => { listProperties(type).forEach((p) => {
props[p] = buildProperty(json, p, type, objectIndex); props[p] = buildProperty(json, p, type, objectIndex);
if (hasAdvancedProperties(json, type)) {
props.showAdvanced = true;
}
}); });
return props; return props;
} }
function hasAdvancedProperties(object, type) {
return Object.keys(object).some((p) => {
return wizardSchema[type].advanced.indexOf(p) > -1 && present(object[p]);
});
}
/// to be removed: necessary due to action array being moved from step to wizard /// to be removed: necessary due to action array being moved from step to wizard
function actionPatch(json) { function actionPatch(json) {
let actions = json.actions || []; let actions = json.actions || [];

Datei anzeigen

@ -18,7 +18,6 @@ const wizard = {
permitted: null, permitted: null,
}, },
mapped: ["permitted"], mapped: ["permitted"],
advanced: ["restart_on_revisit"],
required: ["id"], required: ["id"],
dependent: { dependent: {
after_time: "after_time_scheduled", after_time: "after_time_scheduled",
@ -50,7 +49,6 @@ const step = {
force_final: false, force_final: false,
}, },
mapped: ["required_data", "permitted_params", "condition", "index"], mapped: ["required_data", "permitted_params", "condition", "index"],
advanced: ["required_data", "permitted_params", "condition", "index"],
required: ["id"], required: ["id"],
dependent: {}, dependent: {},
objectArrays: { objectArrays: {
@ -68,6 +66,7 @@ const field = {
label: null, label: null,
image: null, image: null,
description: null, description: null,
property: null,
required: null, required: null,
key: null, key: null,
type: null, type: null,
@ -75,7 +74,6 @@ const field = {
}, },
types: {}, types: {},
mapped: ["prefill", "content", "condition", "index"], mapped: ["prefill", "content", "condition", "index"],
advanced: ["property", "key", "condition", "index"],
required: ["id", "type"], required: ["id", "type"],
dependent: {}, dependent: {},
objectArrays: {}, objectArrays: {},
@ -196,14 +194,14 @@ const action = {
"visibility_level", "visibility_level",
"members_visibility_level", "members_visibility_level",
], ],
advanced: [
"code",
"custom_fields",
"skip_redirect",
"suppress_notifications",
"required",
],
required: ["id", "type"], required: ["id", "type"],
proTypes: [
"send_message",
"add_to_group",
"create_category",
"create_group",
"send_to_api",
],
dependent: {}, dependent: {},
objectArrays: {}, objectArrays: {},
}; };

Datei anzeigen

@ -0,0 +1,33 @@
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import EmberObject from "@ember/object";
const CustomWizardPro = EmberObject.extend();
const basePath = "/admin/wizards/pro";
CustomWizardPro.reopenClass({
status() {
return ajax(basePath, {
type: "GET",
}).catch(popupAjaxError);
},
authorize() {
window.location.href = `${basePath}/authorize`;
},
unauthorize() {
return ajax(`${basePath}/authorize`, {
type: "DELETE",
}).catch(popupAjaxError);
},
update_subscription() {
return ajax(`${basePath}/subscription`, {
type: "POST",
}).catch(popupAjaxError);
},
});
export default CustomWizardPro;

Datei anzeigen

@ -8,7 +8,12 @@ export default DiscourseRoute.extend({
}, },
setupController(controller, model) { setupController(controller, model) {
const customFields = A(model || []); const customFields = A(model.custom_fields || []);
controller.set("customFields", customFields); const proSubscribed = model.pro_subscribed;
controller.setProperties({
customFields,
proSubscribed,
});
}, },
}); });

Datei anzeigen

@ -0,0 +1,19 @@
import CustomWizardPro from "../models/custom-wizard-pro";
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
model() {
return CustomWizardPro.status();
},
setupController(controller, model) {
controller.set("model", model);
controller.setup();
},
actions: {
authorize() {
CustomWizardPro.authorize();
},
},
});

Datei anzeigen

@ -39,6 +39,7 @@ export default DiscourseRoute.extend({
currentStep: wizard.steps[0], currentStep: wizard.steps[0],
currentAction: wizard.actions[0], currentAction: wizard.actions[0],
creating: model.create, creating: model.create,
proSubscribed: parentModel.pro_subscribed,
}; };
controller.setProperties(props); controller.setProperties(props);

Datei anzeigen

@ -32,7 +32,8 @@
{{custom-field-input {{custom-field-input
field=field field=field
removeField=(action "removeField") removeField=(action "removeField")
saveField=(action "saveField")}} saveField=(action "saveField")
proSubscribed=proSubscribed}}
{{/each}} {{/each}}
</tbody> </tbody>
</table> </table>

Datei anzeigen

@ -0,0 +1,34 @@
<div class="admin-wizard-controls">
<h3>{{i18n "admin.wizard.pro.title"}}</h3>
<div class="buttons">
{{#if model.authentication.active}}
{{conditional-loading-spinner size="small" condition=unauthorizing}}
<a {{action "unauthorize"}} title={{i18n "admin.wizard.pro.unauthorize"}} role="button">
{{i18n "admin.wizard.pro.unauthorize"}}
</a>
<label title={{i18n "admin.wizard.pro.authorized"}}>
{{i18n "admin.wizard.pro.authorized"}}
</label>
{{else}}
{{d-button
icon="id-card"
label="admin.wizard.pro.authorize"
title="admin.wizard.pro.authorize"
action=(route-action "authorize")}}
{{/if}}
</div>
</div>
{{wizard-message
key=messageKey
url=messageUrl
type=messageType
opts=messageOpts
component="pro"}}
<div class="admin-wizard-container">
{{#if showSubscription}}
{{wizard-pro-subscription subscription=model.subscription}}
{{/if}}
</div>

Datei anzeigen

@ -126,11 +126,6 @@
</div> </div>
</div> </div>
{{wizard-advanced-toggle showAdvanced=wizard.showAdvanced}}
{{#if wizard.showAdvanced}}
<div class="advanced-settings">
<div class="setting"> <div class="setting">
<div class="setting-label"> <div class="setting-label">
<label>{{i18n "admin.wizard.save_submissions"}}</label> <label>{{i18n "admin.wizard.save_submissions"}}</label>
@ -150,9 +145,6 @@
<span>{{i18n "admin.wizard.restart_on_revisit_label"}}</span> <span>{{i18n "admin.wizard.restart_on_revisit_label"}}</span>
</div> </div>
</div> </div>
</div>
{{/if}}
</div> </div>
{{wizard-links {{wizard-links
@ -166,7 +158,8 @@
wizard=wizard wizard=wizard
currentField=currentField currentField=currentField
wizardFields=wizardFields wizardFields=wizardFields
fieldTypes=fieldTypes}} fieldTypes=fieldTypes
proSubscribed=proSubscribed}}
{{/if}} {{/if}}
{{wizard-links {{wizard-links
@ -182,7 +175,8 @@
wizard=wizard wizard=wizard
apis=apis apis=apis
removeAction="removeAction" removeAction="removeAction"
wizardFields=wizardFields}} wizardFields=wizardFields
proSubscribed=proSubscribed}}
{{/each}} {{/each}}
<div class="admin-wizard-buttons"> <div class="admin-wizard-buttons">

Datei anzeigen

@ -7,6 +7,7 @@
{{/if}} {{/if}}
{{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}} {{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}}
{{nav-item route="adminWizardsManager" label="admin.wizard.manager.nav_label"}} {{nav-item route="adminWizardsManager" label="admin.wizard.manager.nav_label"}}
{{nav-item route="adminWizardsPro" label="admin.wizard.pro.nav_label"}}
<div class="admin-actions"> <div class="admin-actions">
<a target="_blank" class="btn btn-pavilion-pro" rel="noreferrer noopener" href="https://thepavilion.io/w/support" title={{i18n "admin.wizard.pro_support_button.title"}}>{{d-icon "far-life-ring"}}{{i18n "admin.wizard.pro_support_button.label"}}</a> <a target="_blank" class="btn btn-pavilion-pro" rel="noreferrer noopener" href="https://thepavilion.io/w/support" title={{i18n "admin.wizard.pro_support_button.title"}}>{{d-icon "far-life-ring"}}{{i18n "admin.wizard.pro_support_button.label"}}</a>

Datei anzeigen

@ -1,13 +1,13 @@
{{#if showInputs}} {{#if showInputs}}
<td> <td>
{{combo-box {{wizard-pro-selector
value=field.klass value=field.klass
content=klassContent content=klassContent
none="admin.wizard.custom_field.klass.select" none="admin.wizard.custom_field.klass.select"
onChange=(action (mut field.klass))}} onChange=(action (mut field.klass))}}
</td> </td>
<td> <td>
{{combo-box {{wizard-pro-selector
value=field.type value=field.type
content=typeContent content=typeContent
none="admin.wizard.custom_field.type.select" none="admin.wizard.custom_field.type.select"

Datei anzeigen

@ -1,4 +0,0 @@
{{d-button
action="toggleAdvanced"
label="admin.wizard.advanced"
class=toggleClass}}

Datei anzeigen

@ -12,13 +12,14 @@
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{combo-box {{wizard-pro-selector
value=action.type value=action.type
content=actionTypes content=actionTypes
onChange=(action "changeType") onChange=(action "changeType")
options=(hash options=(hash
none="admin.wizard.select_type" none="admin.wizard.select_type"
)}} )
}}
</div> </div>
</div> </div>
@ -714,13 +715,7 @@
</div> </div>
{{/if}} {{/if}}
{{#if showAdvanced}} {{#if hasCustomFields}}
{{wizard-advanced-toggle showAdvanced=action.showAdvanced}}
{{#if action.showAdvanced}}
<div class="advanced-settings">
{{#if hasCustomFields}}
<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.action.custom_fields.label"}}</label> <label>{{i18n "admin.wizard.action.custom_fields.label"}}</label>
@ -742,9 +737,9 @@
)}} )}}
</div> </div>
</div> </div>
{{/if}} {{/if}}
{{#if sendMessage}} {{#if sendMessage}}
<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.required"}}</label> <label>{{i18n "admin.wizard.required"}}</label>
@ -764,9 +759,9 @@
)}} )}}
</div> </div>
</div> </div>
{{/if}} {{/if}}
{{#if showPostAdvanced}} {{#if showPostAdvanced}}
<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>
@ -794,9 +789,9 @@
</span> </span>
</div> </div>
</div> </div>
{{/if}} {{/if}}
{{#if routeTo}} {{#if routeTo}}
<div class="setting"> <div class="setting">
<div class="setting-label"> <div class="setting-label">
<label>{{i18n "admin.wizard.action.route_to.code"}}</label> <label>{{i18n "admin.wizard.action.route_to.code"}}</label>
@ -806,7 +801,4 @@
{{input value=action.code}} {{input value=action.code}}
</div> </div>
</div> </div>
{{/if}}
</div>
{{/if}}
{{/if}} {{/if}}

Datei anzeigen

@ -207,15 +207,11 @@
</div> </div>
{{/if}} {{/if}}
{{#if showAdvanced}} {{#if proSubscribed}}
{{wizard-advanced-toggle showAdvanced=field.showAdvanced}} <div class="setting full field-mapper-setting pro">
{{#if field.showAdvanced}}
<div class="advanced-settings">
<div class="setting full field-mapper-setting">
<div class="setting-label"> <div class="setting-label">
<label>{{i18n "admin.wizard.condition"}}</label> <label>{{i18n "admin.wizard.condition"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div> </div>
<div class="setting-value"> <div class="setting-value">
@ -225,9 +221,10 @@
</div> </div>
</div> </div>
<div class="setting full field-mapper-setting"> <div class="setting full field-mapper-setting pro">
<div class="setting-label"> <div class="setting-label">
<label>{{i18n "admin.wizard.index"}}</label> <label>{{i18n "admin.wizard.index"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div> </div>
<div class="setting-value"> <div class="setting-value">
@ -238,9 +235,10 @@
</div> </div>
{{#if isCategory}} {{#if isCategory}}
<div class="setting"> <div class="setting pro">
<div class="setting-label"> <div class="setting-label">
<label>{{i18n "admin.wizard.field.property"}}</label> <label>{{i18n "admin.wizard.field.property"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div> </div>
<div class="setting-value"> <div class="setting-value">
@ -255,15 +253,15 @@
</div> </div>
{{/if}} {{/if}}
<div class="setting"> <div class="setting pro">
<div class="setting-label"> <div class="setting-label">
<label>{{i18n "admin.wizard.translation"}}</label> <label>{{i18n "admin.wizard.translation"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div> </div>
<div class="setting-value medium"> <div class="setting-value">
{{input {{input
name="key" name="key"
value=field.key value=field.key
class="medium"
placeholderKey="admin.wizard.translation_placeholder"}} placeholderKey="admin.wizard.translation_placeholder"}}
</div> </div>
</div> </div>
@ -271,6 +269,4 @@
{{#if validations}} {{#if validations}}
{{wizard-realtime-validations field=field validations=validations}} {{wizard-realtime-validations field=field validations=validations}}
{{/if}} {{/if}}
</div>
{{/if}}
{{/if}} {{/if}}

Datei anzeigen

@ -33,14 +33,11 @@
</div> </div>
</div> </div>
{{wizard-advanced-toggle showAdvanced=step.showAdvanced}} {{#if proSubscribed}}
<div class="setting full field-mapper-setting pro">
{{#if step.showAdvanced}}
<div class="advanced-settings">
<div class="setting full field-mapper-setting">
<div class="setting-label"> <div class="setting-label">
<label>{{i18n "admin.wizard.condition"}}</label> <label>{{i18n "admin.wizard.condition"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div> </div>
<div class="setting-value"> <div class="setting-value">
@ -59,9 +56,10 @@
</div> </div>
</div> </div>
<div class="setting full field-mapper-setting"> <div class="setting full field-mapper-setting pro">
<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>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{wizard-mapper {{wizard-mapper
@ -85,9 +83,10 @@
</div> </div>
</div> </div>
<div class="setting full field-mapper-setting"> <div class="setting full field-mapper-setting pro">
<div class="setting-label"> <div class="setting-label">
<label>{{i18n "admin.wizard.step.permitted_params.label"}}</label> <label>{{i18n "admin.wizard.step.permitted_params.label"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{wizard-mapper {{wizard-mapper
@ -102,9 +101,10 @@
</div> </div>
</div> </div>
<div class="setting"> <div class="setting pro">
<div class="setting-label"> <div class="setting-label">
<label>{{i18n "admin.wizard.translation"}}</label> <label>{{i18n "admin.wizard.translation"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input {{input
@ -113,7 +113,6 @@
placeholderKey="admin.wizard.translation_placeholder"}} placeholderKey="admin.wizard.translation_placeholder"}}
</div> </div>
</div> </div>
</div>
{{/if}} {{/if}}
{{wizard-links {{wizard-links
@ -129,5 +128,6 @@
currentFieldId=currentField.id currentFieldId=currentField.id
fieldTypes=fieldTypes fieldTypes=fieldTypes
removeField="removeField" removeField="removeField"
wizardFields=wizardFields}} wizardFields=wizardFields
proSubscribed=proSubscribed}}
{{/each}} {{/each}}

Datei anzeigen

@ -0,0 +1,15 @@
<div class="select-kit-header-wrapper">
{{component selectKit.options.selectedNameComponent
tabindex=tabindex
item=selectedContent
selectKit=selectKit
shouldDisplayClearableButton=shouldDisplayClearableButton
}}
{{#if selectedContent.pro}}
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
{{/if}}
{{d-icon caretIcon class="caret-icon"}}
</div>

Datei anzeigen

@ -0,0 +1,15 @@
{{#if icons}}
<div class="icons">
<span class="selection-indicator"></span>
{{#each icons as |icon|}}
{{d-icon icon translatedtitle=(dasherize title)}}
{{/each}}
</div>
{{/if}}
<div class="texts">
<span class="name">{{html-safe label}}</span>
{{#if item.pro}}
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
{{/if}}
</div>

Datei anzeigen

@ -0,0 +1,31 @@
<div class="title-container">
<h3 class="subscription-title">{{title}}</h3>
<div class="buttons">
<span>
{{#if updating}}
{{loading-spinner size="small"}}
{{else if updateIcon}}
{{d-icon updateIcon}}
{{/if}}
</span>
{{d-button
icon="sync"
action=(action "update")
disabled=updating
title="admin.wizard.pro.subscription.update"
label="admin.wizard.pro.subscription.update"}}
</div>
</div>
{{#if subscribed}}
<div class="detail-container">
<div class="subscription-state {{stateClass}}" title={{stateLabel}}>{{stateLabel}}</div>
{{#if subscription.updated_at}}
<div class="subscription-updated-at" title={{subscription.updated_at}}>
{{i18n "admin.wizard.pro.subscription.last_updated"}} {{format-date subscription.updated_at leaveAgo="true"}}
</div>
{{/if}}
</div>
{{/if}}

Datei anzeigen

@ -1,6 +1,9 @@
<h3>{{i18n "admin.wizard.field.validations.header"}}</h3> <div class="setting-label">
<label>{{i18n "admin.wizard.field.validations.header"}}</label>
<ul> <span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting-value full">
<ul>
{{#each-in field.validations as |type props|}} {{#each-in field.validations as |type props|}}
<li> <li>
<span class="setting-title"> <span class="setting-title">
@ -39,12 +42,13 @@
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{radio-button name=(concat type field.id) value="above" selection=props.position}} {{radio-button name=(concat type field.id) value="above" selection=props.position}}
{{i18n "admin.wizard.field.validations.above"}} <span>{{i18n "admin.wizard.field.validations.above"}}</span>
{{radio-button name=(concat type field.id) value="below" selection=props.position}} {{radio-button name=(concat type field.id) value="below" selection=props.position}}
{{i18n "admin.wizard.field.validations.below"}} <span>{{i18n "admin.wizard.field.validations.below"}}</span>
</div> </div>
</div> </div>
</div> </div>
</li> </li>
{{/each-in}} {{/each-in}}
</ul> </ul>
</div>

Datei anzeigen

@ -8,7 +8,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: 20px; margin-bottom: 10px;
& + .wizard-message + div { & + .wizard-message + div {
margin-top: 20px; margin-top: 20px;
@ -294,7 +294,7 @@
font-size: 0.85em; font-size: 0.85em;
} }
span { > span {
font-size: 0.929em; font-size: 0.929em;
} }
@ -423,6 +423,17 @@
.setting-gutter { .setting-gutter {
margin-top: 5px; margin-top: 5px;
} }
&.pro {
.setting-label {
display: flex;
flex-direction: column;
label {
margin: 0;
}
}
}
} }
.advanced-settings { .advanced-settings {
@ -729,27 +740,59 @@
} }
} }
.realtime-validations > ul { .admin-wizard-container.settings .realtime-validations .setting-value > ul {
list-style: none; list-style: none;
margin: 0; margin: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
> li { > li {
background-color: var(--primary-low); background-color: var(--primary-low);
padding: 1em; padding: 1em;
margin: 0 0 1em 0; margin: 0 0 1em 0;
input { .setting-title {
margin-bottom: 0; display: flex;
align-items: center;
h4 {
margin: 0 15px 0 0;
}
input[type="checkbox"] {
margin: 0 5px 0 0;
}
}
.setting-label {
width: 100px;
}
.setting-value {
display: flex;
align-items: center;
.input .select-kit,
> .select-kit {
max-width: unset !important;
}
> span {
margin-right: 1em;
}
} }
} }
} }
.validation-container { .validation-container {
display: flex; display: flex;
flex-direction: column;
padding: 1em 0; padding: 1em 0;
.validation-section { .validation-section {
width: 250px; min-width: 250px;
margin: 0.5em 0;
} }
} }
@ -767,6 +810,82 @@
vertical-align: middle; vertical-align: middle;
} }
.pro-label {
color: var(--tertiary);
font-size: 0.75em;
}
.admin-wizards-pro {
.admin-wizard-controls {
h3,
label {
margin: 0;
}
label {
padding: 0.4em 0.5em;
margin-left: 0.75em;
background-color: var(--success);
color: var(--secondary);
}
.buttons {
display: flex;
align-items: center;
.loading-container {
margin-right: 1em;
}
}
}
.custom-wizard-pro-subscription {
.title-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5em;
h3 {
margin: 0;
}
.buttons > span {
margin-right: 0.5em;
}
}
.detail-container {
display: flex;
align-items: center;
padding: 1em;
background-color: var(--primary-very-low);
.subscription-state {
padding: 0.25em 0.5em;
margin-right: 0.75em;
&.active {
background-color: var(--success);
color: var(--secondary);
}
}
}
}
}
.wizard-pro-selector.select-kit.single-select {
.select-kit-row .texts {
display: flex;
align-items: center;
}
.pro-label {
margin-left: 0.75em;
padding-top: 0.25em;
}
}
.btn.btn-pavilion-pro { .btn.btn-pavilion-pro {
background: var(--pavilion-primary); background: var(--pavilion-primary);
color: var(--pavilion-secondary); color: var(--pavilion-secondary);

Datei anzeigen

@ -58,6 +58,7 @@ en:
select_type: "Select a type" select_type: "Select a type"
condition: "Condition" condition: "Condition"
index: "Index" index: "Index"
pro_support_button: pro_support_button:
title: "Request Pro Support" title: "Request Pro Support"
label: "Pro Support" label: "Pro Support"
@ -89,11 +90,19 @@ en:
no_file: Please choose a file to import no_file: Please choose a file to import
file_size_error: The file size must be 512kb or less file_size_error: The file size must be 512kb or less
file_format_error: The file must be a .json file file_format_error: The file must be a .json file
server_error: "Error: {{message}}"
importing: Importing wizards... importing: Importing wizards...
destroying: Destroying wizards... destroying: Destroying wizards...
import_complete: Import complete import_complete: Import complete
destroy_complete: Destruction complete destroy_complete: Destruction complete
pro:
documentation: Check out the PRO documentation
authorize: "Authorize this forum to use your PRO subscription plan on %{server}."
not_subscribed: "You've authorized, but are not currently subscribed to a PRO plan on %{server}."
subscription_expiring: "Your subscription is active, but will expire in the next 48 hours."
subscription_active: "Your subscription is active."
subscription_inactive: "Your subscription is inactive on this forum. Read more in <a href='https://thepavilion.io/t/3652'>the documentation</a>."
unauthorized: "You're unauthorized. If you have a subscription, it will become inactive in the next 48 hours."
unauthorize_failed: Failed to unauthorize.
submissions: submissions:
select: "Select a wizard to see its submissions" select: "Select a wizard to see its submissions"
viewing: "You're viewing the submissions of the %{wizardName}. Click 'Download' on the right to download them." viewing: "You're viewing the submissions of the %{wizardName}. Click 'Download' on the right to download them."
@ -102,7 +111,6 @@ en:
viewing: "View recent logs for wizards on the forum" viewing: "View recent logs for wizards on the forum"
documentation: "Check out the logs documentation" documentation: "Check out the logs documentation"
editor: editor:
show: "Show" show: "Show"
hide: "Hide" hide: "Hide"
@ -182,9 +190,9 @@ en:
min_length_placeholder: "Minimum length in characters" min_length_placeholder: "Minimum length in characters"
max_length: "Max Length" max_length: "Max Length"
max_length_placeholder: "Maximum length in characters" max_length_placeholder: "Maximum length in characters"
char_counter: "Character Counter" char_counter: "Counter"
char_counter_placeholder: "Display Character Counter" char_counter_placeholder: "Display Character Counter"
field_placeholder: "Field Placeholder" field_placeholder: "Placeholder"
file_types: "File Types" file_types: "File Types"
preview_template: "Preview Template" preview_template: "Preview Template"
limit: "Limit" limit: "Limit"
@ -195,7 +203,7 @@ en:
label: "Format" label: "Format"
instructions: "<a href='https://momentjs.com/docs/#/displaying/format/' target='_blank'>Moment.js format</a>" instructions: "<a href='https://momentjs.com/docs/#/displaying/format/' target='_blank'>Moment.js format</a>"
validations: validations:
header: "Realtime Validations" header: "Validations"
enabled: "Enabled" enabled: "Enabled"
similar_topics: "Similar Topics" similar_topics: "Similar Topics"
position: "Position" position: "Position"
@ -443,6 +451,24 @@ en:
destroy: Destroy destroy: Destroy
destroyed: destroyed destroyed: destroyed
pro:
nav_label: PRO
label: PRO
title: Custom Wizard PRO
authorize: Authorize
authorized: Authorized
unauthorize: cancel
not_subscribed: You're not currently subscribed
subscription:
title:
community: Community Subscription
business: Business Subscription
status:
active: Active
inactive: Inactive
update: Update
last_updated: Last updated
wizard_js: wizard_js:
group: group:
select: "Select a group" select: "Select a group"

Datei anzeigen

@ -17,6 +17,7 @@ en:
name_too_short: "'%{name}' is too short for a custom field name (min length is #{min_length})" name_too_short: "'%{name}' is too short for a custom field name (min length is #{min_length})"
name_already_taken: "'%{name}' is already taken as a custom field name" name_already_taken: "'%{name}' is already taken as a custom field name"
save_default: "Failed to save custom field '%{name}'" save_default: "Failed to save custom field '%{name}'"
pro_type: "%{type} custom fields require PRO Subscription"
field: field:
too_short: "%{label} must be at least %{min} characters" too_short: "%{label} must be at least %{min} characters"
@ -49,6 +50,7 @@ en:
required: "%{property} is required" required: "%{property} is required"
conflict: "Wizard with id '%{wizard_id}' already exists" conflict: "Wizard with id '%{wizard_id}' already exists"
after_time: "After time setting is invalid" after_time: "After time setting is invalid"
pro: "%{type} %{property} is PRO only"
site_settings: site_settings:
custom_wizard_enabled: "Enable custom wizards." custom_wizard_enabled: "Enable custom wizards."

Datei anzeigen

@ -43,5 +43,11 @@ Discourse::Application.routes.append do
get 'admin/wizards/manager/export' => 'admin_manager#export' get 'admin/wizards/manager/export' => 'admin_manager#export'
post 'admin/wizards/manager/import' => 'admin_manager#import' post 'admin/wizards/manager/import' => 'admin_manager#import'
delete 'admin/wizards/manager/destroy' => 'admin_manager#destroy' delete 'admin/wizards/manager/destroy' => 'admin_manager#destroy'
get 'admin/wizards/pro' => 'admin_pro#index'
get 'admin/wizards/pro/authorize' => 'admin_pro#authorize'
get 'admin/wizards/pro/authorize/callback' => 'admin_pro#authorize_callback'
delete 'admin/wizards/pro/authorize' => 'admin_pro#destroy_authentication'
post 'admin/wizards/pro/subscription' => 'admin_pro#update_subscription'
end end
end end

Datei anzeigen

@ -1,7 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController
def index def index
render_json_dump(custom_field_list) render_json_dump(
custom_fields: custom_field_list,
pro_subscribed: CustomWizard::Pro.subscribed?
)
end end
def update def update

Datei anzeigen

@ -0,0 +1,48 @@
# frozen_string_literal: true
class CustomWizard::AdminProController < CustomWizard::AdminController
skip_before_action :check_xhr, :preload_json, :verify_authenticity_token, only: [:authorize, :authorize_callback]
def index
render_serialized(pro, CustomWizard::ProSerializer, root: false)
end
def authorize
request_id = SecureRandom.hex(32)
cookies[:user_api_request_id] = request_id
redirect_to pro.authentication_url(current_user.id, request_id).to_s
end
def authorize_callback
payload = params[:payload]
request_id = cookies[:user_api_request_id]
pro.authentication_response(request_id, payload)
pro.update_subscription
redirect_to '/admin/wizards/pro'
end
def destroy_authentication
if pro.destroy_authentication
render json: success_json
else
render json: failed_json
end
end
def update_subscription
if pro.update_subscription
subscription = CustomWizard::ProSubscriptionSerializer.new(pro.subscription, root: false)
render json: success_json.merge(subscription: subscription)
else
render json: failed_json
end
end
protected
def pro
@pro ||= CustomWizard::Pro.new
end
end

Datei anzeigen

@ -10,7 +10,8 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
), ),
field_types: CustomWizard::Field.types, field_types: CustomWizard::Field.types,
realtime_validations: CustomWizard::RealtimeValidation.types, realtime_validations: CustomWizard::RealtimeValidation.types,
custom_fields: custom_field_list custom_fields: custom_field_list,
pro_subscribed: CustomWizard::Pro.subscribed?
) )
end end

Datei anzeigen

@ -5,6 +5,7 @@ class CustomWizard::WizardController < ::ApplicationController
layout 'wizard' layout 'wizard'
before_action :ensure_plugin_enabled before_action :ensure_plugin_enabled
before_action :update_pro_subscription, only: [:index]
helper_method :wizard_page_title helper_method :wizard_page_title
helper_method :wizard_theme_id helper_method :wizard_theme_id
helper_method :wizard_theme_lookup helper_method :wizard_theme_lookup
@ -82,4 +83,8 @@ class CustomWizard::WizardController < ::ApplicationController
redirect_to path("/") redirect_to path("/")
end end
end end
def update_pro_subscription
CustomWizard::Pro.update_subscription
end
end end

Datei anzeigen

@ -1,5 +1,5 @@
{ {
"result": { "result": {
"line": 91.83 "line": 91.96
} }
} }

Datei anzeigen

@ -0,0 +1,9 @@
# frozen_string_literal: true
class CustomWizard::UpdateProSubscription < ::Jobs::Scheduled
every 1.hour
def execute(args = {})
CustomWizard::Pro.update_subscription
end
end

Datei anzeigen

@ -749,4 +749,8 @@ class CustomWizard::Action
@log.join('; ') @log.join('; ')
) )
end end
def pro_actions
%w[send_message watch_categories send_to_api create_group create_category]
end
end end

Datei anzeigen

@ -21,13 +21,6 @@ class CustomWizard::Builder
@sorted_handlers.sort_by! { |h| -h[:priority] } @sorted_handlers.sort_by! { |h| -h[:priority] }
end end
def mapper
CustomWizard::Mapper.new(
user: @wizard.user,
data: @wizard.current_submission&.fields_and_meta
)
end
def build(build_opts = {}, params = {}) def build(build_opts = {}, params = {})
return nil if !SiteSetting.custom_wizard_enabled || !@wizard return nil if !SiteSetting.custom_wizard_enabled || !@wizard
return @wizard if !@wizard.can_access? && !build_opts[:force] return @wizard if !@wizard.can_access? && !build_opts[:force]
@ -79,6 +72,32 @@ class CustomWizard::Builder
@wizard @wizard
end end
def check_condition(template)
if template['condition'].present?
result = CustomWizard::Mapper.new(
inputs: template['condition'],
user: @wizard.user,
data: @wizard.current_submission&.fields_and_meta,
opts: {
multiple: true
}
).perform
result.any?
else
true
end
end
private
def mapper
CustomWizard::Mapper.new(
user: @wizard.user,
data: @wizard.current_submission&.fields_and_meta
)
end
def append_field(step, step_template, field_template, build_opts) def append_field(step, step_template, field_template, build_opts)
params = { params = {
id: field_template['id'], id: field_template['id'],
@ -222,23 +241,6 @@ class CustomWizard::Builder
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&.fields_and_meta,
opts: {
multiple: true
}
).perform
result.any?
else
true
end
end
def check_if_permitted(step, step_template) def check_if_permitted(step, step_template)
step.permitted = true step.permitted = true

Datei anzeigen

@ -17,7 +17,9 @@ class ::CustomWizard::CustomField
category: ["basic_category"], category: ["basic_category"],
post: ["post"] post: ["post"]
} }
PRO_CLASSES ||= ['category', 'group']
TYPES ||= ["string", "boolean", "integer", "json"] TYPES ||= ["string", "boolean", "integer", "json"]
PRO_TYPES ||= ["json"]
LIST_CACHE_KEY ||= 'custom_field_list' LIST_CACHE_KEY ||= 'custom_field_list'
def self.serializers def self.serializers
@ -37,6 +39,8 @@ class ::CustomWizard::CustomField
send("#{attr}=", value) send("#{attr}=", value)
end end
end end
@pro = CustomWizard::Pro.new
end end
def save def save
@ -81,6 +85,10 @@ class ::CustomWizard::CustomField
next next
end end
if attr == 'klass' && PRO_CLASSES.include?(value) && !@pro.subscribed?
add_error(I18n.t("wizard.custom_field.error.pro_type", type: value))
end
if attr == 'serializers' && (unsupported = value - CLASSES[klass.to_sym]).length > 0 if attr == 'serializers' && (unsupported = value - CLASSES[klass.to_sym]).length > 0
add_error(I18n.t("#{i18n_key}.unsupported_serializers", add_error(I18n.t("#{i18n_key}.unsupported_serializers",
class: klass, class: klass,
@ -92,6 +100,10 @@ class ::CustomWizard::CustomField
add_error(I18n.t("#{i18n_key}.unsupported_type", type: value)) add_error(I18n.t("#{i18n_key}.unsupported_type", type: value))
end end
if attr == 'type' && PRO_TYPES.include?(value) && !@pro.subscribed?
add_error(I18n.t("wizard.custom_field.error.pro_type", type: value))
end
if attr == 'name' if attr == 'name'
unless value.is_a?(String) unless value.is_a?(String)
add_error(I18n.t("#{i18n_key}.name_invalid", name: value)) add_error(I18n.t("#{i18n_key}.name_invalid", name: value))

Datei anzeigen

@ -47,6 +47,7 @@ class CustomWizard::Mapper
@data = params[:data] || {} @data = params[:data] || {}
@user = params[:user] @user = params[:user]
@opts = params[:opts] || {} @opts = params[:opts] || {}
@pro = CustomWizard::Pro.new
end end
def perform def perform
@ -251,7 +252,7 @@ class CustomWizard::Mapper
end end
end end
if opts[:template] if opts[:template] && @pro.subscribed?
template = Liquid::Template.parse(string) template = Liquid::Template.parse(string)
string = template.render(data) string = template.render(data)
end end

186
lib/custom_wizard/pro.rb Normale Datei
Datei anzeigen

@ -0,0 +1,186 @@
# frozen_string_literal: true
class CustomWizard::Pro
include ActiveModel::Serialization
attr_accessor :authentication,
:subscription
def initialize
@authentication = CustomWizard::ProAuthentication.new(get_authentication)
@subscription = CustomWizard::ProSubscription.new(get_subscription)
end
def authorized?
@authentication.active?
end
def subscribed?
@subscription.active?
end
def server
"test.thepavilion.io"
end
def subscription_type
"stripe"
end
def client_name
"custom-wizard"
end
def scope
"discourse-subscription-server:user_subscription"
end
def update_subscription
if @authentication.active?
response = Excon.get(
"https://#{server}/subscription-server/user-subscriptions/#{subscription_type}/#{client_name}",
headers: {
"User-Api-Key" => @authentication.api_key
}
)
if response.status == 200
begin
data = JSON.parse(response.body).deep_symbolize_keys
rescue JSON::ParserError
return false
end
return false unless data && data.is_a?(Hash)
subscriptions = data[:subscriptions]
if subscriptions.present? && type = subscriptions.first[:price_nickname]
@subscription = set_subscription(type)
return true
end
end
end
destroy_subscription
false
end
def destroy_subscription
if remove_subscription
@subscription = CustomWizard::ProSubscription.new(get_subscription)
!@subscription.active?
else
false
end
end
def authentication_url(user_id, request_id)
keys = @authentication.generate_keys(user_id, request_id)
params = {
public_key: keys.public_key,
nonce: keys.nonce,
client_id: @authentication.client_id,
auth_redirect: "#{Discourse.base_url}/admin/wizards/pro/authorize/callback",
application_name: SiteSetting.title,
scopes: scope
}
uri = URI.parse("https://#{server}/user-api-key/new")
uri.query = URI.encode_www_form(params)
uri.to_s
end
def authentication_response(request_id, payload)
data = @authentication.decrypt_payload(request_id, payload)
return false unless data.is_a?(Hash) && data[:key] && data[:user_id]
api_key = data[:key]
user_id = data[:user_id]
user = User.find(user_id)
if user&.admin
@authentication = set_authentication(api_key, user.id)
true
else
false
end
end
def destroy_authentication
if remove_authentication
@authentication = CustomWizard::ProAuthentication.new(get_authentication)
!@authentication.active?
else
false
end
end
def self.subscribed?
self.new.subscribed?
end
def self.authorized?
self.new.authorized?
end
def self.update_subscription
self.new.update_subscription
end
def self.namespace
"custom_wizard_pro"
end
private
def subscription_db_key
"subscription"
end
def authentication_db_key
"authentication"
end
def get_subscription
raw = PluginStore.get(self.class.namespace, subscription_db_key)
if raw.present?
OpenStruct.new(
type: raw['type'],
updated_at: raw['updated_at']
)
end
end
def remove_subscription
PluginStore.remove(self.class.namespace, subscription_db_key)
end
def set_subscription(type)
PluginStore.set(CustomWizard::Pro.namespace, subscription_db_key, type: type, updated_at: Time.now)
CustomWizard::ProSubscription.new(get_subscription)
end
def get_authentication
raw = PluginStore.get(self.class.namespace, authentication_db_key)
OpenStruct.new(
key: raw && raw['key'],
auth_by: raw && raw['auth_by'],
auth_at: raw && raw['auth_at']
)
end
def set_authentication(key, user_id)
PluginStore.set(self.class.namespace, authentication_db_key,
key: key,
auth_by: user_id,
auth_at: Time.now
)
CustomWizard::ProAuthentication.new(get_authentication)
end
def remove_authentication
PluginStore.remove(self.class.namespace, authentication_db_key)
get_authentication
end
end

Datei anzeigen

@ -0,0 +1,95 @@
# frozen_string_literal: true
class CustomWizard::ProAuthentication
include ActiveModel::Serialization
attr_reader :client_id,
:auth_by,
:auth_at,
:api_key
def initialize(auth)
if auth
@api_key = auth.key
@auth_at = auth.auth_at
@auth_by = auth.auth_by
end
@client_id = get_client_id || set_client_id
end
def active?
@api_key.present?
end
def generate_keys(user_id, request_id)
rsa = OpenSSL::PKey::RSA.generate(2048)
nonce = SecureRandom.hex(32)
set_keys(request_id, user_id, rsa, nonce)
OpenStruct.new(nonce: nonce, public_key: rsa.public_key)
end
def decrypt_payload(request_id, payload)
keys = get_keys(request_id)
return false unless keys.present? && keys.pem
delete_keys(request_id)
rsa = OpenSSL::PKey::RSA.new(keys.pem)
decrypted_payload = rsa.private_decrypt(Base64.decode64(payload))
return false unless decrypted_payload.present?
begin
data = JSON.parse(decrypted_payload).symbolize_keys
rescue JSON::ParserError
return false
end
return false unless data[:nonce] == keys.nonce
data[:user_id] = keys.user_id
data
end
def get_keys(request_id)
raw = PluginStore.get(CustomWizard::Pro.namespace, "#{keys_db_key}_#{request_id}")
OpenStruct.new(
user_id: raw && raw['user_id'],
pem: raw && raw['pem'],
nonce: raw && raw['nonce']
)
end
private
def keys_db_key
"keys"
end
def client_id_db_key
"client_id"
end
def set_keys(request_id, user_id, rsa, nonce)
PluginStore.set(CustomWizard::Pro.namespace, "#{keys_db_key}_#{request_id}",
user_id: user_id,
pem: rsa.export,
nonce: nonce
)
end
def delete_keys(request_id)
PluginStore.remove(CustomWizard::Pro.namespace, "#{keys_db_key}_#{request_id}")
end
def get_client_id
PluginStore.get(CustomWizard::Pro.namespace, client_id_db_key)
end
def set_client_id
client_id = SecureRandom.hex(32)
PluginStore.set(CustomWizard::Pro.namespace, client_id_db_key, client_id)
client_id
end
end

Datei anzeigen

@ -0,0 +1,22 @@
# frozen_string_literal: true
class CustomWizard::ProSubscription
include ActiveModel::Serialization
attr_reader :type,
:updated_at
def initialize(subscription)
if subscription
@type = subscription.type
@updated_at = subscription.updated_at
end
end
def types
%w(community business)
end
def active?
types.include?(type) && updated_at.to_datetime > (Time.zone.now - 2.hours).to_datetime
end
end

Datei anzeigen

@ -6,6 +6,7 @@ class CustomWizard::TemplateValidator
def initialize(data, opts = {}) def initialize(data, opts = {})
@data = data @data = data
@opts = opts @opts = opts
@pro = CustomWizard::Pro.new
end end
def perform def perform
@ -14,12 +15,15 @@ class CustomWizard::TemplateValidator
check_id(data, :wizard) check_id(data, :wizard)
check_required(data, :wizard) check_required(data, :wizard)
validate_after_time validate_after_time
validate_pro(data, :wizard)
data[:steps].each do |step| data[:steps].each do |step|
check_required(step, :step) check_required(step, :step)
validate_pro(step, :step)
if data[:fields].present? if step[:fields].present?
data[:fields].each do |field| step[:fields].each do |field|
validate_pro(field, :field)
check_required(field, :field) check_required(field, :field)
end end
end end
@ -27,6 +31,7 @@ class CustomWizard::TemplateValidator
if data[:actions].present? if data[:actions].present?
data[:actions].each do |action| data[:actions].each do |action|
validate_pro(action, :action)
check_required(action, :action) check_required(action, :action)
end end
end end
@ -47,16 +52,53 @@ class CustomWizard::TemplateValidator
} }
end end
def self.pro
{
wizard: {},
step: {
condition: 'present',
index: 'conditional'
},
field: {
condition: 'present',
index: 'conditional'
},
action: {
type: %w[
send_message
add_to_group
create_category
create_group
send_to_api
]
}
}
end
private private
def check_required(object, type) def check_required(object, type)
CustomWizard::TemplateValidator.required[type].each do |property| self.class.required[type].each do |property|
if object[property].blank? if object[property].blank?
errors.add :base, I18n.t("wizard.validation.required", property: property) errors.add :base, I18n.t("wizard.validation.required", property: property)
end end
end end
end end
def validate_pro(object, type)
self.class.pro[type].each do |property, pro_type|
is_pro = object[property.to_s].present? && (
pro_type === 'present' ||
(pro_type === 'conditional' && object[property.to_s].is_a?(Hash)) ||
(pro_type.is_a?(Array) && pro_type.include?(object[property.to_s]))
)
if is_pro && !@pro.subscribed?
errors.add :base, I18n.t("wizard.validation.pro", type: type.to_s, property: property)
end
end
end
def check_id(object, type) def check_id(object, type)
if type === :wizard && @opts[:create] && CustomWizard::Template.exists?(object[:id]) if type === :wizard && @opts[:create] && CustomWizard::Template.exists?(object[:id])
errors.add :base, I18n.t("wizard.validation.conflict", wizard_id: object[:id]) errors.add :base, I18n.t("wizard.validation.conflict", wizard_id: object[:id])

Datei anzeigen

@ -310,10 +310,10 @@ class CustomWizard::Wizard
end end
end end
def self.list(user, template_opts: {}, not_completed: false) def self.list(user, template_opts = {}, not_completed = false)
return [] unless user return [] unless user
CustomWizard::Template.list(template_opts).reduce([]) do |result, template| CustomWizard::Template.list(**template_opts).reduce([]) do |result, template|
wizard = new(template, user) wizard = new(template, user)
result.push(wizard) if wizard.can_access? && ( result.push(wizard) if wizard.can_access? && (
!not_completed || !wizard.completed? !not_completed || !wizard.completed?
@ -325,7 +325,7 @@ class CustomWizard::Wizard
def self.after_signup(user) def self.after_signup(user)
wizards = list( wizards = list(
user, user,
template_opts: { {
setting: 'after_signup', setting: 'after_signup',
order: "(value::json ->> 'permitted') IS NOT NULL DESC" order: "(value::json ->> 'permitted') IS NOT NULL DESC"
} }
@ -336,11 +336,11 @@ class CustomWizard::Wizard
def self.prompt_completion(user) def self.prompt_completion(user)
wizards = list( wizards = list(
user, user,
template_opts: { {
setting: 'prompt_completion', setting: 'prompt_completion',
order: "(value::json ->> 'permitted') IS NOT NULL DESC" order: "(value::json ->> 'permitted') IS NOT NULL DESC"
}, },
not_completed: true true
) )
if wizards.any? if wizards.any?
wizards.map do |w| wizards.map do |w|

Datei anzeigen

@ -71,11 +71,13 @@ after_initialize do
../controllers/custom_wizard/admin/logs.rb ../controllers/custom_wizard/admin/logs.rb
../controllers/custom_wizard/admin/manager.rb ../controllers/custom_wizard/admin/manager.rb
../controllers/custom_wizard/admin/custom_fields.rb ../controllers/custom_wizard/admin/custom_fields.rb
../controllers/custom_wizard/admin/pro.rb
../controllers/custom_wizard/wizard.rb ../controllers/custom_wizard/wizard.rb
../controllers/custom_wizard/steps.rb ../controllers/custom_wizard/steps.rb
../controllers/custom_wizard/realtime_validations.rb ../controllers/custom_wizard/realtime_validations.rb
../jobs/refresh_api_access_token.rb ../jobs/regular/refresh_api_access_token.rb
../jobs/set_after_time_wizard.rb ../jobs/regular/set_after_time_wizard.rb
../jobs/scheduled/update_pro_subscription.rb
../lib/custom_wizard/validators/template.rb ../lib/custom_wizard/validators/template.rb
../lib/custom_wizard/validators/update.rb ../lib/custom_wizard/validators/update.rb
../lib/custom_wizard/action_result.rb ../lib/custom_wizard/action_result.rb
@ -94,6 +96,9 @@ after_initialize do
../lib/custom_wizard/submission.rb ../lib/custom_wizard/submission.rb
../lib/custom_wizard/template.rb ../lib/custom_wizard/template.rb
../lib/custom_wizard/wizard.rb ../lib/custom_wizard/wizard.rb
../lib/custom_wizard/pro.rb
../lib/custom_wizard/pro/subscription.rb
../lib/custom_wizard/pro/authentication.rb
../lib/custom_wizard/api/api.rb ../lib/custom_wizard/api/api.rb
../lib/custom_wizard/api/authorization.rb ../lib/custom_wizard/api/authorization.rb
../lib/custom_wizard/api/endpoint.rb ../lib/custom_wizard/api/endpoint.rb
@ -114,6 +119,9 @@ after_initialize do
../serializers/custom_wizard/log_serializer.rb ../serializers/custom_wizard/log_serializer.rb
../serializers/custom_wizard/submission_serializer.rb ../serializers/custom_wizard/submission_serializer.rb
../serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb ../serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb
../serializers/custom_wizard/pro/authentication_serializer.rb
../serializers/custom_wizard/pro/subscription_serializer.rb
../serializers/custom_wizard/pro_serializer.rb
../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

Datei anzeigen

@ -0,0 +1,11 @@
# frozen_string_literal: true
class CustomWizard::ProAuthenticationSerializer < ApplicationSerializer
attributes :active,
:client_id,
:auth_by,
:auth_at
def active
object.active?
end
end

Datei anzeigen

@ -0,0 +1,10 @@
# frozen_string_literal: true
class CustomWizard::ProSubscriptionSerializer < ApplicationSerializer
attributes :type,
:active,
:updated_at
def active
object.active?
end
end

Datei anzeigen

@ -0,0 +1,6 @@
# frozen_string_literal: true
class CustomWizard::ProSerializer < ApplicationSerializer
attributes :server
has_one :authentication, serializer: CustomWizard::ProAuthenticationSerializer, embed: :objects
has_one :subscription, serializer: CustomWizard::ProSubscriptionSerializer, embed: :objects
end

Datei anzeigen

@ -6,26 +6,22 @@ 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) { let(:wizard_template) { get_wizard_fixture("wizard") }
JSON.parse( let(:open_composer) { get_wizard_fixture("actions/open_composer") }
File.open( let(:create_category) { get_wizard_fixture("actions/create_category") }
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" let(:create_group) { get_wizard_fixture("actions/create_group") }
).read let(:add_to_group) { get_wizard_fixture("actions/add_to_group") }
) let(:send_message) { get_wizard_fixture("actions/send_message") }
} let(:send_message_multi) { get_wizard_fixture("actions/send_message_multi") }
let(:open_composer) { def update_template(template)
JSON.parse( CustomWizard::Template.save(template, skip_jobs: true)
File.open( @template = CustomWizard::Template.find('super_mega_fun_wizard')
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/actions/open_composer.json" end
).read
)
}
before do before do
Group.refresh_automatic_group!(:trust_level_2) Group.refresh_automatic_group!(:trust_level_2)
CustomWizard::Template.save(wizard_template, skip_jobs: true) update_template(wizard_template)
@template = CustomWizard::Template.find('super_mega_fun_wizard')
end end
context 'creating a topic' do context 'creating a topic' do
@ -110,54 +106,6 @@ describe CustomWizard::Action do
end end
end end
context 'sending a message' do
it 'works' do
User.create(username: 'angus1', email: "angus1@email.com")
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: "Message 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.topic_allowed_users.first.user.username).to eq('angus1')
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
it 'updates a profile' do it 'updates a profile' do
wizard = CustomWizard::Builder.new(@template[:id], user).build wizard = CustomWizard::Builder.new(@template[:id], user).build
upload = Upload.create!( upload = Upload.create!(
@ -182,10 +130,8 @@ describe CustomWizard::Action do
updater = wizard.create_updater(wizard.steps[1].id, {}) updater = wizard.create_updater(wizard.steps[1].id, {})
updater.update updater.update
category = Category.find_by(id: wizard.current_submission.fields['action_8'])
expect(updater.result[:redirect_on_next]).to eq( expect(updater.result[:redirect_on_next]).to eq(
"/new-topic?title=Title%20of%20the%20composer%20topic&body=I%20am%20interpolating%20some%20user%20fields%20Angus%20angus%20angus%40email.com&category_id=#{category.id}&tags=tag1" "/new-topic?title=Title%20of%20the%20composer%20topic&body=I%20am%20interpolating%20some%20user%20fields%20Angus%20angus%20angus%40email.com&tags=tag1"
) )
end end
@ -210,35 +156,11 @@ describe CustomWizard::Action do
end end
end end
it 'creates a category' do
wizard = CustomWizard::Builder.new(@template[:id], user).build
wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update
wizard.create_updater(wizard.steps[1].id, {}).update
expect(Category.where(id: wizard.current_submission.fields['action_8']).exists?).to eq(true)
end
it 'creates a group' do
wizard = CustomWizard::Builder.new(@template[:id], user).build
wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update
expect(Group.where(name: wizard.current_submission.fields['action_9']).exists?).to eq(true)
end
it 'adds a user to a group' do
wizard = CustomWizard::Builder.new(@template[:id], user).build
step_id = wizard.steps[0].id
updater = wizard.create_updater(step_id, step_1_field_1: "Text input").update
group = Group.find_by(name: wizard.current_submission.fields['action_9'])
expect(group.users.first.username).to eq('angus')
end
it 'watches categories' do it 'watches categories' do
wizard = CustomWizard::Builder.new(@template[:id], user).build wizard = CustomWizard::Builder.new(@template[:id], user).build
wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update
wizard.create_updater(wizard.steps[1].id, {}).update wizard.create_updater(wizard.steps[1].id, {}).update
expect(CategoryUser.where(
category_id: wizard.current_submission.fields['action_8'],
user_id: user.id
).first.notification_level).to eq(2)
expect(CategoryUser.where( expect(CategoryUser.where(
category_id: category.id, category_id: category.id,
user_id: user.id user_id: user.id
@ -251,4 +173,97 @@ describe CustomWizard::Action do
updater.update updater.update
expect(updater.result[:redirect_on_next]).to eq("https://google.com") expect(updater.result[:redirect_on_next]).to eq("https://google.com")
end end
context "pro actions" do
before do
enable_pro
end
it '#send_message' do
wizard_template['actions'] << send_message
update_template(wizard_template)
User.create(username: 'angus1', email: "angus1@email.com")
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: "Message 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.topic_allowed_users.first.user.username).to eq('angus1')
expect(post.exists?).to eq(true)
end
it '#send_message allows using multiple targets' do
wizard_template['actions'] << send_message_multi
update_template(wizard_template)
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
it '#create_category' do
wizard_template['actions'] << create_category
update_template(wizard_template)
wizard = CustomWizard::Builder.new(@template[:id], user).build
wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update
wizard.create_updater(wizard.steps[1].id, {}).update
expect(Category.where(id: wizard.current_submission.fields['action_8']).exists?).to eq(true)
end
it '#create_group' do
wizard_template['actions'] << create_group
update_template(wizard_template)
wizard = CustomWizard::Builder.new(@template[:id], user).build
wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update
expect(Group.where(name: wizard.current_submission.fields['action_9']).exists?).to eq(true)
end
it '#add_to_group' do
wizard_template['actions'] << create_group
wizard_template['actions'] << add_to_group
update_template(wizard_template)
wizard = CustomWizard::Builder.new(@template[:id], user).build
step_id = wizard.steps[0].id
updater = wizard.create_updater(step_id, step_1_field_1: "Text input").update
group = Group.find_by(name: wizard.current_submission.fields['action_9'])
expect(group.users.first.username).to eq('angus')
end
end
end end

Datei anzeigen

@ -15,45 +15,15 @@ describe CustomWizard::Builder do
fab!(:category2) { Fabricate(:category, name: 'cat2') } fab!(:category2) { Fabricate(:category, name: 'cat2') }
fab!(:group) { Fabricate(:group) } fab!(:group) { Fabricate(:group) }
let(:required_data_json) { let(:wizard_template) { get_wizard_fixture("wizard") }
JSON.parse( let(:required_data_json) { get_wizard_fixture("step/required_data") }
File.open( let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/required_data.json" let(:permitted_param_json) { get_wizard_fixture("step/permitted_params") }
).read let(:user_condition_json) { get_wizard_fixture("condition/user_condition") }
)
}
let(:permitted_json) {
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json"
).read
)
}
let(:permitted_param_json) {
JSON.parse(
File.open(
"#{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
Group.refresh_automatic_group!(:trust_level_3) Group.refresh_automatic_group!(:trust_level_3)
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
@ -283,6 +253,7 @@ describe CustomWizard::Builder do
context "with condition" do context "with condition" do
before do before do
enable_pro
@template[:steps][0][:condition] = user_condition_json['condition'] @template[:steps][0][:condition] = user_condition_json['condition']
CustomWizard::Template.save(@template.as_json) CustomWizard::Template.save(@template.as_json)
end end
@ -321,6 +292,7 @@ describe CustomWizard::Builder do
context "with condition" do context "with condition" do
before do before do
enable_pro
@template[:steps][0][:fields][0][:condition] = user_condition_json['condition'] @template[:steps][0][:fields][0][:condition] = user_condition_json['condition']
CustomWizard::Template.save(@template.as_json) CustomWizard::Template.save(@template.as_json)
end end

Datei anzeigen

@ -3,12 +3,8 @@
require_relative '../../plugin_helper' require_relative '../../plugin_helper'
describe CustomWizard::CustomField do describe CustomWizard::CustomField do
let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") }
let(:custom_field_json) { let(:custom_field_pro_json) { get_wizard_fixture("custom_field/pro_custom_fields") }
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json"
).read)
}
before do before do
CustomWizard::CustomField.invalidate_cache CustomWizard::CustomField.invalidate_cache
@ -104,8 +100,8 @@ describe CustomWizard::CustomField do
it "does not save with an unsupported serializer" do it "does not save with an unsupported serializer" do
invalid_field_json = custom_field_json['custom_fields'].first invalid_field_json = custom_field_json['custom_fields'].first
invalid_field_json['klass'] = 'category' invalid_field_json['klass'] = 'post'
invalid_field_json['serializers'] = ['category', 'site_category'] invalid_field_json['serializers'] = ['post', 'post_revision']
custom_field = CustomWizard::CustomField.new(nil, invalid_field_json) custom_field = CustomWizard::CustomField.new(nil, invalid_field_json)
@ -113,8 +109,8 @@ describe CustomWizard::CustomField do
expect(custom_field.valid?).to eq(false) expect(custom_field.valid?).to eq(false)
expect(custom_field.errors.full_messages.first).to eq( expect(custom_field.errors.full_messages.first).to eq(
I18n.t("wizard.custom_field.error.unsupported_serializers", I18n.t("wizard.custom_field.error.unsupported_serializers",
class: "category", class: "post",
serializers: "category, site_category" serializers: "post_revision"
) )
) )
expect( expect(
@ -196,6 +192,50 @@ describe CustomWizard::CustomField do
).exists? ).exists?
).to eq(false) ).to eq(false)
end end
it "does not save pro field types without a pro subscription" do
pro_field_json = custom_field_pro_json['custom_fields'].first
custom_field = CustomWizard::CustomField.new(nil, pro_field_json)
expect(custom_field.save).to eq(false)
expect(custom_field.valid?).to eq(false)
expect(custom_field.errors.full_messages.first).to eq(
I18n.t("wizard.custom_field.error.pro_type", type: "json")
)
end
it "does not save pro field classes without a pro subscription" do
pro_field_json = custom_field_pro_json['custom_fields'].second
custom_field = CustomWizard::CustomField.new(nil, pro_field_json)
expect(custom_field.save).to eq(false)
expect(custom_field.valid?).to eq(false)
expect(custom_field.errors.full_messages.first).to eq(
I18n.t("wizard.custom_field.error.pro_type", type: "category")
)
end
context "with a pro subscription" do
before do
enable_pro
end
it "saves pro field types" do
pro_field_json = custom_field_pro_json['custom_fields'].first
custom_field = CustomWizard::CustomField.new(nil, pro_field_json)
expect(custom_field.save).to eq(true)
expect(custom_field.valid?).to eq(true)
end
it "saves pro field classes" do
pro_field_json = custom_field_pro_json['custom_fields'].second
custom_field = CustomWizard::CustomField.new(nil, pro_field_json)
expect(custom_field.save).to eq(true)
expect(custom_field.valid?).to eq(true)
end
end
end end
context "lists" do context "lists" do
@ -205,15 +245,15 @@ describe CustomWizard::CustomField do
end end
end end
it "lists saved custom field records" do it "saved custom field records" do
expect(CustomWizard::CustomField.list.length).to eq(4) expect(CustomWizard::CustomField.list.length).to eq(2)
end end
it "lists saved custom field records by attribute value" do it "saved custom field records by attribute value" do
expect(CustomWizard::CustomField.list_by(:klass, 'topic').length).to eq(1) expect(CustomWizard::CustomField.list_by(:klass, 'topic').length).to eq(1)
end end
it "lists saved custom field records by optional values" do it "saved custom field records by optional values" do
field_json = custom_field_json['custom_fields'].first field_json = custom_field_json['custom_fields'].first
field_json['serializers'] = nil field_json['serializers'] = nil
@ -221,12 +261,12 @@ describe CustomWizard::CustomField do
expect(CustomWizard::CustomField.list_by(:serializers, ['post']).length).to eq(0) expect(CustomWizard::CustomField.list_by(:serializers, ['post']).length).to eq(0)
end end
it "lists custom field records added by other plugins " do it "custom field records added by other plugins " do
expect(CustomWizard::CustomField.external_list.length).to eq(11) expect(CustomWizard::CustomField.external_list.length).to be > 10
end end
it "lists all custom field records" do it "all custom field records" do
expect(CustomWizard::CustomField.full_list.length).to eq(15) expect(CustomWizard::CustomField.full_list.length).to be > 12
end end
end end

Datei anzeigen

@ -2,11 +2,7 @@
require_relative '../../plugin_helper' require_relative '../../plugin_helper'
describe CustomWizard::Field do describe CustomWizard::Field do
let(:field_hash) do let(:field_hash) { get_wizard_fixture("field/field") }
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(

Datei anzeigen

@ -31,16 +31,8 @@ describe CustomWizard::Mapper do
] ]
) )
} }
let(:inputs) { let(:inputs) { get_wizard_fixture("mapper/inputs") }
JSON.parse(File.open( let(:data) { get_wizard_fixture("mapper/data") }
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/mapper/inputs.json"
).read)
}
let(:data) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/mapper/data.json"
).read)
}
let(:template_params) { let(:template_params) {
{ {
"step_1_field_1" => "Hello" "step_1_field_1" => "Hello"
@ -352,6 +344,24 @@ describe CustomWizard::Mapper do
expect(result).to eq(template_params["step_1_field_1"]) expect(result).to eq(template_params["step_1_field_1"])
end end
it "requires a pro subscription" do
template = '{{ "w{step_1_field_1}" | size }}'
mapper = create_template_mapper(template_params, user1)
result = mapper.interpolate(
template.dup,
template: true,
user: true,
wizard: true,
value: true
)
expect(result).to eq("{{ \"#{template_params["step_1_field_1"]}\" | size }}")
end
context "with a pro subscription" do
before do
enable_pro
end
it "treats replaced values as string literals" do it "treats replaced values as string literals" do
template = '{{ "w{step_1_field_1}" | size }}' template = '{{ "w{step_1_field_1}" | size }}'
mapper = create_template_mapper(template_params, user1) mapper = create_template_mapper(template_params, user1)
@ -461,4 +471,5 @@ describe CustomWizard::Mapper do
end end
end end
end end
end
end end

Datei anzeigen

@ -0,0 +1,125 @@
# frozen_string_literal: true
require_relative '../../plugin_helper'
describe CustomWizard::Pro do
fab!(:user) { Fabricate(:user) }
it "initializes pro authentication and subscription" do
pro = described_class.new
expect(pro.authentication.class).to eq(CustomWizard::ProAuthentication)
expect(pro.subscription.class).to eq(CustomWizard::ProSubscription)
end
it "returns authorized and subscribed states" do
pro = described_class.new
expect(pro.authorized?).to eq(false)
expect(pro.subscribed?).to eq(false)
end
context "subscription" do
before do
@pro = described_class.new
end
it "updates valid subscriptions" do
stub_subscription_request(200, valid_subscription)
expect(@pro.update_subscription).to eq(true)
expect(@pro.subscribed?).to eq(true)
end
it "handles invalid subscriptions" do
stub_subscription_request(200, invalid_subscription)
expect(@pro.update_subscription).to eq(false)
expect(@pro.subscribed?).to eq(false)
end
it "handles subscription http errors" do
stub_subscription_request(404, {})
expect(@pro.update_subscription).to eq(false)
expect(@pro.subscribed?).to eq(false)
end
it "destroys subscriptions" do
stub_subscription_request(200, valid_subscription)
expect(@pro.update_subscription).to eq(true)
expect(@pro.destroy_subscription).to eq(true)
expect(@pro.subscribed?).to eq(false)
end
it "has class aliases" do
authenticate_pro
stub_subscription_request(200, valid_subscription)
expect(described_class.update_subscription).to eq(true)
expect(described_class.subscribed?).to eq(true)
end
end
context "authentication" do
before do
@pro = described_class.new
user.update!(admin: true)
end
it "generates a valid authentication request url" do
request_id = SecureRandom.hex(32)
uri = URI(@pro.authentication_url(user.id, request_id))
expect(uri.host).to eq(@pro.server)
parsed_query = Rack::Utils.parse_query uri.query
expect(parsed_query['public_key'].present?).to eq(true)
expect(parsed_query['nonce'].present?).to eq(true)
expect(parsed_query['client_id'].present?).to eq(true)
expect(parsed_query['auth_redirect'].present?).to eq(true)
expect(parsed_query['application_name']).to eq(SiteSetting.title)
expect(parsed_query['scopes']).to eq(@pro.scope)
end
def generate_payload(request_id, user_id)
uri = URI(@pro.authentication_url(user_id, request_id))
keys = @pro.authentication.get_keys(request_id)
raw_payload = {
key: "12345",
nonce: keys.nonce,
push: false,
api: UserApiKeysController::AUTH_API_VERSION
}.to_json
public_key = OpenSSL::PKey::RSA.new(keys.pem)
Base64.encode64(public_key.public_encrypt(raw_payload))
end
it "handles authentication response if request and response is valid" do
request_id = SecureRandom.hex(32)
payload = generate_payload(request_id, user.id)
expect(@pro.authentication_response(request_id, payload)).to eq(true)
expect(@pro.authorized?).to eq(true)
end
it "discards authentication response if user who made request as not an admin" do
user.update!(admin: false)
request_id = SecureRandom.hex(32)
payload = generate_payload(request_id, user.id)
expect(@pro.authentication_response(request_id, payload)).to eq(false)
expect(@pro.authorized?).to eq(false)
end
it "discards authentication response if request_id is invalid" do
payload = generate_payload(SecureRandom.hex(32), user.id)
expect(@pro.authentication_response(SecureRandom.hex(32), payload)).to eq(false)
expect(@pro.authorized?).to eq(false)
end
it "destroys authentication" do
request_id = SecureRandom.hex(32)
payload = generate_payload(request_id, user.id)
@pro.authentication_response(request_id, payload)
expect(@pro.destroy_authentication).to eq(true)
expect(@pro.authorized?).to eq(false)
end
end
end

Datei anzeigen

@ -2,21 +2,8 @@
require_relative '../../plugin_helper' require_relative '../../plugin_helper'
describe CustomWizard::Step do describe CustomWizard::Step do
let(:step_hash) do let(:step_hash) { get_wizard_fixture("step/step") }
JSON.parse( let(:field_hash) { get_wizard_fixture("field/field") }
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 before do
@step = CustomWizard::Step.new(step_hash[:id]) @step = CustomWizard::Step.new(step_hash[:id])

Datei anzeigen

@ -4,12 +4,7 @@ require_relative '../../plugin_helper'
describe CustomWizard::Submission do describe CustomWizard::Submission do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
fab!(:user2) { Fabricate(:user) } fab!(:user2) { Fabricate(:user) }
let(:template_json) { get_wizard_fixture("wizard") }
let(:template_json) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read)
}
before do before do
CustomWizard::Template.save(template_json, skip_jobs: true) CustomWizard::Template.save(template_json, skip_jobs: true)

Datei anzeigen

@ -3,17 +3,8 @@ require_relative '../../plugin_helper'
describe CustomWizard::Template do describe CustomWizard::Template do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
let(:template_json) { get_wizard_fixture("wizard") }
let(:template_json) { let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read)
}
let(:permitted_json) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json"
).read)
}
before do before do
CustomWizard::Template.save(template_json, skip_jobs: true) CustomWizard::Template.save(template_json, skip_jobs: true)

Datei anzeigen

@ -3,12 +3,9 @@ require_relative '../../plugin_helper'
describe CustomWizard::TemplateValidator do describe CustomWizard::TemplateValidator do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
let(:template) { get_wizard_fixture("wizard") }
let(:template) { let(:create_category) { get_wizard_fixture("actions/create_category") }
JSON.parse(File.open( let(:user_condition) { get_wizard_fixture("condition/user_condition") }
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read).with_indifferent_access
}
it "validates valid templates" do it "validates valid templates" do
expect( expect(
@ -45,4 +42,52 @@ describe CustomWizard::TemplateValidator do
CustomWizard::TemplateValidator.new(template).perform CustomWizard::TemplateValidator.new(template).perform
).to eq(false) ).to eq(false)
end end
it "invalidates pro step attributes without a pro subscription" do
template[:steps][0][:condition] = user_condition['condition']
expect(
CustomWizard::TemplateValidator.new(template).perform
).to eq(false)
end
it "invalidates pro field attributes without a pro subscription" do
template[:steps][0][:fields][0][:condition] = user_condition['condition']
expect(
CustomWizard::TemplateValidator.new(template).perform
).to eq(false)
end
it "invalidates pro actions without a pro subscription" do
template[:actions] << create_category
expect(
CustomWizard::TemplateValidator.new(template).perform
).to eq(false)
end
context "with pro subscription" do
before do
enable_pro
end
it "validates pro step attributes" do
template[:steps][0][:condition] = user_condition['condition']
expect(
CustomWizard::TemplateValidator.new(template).perform
).to eq(true)
end
it "validates pro field attributes" do
template[:steps][0][:fields][0][:condition] = user_condition['condition']
expect(
CustomWizard::TemplateValidator.new(template).perform
).to eq(true)
end
it "validates pro actions" do
template[:actions] << create_category
expect(
CustomWizard::TemplateValidator.new(template).perform
).to eq(true)
end
end
end end

Datei anzeigen

@ -3,12 +3,7 @@ require_relative '../../plugin_helper'
describe CustomWizard::UpdateValidator do describe CustomWizard::UpdateValidator do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
let(:template) { get_wizard_fixture("wizard") }
let(:template) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read).with_indifferent_access
}
before do before do
CustomWizard::Template.save(template, skip_jobs: true) CustomWizard::Template.save(template, skip_jobs: true)

Datei anzeigen

@ -6,22 +6,8 @@ describe CustomWizard::Wizard do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
fab!(:trusted_user) { Fabricate(:user, trust_level: TrustLevel[3]) } fab!(:trusted_user) { Fabricate(:user, trust_level: TrustLevel[3]) }
fab!(:admin_user) { Fabricate(:user, admin: true) } fab!(:admin_user) { Fabricate(:user, admin: true) }
let(:template_json) { get_wizard_fixture("wizard") }
let(:template_json) { let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read
)
}
let(:permitted_json) {
JSON.parse(
File.open(
"#{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)

Datei anzeigen

@ -9,11 +9,8 @@ describe "custom field extensions" do
fab!(:group) { Fabricate(:group) } fab!(:group) { Fabricate(:group) }
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
let(:custom_field_json) { let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") }
JSON.parse(File.open( let(:pro_custom_field_json) { get_wizard_fixture("custom_field/pro_custom_fields") }
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json"
).read)
}
before do before do
custom_field_json['custom_fields'].each do |field_json| custom_field_json['custom_fields'].each do |field_json|
@ -75,13 +72,23 @@ describe "custom field extensions" do
end end
end end
context "pro custom fields" do
before do
enable_pro
pro_custom_field_json['custom_fields'].each do |field_json|
custom_field = CustomWizard::CustomField.new(nil, field_json)
custom_field.save
end
end
context "category" do context "category" do
it "registers category custom fields" do it "registers" do
category category
expect(Category.get_custom_field_type("category_field_1")).to eq(:json) expect(Category.get_custom_field_type("category_field_1")).to eq(:json)
end end
it "adds category custom fields to the basic category serializer" do it "adds custom fields to the basic category serializer" do
category.custom_fields["category_field_1"] = { a: 1, b: 2 }.to_json category.custom_fields["category_field_1"] = { a: 1, b: 2 }.to_json
category.save_custom_fields(true) category.save_custom_fields(true)
@ -96,12 +103,12 @@ describe "custom field extensions" do
end end
context "group" do context "group" do
it "registers group custom fields" do it "registers" do
group group
expect(Group.get_custom_field_type("group_field_1")).to eq(:string) expect(Group.get_custom_field_type("group_field_1")).to eq(:string)
end end
it "adds group custom fields to the basic group serializer" do it "adds custom fields to the basic group serializer" do
group.custom_fields["group_field_1"] = "Hello" group.custom_fields["group_field_1"] = "Hello"
group.save_custom_fields(true) group.save_custom_fields(true)
@ -114,4 +121,5 @@ describe "custom field extensions" do
expect(serializer[:group_field_1]).to eq("Hello") expect(serializer[:group_field_1]).to eq("Hello")
end end
end end
end
end end

Datei anzeigen

@ -4,18 +4,8 @@ require_relative '../plugin_helper'
describe ExtraLocalesControllerCustomWizard, type: :request do describe ExtraLocalesControllerCustomWizard, type: :request do
let(:new_user) { Fabricate(:user, trust_level: TrustLevel[0]) } let(:new_user) { Fabricate(:user, trust_level: TrustLevel[0]) }
let(:staff_user) { Fabricate(:moderator) } let(:staff_user) { Fabricate(:moderator) }
let(:template) { get_wizard_fixture("wizard") }
let(:template) { let(:permitted) { get_wizard_fixture("wizard/permitted") }
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read)
}
let(:permitted) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json"
).read)
}
before do before do
CustomWizard::Template.save(template, skip_jobs: true) CustomWizard::Template.save(template, skip_jobs: true)

Datei anzeigen

@ -4,12 +4,7 @@ require_relative '../plugin_helper'
describe InvitesControllerCustomWizard, type: :request do describe InvitesControllerCustomWizard, type: :request do
fab!(:topic) { Fabricate(:topic) } fab!(:topic) { Fabricate(:topic) }
let(:invite) { Invite.generate(topic.user, email: "angus@mcleod.org", topic: topic) } let(:invite) { Invite.generate(topic.user, email: "angus@mcleod.org", topic: topic) }
let(:template) { get_wizard_fixture("wizard") }
let(:template) do
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read)
end
before do before do
@controller = InvitesController.new @controller = InvitesController.new

Datei anzeigen

@ -2,11 +2,7 @@
require_relative '../plugin_helper' require_relative '../plugin_helper'
describe CustomWizardUsersController, type: :request do describe CustomWizardUsersController, type: :request do
let(:template) do let(:template) { get_wizard_fixture("wizard") }
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read)
end
before do before do
@controller = UsersController.new @controller = UsersController.new

13
spec/fixtures/actions/add_to_group.json gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,13 @@
{
"id": "action_6",
"run_after": "step_1",
"type": "add_to_group",
"group": [
{
"type": "assignment",
"output": "action_9",
"output_type": "wizard_action",
"output_connector": "set"
}
]
}

51
spec/fixtures/actions/create_category.json gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,51 @@
{
"id": "action_8",
"run_after": "step_1",
"type": "create_category",
"custom_fields": [
{
"type": "association",
"pairs": [
{
"index": 0,
"key": "category_custom_field",
"key_type": "text",
"value": "CC Val",
"value_type": "text",
"connector": "association"
}
]
}
],
"name": [
{
"type": "assignment",
"output": "step_1_field_1",
"output_type": "wizard_field",
"output_connector": "set"
}
],
"slug": [
{
"type": "assignment",
"output": "step_1_field_1",
"output_type": "wizard_field",
"output_connector": "set"
}
],
"permissions": [
{
"type": "association",
"pairs": [
{
"index": 0,
"key": "action_9",
"key_type": "wizard_action",
"value": "2",
"value_type": "text",
"connector": "association"
}
]
}
]
}

104
spec/fixtures/actions/create_group.json gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,104 @@
{
"id": "action_9",
"run_after": "step_1",
"type": "create_group",
"title": [
{
"type": "assignment",
"output": "New Group Member",
"output_type": "text",
"output_connector": "set"
}
],
"custom_fields": [
{
"type": "association",
"pairs": [
{
"index": 0,
"key": "group_custom_field",
"key_type": "text",
"value": "step_3_field_1",
"value_type": "wizard_field",
"connector": "association"
}
]
}
],
"name": [
{
"type": "assignment",
"output": "step_1_field_1",
"output_type": "wizard_field",
"output_connector": "set"
}
],
"full_name": [
{
"type": "assignment",
"output": "step_1_field_1",
"output_type": "wizard_field",
"output_connector": "set"
}
],
"usernames": [
{
"type": "assignment",
"output_type": "user",
"output_connector": "set",
"output": [
"angus1"
]
}
],
"owner_usernames": [
{
"type": "assignment",
"output_type": "user",
"output_connector": "set",
"output": [
"angus"
]
}
],
"grant_trust_level": [
{
"type": "assignment",
"output": "3",
"output_type": "text",
"output_connector": "set"
}
],
"mentionable_level": [
{
"type": "assignment",
"output": "1",
"output_type": "text",
"output_connector": "set"
}
],
"messageable_level": [
{
"type": "assignment",
"output": "2",
"output_type": "text",
"output_connector": "set"
}
],
"visibility_level": [
{
"type": "assignment",
"output": "3",
"output_type": "text",
"output_connector": "set"
}
],
"members_visibility_level": [
{
"type": "assignment",
"output": "99",
"output_type": "text",
"output_connector": "set"
}
]
}

25
spec/fixtures/actions/send_message.json gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,25 @@
{
"id": "action_2",
"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": "Message title",
"output_type": "text",
"output_connector": "set"
}
],
"recipient": [
{
"type": "assignment",
"output_type": "user",
"output_connector": "set",
"output": [
"angus1"
]
}
]
}

Datei anzeigen

@ -0,0 +1,28 @@
{
"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"
]
}
]
}

Datei anzeigen

@ -16,22 +16,6 @@
"serializers": [ "serializers": [
"post" "post"
] ]
},
{
"klass": "category",
"name": "category_field_1",
"type": "json",
"serializers": [
"basic_category"
]
},
{
"klass": "group",
"name": "group_field_1",
"type": "string",
"serializers": [
"basic_group"
]
} }
] ]
} }

Datei anzeigen

@ -0,0 +1,28 @@
{
"custom_fields": [
{
"klass": "topic",
"name": "topic_field_2",
"type": "json",
"serializers": [
"topic_view"
]
},
{
"klass": "category",
"name": "category_field_1",
"type": "json",
"serializers": [
"basic_category"
]
},
{
"klass": "group",
"name": "group_field_1",
"type": "string",
"serializers": [
"basic_group"
]
}
]
}

Datei anzeigen

@ -163,197 +163,6 @@
} }
], ],
"actions": [ "actions": [
{
"id": "action_9",
"run_after": "step_1",
"type": "create_group",
"title": [
{
"type": "assignment",
"output": "New Group Member",
"output_type": "text",
"output_connector": "set"
}
],
"custom_fields": [
{
"type": "association",
"pairs": [
{
"index": 0,
"key": "group_custom_field",
"key_type": "text",
"value": "step_3_field_1",
"value_type": "wizard_field",
"connector": "association"
}
]
}
],
"name": [
{
"type": "assignment",
"output": "step_1_field_1",
"output_type": "wizard_field",
"output_connector": "set"
}
],
"full_name": [
{
"type": "assignment",
"output": "step_1_field_1",
"output_type": "wizard_field",
"output_connector": "set"
}
],
"usernames": [
{
"type": "assignment",
"output_type": "user",
"output_connector": "set",
"output": [
"angus1"
]
}
],
"owner_usernames": [
{
"type": "assignment",
"output_type": "user",
"output_connector": "set",
"output": [
"angus"
]
}
],
"grant_trust_level": [
{
"type": "assignment",
"output": "3",
"output_type": "text",
"output_connector": "set"
}
],
"mentionable_level": [
{
"type": "assignment",
"output": "1",
"output_type": "text",
"output_connector": "set"
}
],
"messageable_level": [
{
"type": "assignment",
"output": "2",
"output_type": "text",
"output_connector": "set"
}
],
"visibility_level": [
{
"type": "assignment",
"output": "3",
"output_type": "text",
"output_connector": "set"
}
],
"members_visibility_level": [
{
"type": "assignment",
"output": "99",
"output_type": "text",
"output_connector": "set"
}
]
},
{
"id": "action_6",
"run_after": "step_1",
"type": "add_to_group",
"group": [
{
"type": "assignment",
"output": "action_9",
"output_type": "wizard_action",
"output_connector": "set"
}
]
},
{
"id": "action_8",
"run_after": "step_1",
"type": "create_category",
"custom_fields": [
{
"type": "association",
"pairs": [
{
"index": 0,
"key": "category_custom_field",
"key_type": "text",
"value": "CC Val",
"value_type": "text",
"connector": "association"
}
]
}
],
"name": [
{
"type": "assignment",
"output": "step_1_field_1",
"output_type": "wizard_field",
"output_connector": "set"
}
],
"slug": [
{
"type": "assignment",
"output": "step_1_field_1",
"output_type": "wizard_field",
"output_connector": "set"
}
],
"permissions": [
{
"type": "association",
"pairs": [
{
"index": 0,
"key": "action_9",
"key_type": "wizard_action",
"value": "2",
"value_type": "text",
"connector": "association"
}
]
}
]
},
{
"id": "action_5",
"run_after": "step_1",
"type": "watch_categories",
"notification_level": "tracking",
"wizard_user": true,
"categories": [
{
"type": "assignment",
"output": "action_8",
"output_type": "wizard_action",
"output_connector": "set"
}
],
"mute_remainder": [
{
"type": "assignment",
"output": "true",
"output_type": "text",
"output_connector": "set"
}
]
},
{ {
"id": "action_1", "id": "action_1",
"run_after": "step_3", "run_after": "step_3",
@ -442,6 +251,29 @@
} }
] ]
}, },
{
"id": "action_5",
"run_after": "step_1",
"type": "watch_categories",
"notification_level": "tracking",
"wizard_user": true,
"categories": [
{
"type": "assignment",
"output": "action_8",
"output_type": "wizard_action",
"output_connector": "set"
}
],
"mute_remainder": [
{
"type": "assignment",
"output": "true",
"output_type": "text",
"output_connector": "set"
}
]
},
{ {
"id": "action_4", "id": "action_4",
"run_after": "step_2", "run_after": "step_2",
@ -462,59 +294,6 @@
} }
] ]
}, },
{
"id": "action_2",
"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": "Message title",
"output_type": "text",
"output_connector": "set"
}
],
"recipient": [
{
"type": "assignment",
"output_type": "user",
"output_connector": "set",
"output": [
"angus1"
]
}
]
},
{
"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",
@ -529,24 +308,6 @@
"output_connector": "set" "output_connector": "set"
} }
], ],
"category": [
{
"type": "assignment",
"output": "action_8",
"output_type": "wizard_action",
"output_connector": "set",
"pairs": [
{
"index": 0,
"key": "step_2_field_5",
"key_type": "wizard_field",
"value": "true",
"value_type": "text",
"connector": "is"
}
]
}
],
"tags": [ "tags": [
{ {
"type": "assignment", "type": "assignment",

Datei anzeigen

@ -7,11 +7,7 @@ describe Jobs::SetAfterTimeWizard do
fab!(:user2) { Fabricate(:user) } fab!(:user2) { Fabricate(:user) }
fab!(:user3) { Fabricate(:user) } fab!(:user3) { Fabricate(:user) }
let(:template) { let(:template) { get_wizard_fixture("wizard") }
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read).with_indifferent_access
}
it "sets wizard redirect for all users " do it "sets wizard redirect for all users " do
after_time_template = template.dup after_time_template = template.dup

Datei anzeigen

@ -0,0 +1,11 @@
# frozen_string_literal: true
require_relative '../plugin_helper'
describe CustomWizard::UpdateProSubscription do
it "updates the pro subscription" do
stub_subscription_request(200, valid_subscription)
described_class.new.execute
expect(CustomWizard::Pro.subscribed?).to eq(true)
end
end

Datei anzeigen

@ -15,3 +15,44 @@ require 'oj'
Oj.default_options = Oj.default_options.merge(cache_str: -1) Oj.default_options = Oj.default_options.merge(cache_str: -1)
require 'rails_helper' require 'rails_helper'
def get_wizard_fixture(path)
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/#{path}.json"
).read
).with_indifferent_access
end
def authenticate_pro
CustomWizard::ProAuthentication.any_instance.stubs(:active?).returns(true)
end
def enable_pro
CustomWizard::Pro.any_instance.stubs(:subscribed?).returns(true)
end
def disable_pro
CustomWizard::Pro.any_instance.stubs(:subscribed?).returns(false)
end
def valid_subscription
{
product_id: "prod_CBTNpi3fqWWkq0",
price_id: "price_id",
price_nickname: "business"
}
end
def invalid_subscription
{
product_id: "prod_CBTNpi3fqWWkq0",
price_id: "price_id"
}
end
def stub_subscription_request(status, subscription)
authenticate_pro
pro = CustomWizard::Pro.new
stub_request(:get, "https://#{pro.server}/subscription-server/user-subscriptions/#{pro.subscription_type}/#{pro.client_name}").to_return(status: status, body: { subscriptions: [subscription] }.to_json)
end

Datei anzeigen

@ -3,12 +3,7 @@ require_relative '../../../plugin_helper'
describe CustomWizard::AdminCustomFieldsController do describe CustomWizard::AdminCustomFieldsController do
fab!(:admin_user) { Fabricate(:user, admin: true) } fab!(:admin_user) { Fabricate(:user, admin: true) }
let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") }
let(:custom_field_json) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json"
).read)
}
before do before do
custom_field_json['custom_fields'].each do |field_json| custom_field_json['custom_fields'].each do |field_json|
@ -19,7 +14,7 @@ describe CustomWizard::AdminCustomFieldsController do
it "returns the full list of custom fields" do it "returns the full list of custom fields" do
get "/admin/wizards/custom-fields.json" get "/admin/wizards/custom-fields.json"
expect(response.parsed_body.length).to eq(15) expect(response.parsed_body["custom_fields"].length).to be > 12
end end
it "saves custom fields" do it "saves custom fields" do

Datei anzeigen

@ -3,12 +3,7 @@ require_relative '../../../plugin_helper'
describe CustomWizard::AdminManagerController do describe CustomWizard::AdminManagerController do
fab!(:admin_user) { Fabricate(:user, admin: true) } fab!(:admin_user) { Fabricate(:user, admin: true) }
let(:template) { get_wizard_fixture("wizard") }
let(:template) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read)
}
before do before do
sign_in(admin_user) sign_in(admin_user)

Datei anzeigen

@ -0,0 +1,71 @@
# frozen_string_literal: true
require_relative '../../../plugin_helper'
describe CustomWizard::AdminProController do
fab!(:admin_user) { Fabricate(:user, admin: true) }
def generate_payload(request_id, user_id)
uri = URI(@pro.authentication_url(user_id, request_id))
keys = @pro.authentication.get_keys(request_id)
raw_payload = {
key: "12345",
nonce: keys.nonce,
push: false,
api: UserApiKeysController::AUTH_API_VERSION
}.to_json
public_key = OpenSSL::PKey::RSA.new(keys.pem)
Base64.encode64(public_key.public_encrypt(raw_payload))
end
before do
@pro = CustomWizard::Pro.new
sign_in(admin_user)
end
it "#index" do
get "/admin/wizards/pro.json"
expect(response.parsed_body['server']).to eq(@pro.server)
expect(response.parsed_body['authentication'].deep_symbolize_keys).to eq(CustomWizard::ProAuthenticationSerializer.new(@pro.authentication, root: false).as_json)
expect(response.parsed_body['subscription'].deep_symbolize_keys).to eq(CustomWizard::ProSubscriptionSerializer.new(@pro.subscription, root: false).as_json)
end
it "#authorize" do
get "/admin/wizards/pro/authorize"
expect(response.status).to eq(302)
expect(cookies[:user_api_request_id].present?).to eq(true)
end
it "#destroy_authentication" do
request_id = SecureRandom.hex(32)
payload = generate_payload(request_id, admin_user.id)
@pro.authentication_response(request_id, payload)
delete "/admin/wizards/pro/authorize.json"
expect(response.status).to eq(200)
expect(CustomWizard::Pro.authorized?).to eq(false)
end
context "subscription" do
before do
stub_subscription_request(200, valid_subscription)
end
it "handles authentication response and the updates subscription" do
request_id = cookies[:user_api_request_id] = SecureRandom.hex(32)
payload = generate_payload(request_id, admin_user.id)
get "/admin/wizards/pro/authorize/callback", params: { payload: payload }
expect(response).to redirect_to("/admin/wizards/pro")
expect(CustomWizard::Pro.subscribed?).to eq(true)
end
it "updates the subscription" do
authenticate_pro
post "/admin/wizards/pro/subscription.json"
expect(response.status).to eq(200)
expect(CustomWizard::Pro.subscribed?).to eq(true)
end
end
end

Datei anzeigen

@ -7,12 +7,7 @@ describe CustomWizard::AdminSubmissionsController do
fab!(:user2) { Fabricate(:user) } fab!(:user2) { Fabricate(:user) }
fab!(:user3) { Fabricate(:user) } fab!(:user3) { Fabricate(:user) }
let(:template) { let(:template) { get_wizard_fixture("wizard") }
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read)
}
let(:template_2) { let(:template_2) {
temp = template.dup temp = template.dup
temp["id"] = "super_mega_fun_wizard_2" temp["id"] = "super_mega_fun_wizard_2"

Datei anzeigen

@ -5,12 +5,7 @@ describe CustomWizard::AdminWizardController do
fab!(:admin_user) { Fabricate(:user, admin: true) } fab!(:admin_user) { Fabricate(:user, admin: true) }
fab!(:user1) { Fabricate(:user) } fab!(:user1) { Fabricate(:user) }
fab!(:user2) { Fabricate(:user) } fab!(:user2) { Fabricate(:user) }
let(:template) { get_wizard_fixture("wizard") }
let(:template) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read)
}
before do before do
CustomWizard::Template.save(template, skip_jobs: true) CustomWizard::Template.save(template, skip_jobs: true)

Datei anzeigen

@ -2,21 +2,11 @@
require_relative '../../plugin_helper' require_relative '../../plugin_helper'
describe ApplicationController do describe ApplicationController do
fab!(:user) { fab!(:user) { Fabricate(:user, username: 'angus', email: "angus@email.com", trust_level: TrustLevel[3]) }
Fabricate( let(:wizard_template) { get_wizard_fixture("wizard") }
:user,
username: 'angus',
email: "angus@email.com",
trust_level: TrustLevel[3]
)
}
before do before do
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

Datei anzeigen

@ -8,12 +8,8 @@ describe "custom field extensions" do
let!(:category) { Fabricate(:category) } let!(:category) { Fabricate(:category) }
let!(:user) { Fabricate(:user) } let!(:user) { Fabricate(:user) }
let!(:group) { Fabricate(:group, users: [user]) } let!(:group) { Fabricate(:group, users: [user]) }
let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") }
let(:custom_field_json) { let(:pro_custom_field_json) { get_wizard_fixture("custom_field/pro_custom_fields") }
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json"
).read)
}
before do before do
custom_field_json['custom_fields'].each do |field_json| custom_field_json['custom_fields'].each do |field_json|
@ -32,6 +28,26 @@ describe "custom field extensions" do
expect(response.parsed_body["topic_field_1"]).to eq(true) expect(response.parsed_body["topic_field_1"]).to eq(true)
end end
it "adds post custom fields to the show post response" do
post.custom_fields["post_field_1"] = 7
post.save_custom_fields(true)
get "/posts/#{post.id}.json"
expect(response.status).to eq(200)
expect(response.parsed_body['post_field_1']).to eq(7)
end
context "with a pro subscription" do
before do
enable_pro
pro_custom_field_json['custom_fields'].each do |field_json|
custom_field = CustomWizard::CustomField.new(nil, field_json)
custom_field.save
end
end
it "adds category custom fields to the show categories response" do it "adds category custom fields to the show categories response" do
category.custom_fields["category_field_1"] = { a: 1, b: 2 } category.custom_fields["category_field_1"] = { a: 1, b: 2 }
category.save_custom_fields(true) category.save_custom_fields(true)
@ -53,16 +69,6 @@ describe "custom field extensions" do
expect(response.parsed_body['group']['group_field_1']).to eq("Group cf entry") expect(response.parsed_body['group']['group_field_1']).to eq("Group cf entry")
end end
it "adds post custom fields to the show post response" do
post.custom_fields["post_field_1"] = 7
post.save_custom_fields(true)
get "/posts/#{post.id}.json"
expect(response.status).to eq(200)
expect(response.parsed_body['post_field_1']).to eq(7)
end
context "preloaded" do context "preloaded" do
it "preloads category custom fields on site categories" do it "preloads category custom fields on site categories" do
Site.preloaded_category_custom_fields << "other_field" Site.preloaded_category_custom_fields << "other_field"
@ -91,4 +97,5 @@ describe "custom field extensions" do
expect(group['group_field_1']).to eq("Group cf entry") expect(group['group_field_1']).to eq("Group cf entry")
end end
end end
end
end end

Datei anzeigen

@ -2,55 +2,12 @@
require_relative '../../plugin_helper' require_relative '../../plugin_helper'
describe CustomWizard::StepsController do describe CustomWizard::StepsController do
fab!(:user) { fab!(:user) { Fabricate(:user, username: 'angus', email: "angus@email.com", trust_level: TrustLevel[3]) }
Fabricate( fab!(:user2) { Fabricate(:user, username: 'bob', email: "bob@email.com", trust_level: TrustLevel[2]) }
:user, let(:wizard_template) { get_wizard_fixture("wizard") }
username: 'angus', let(:wizard_field_condition_template) { get_wizard_fixture("condition/wizard_field_condition") }
email: "angus@email.com", let(:user_condition_template) { get_wizard_fixture("condition/user_condition") }
trust_level: TrustLevel[3] let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
)
}
fab!(:user2) {
Fabricate(
: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"
).read
)
}
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 before do
CustomWizard::Template.save(wizard_template, skip_jobs: true) CustomWizard::Template.save(wizard_template, skip_jobs: true)
@ -90,17 +47,6 @@ describe CustomWizard::StepsController do
put '/w/super-mega-fun-wizard/steps/step_10.json' put '/w/super-mega-fun-wizard/steps/step_10.json'
expect(response.status).to eq(400) expect(response.status).to eq(400)
end 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
@ -123,64 +69,40 @@ describe CustomWizard::StepsController do
expect(response.parsed_body['wizard']['start']).to eq("step_2") expect(response.parsed_body['wizard']['start']).to eq("step_2")
end 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 it "runs completion actions if user has completed wizard" do
new_template = wizard_template.dup new_template = wizard_template.dup
## route_to action ## route_to action
new_template['actions'].last['run_after'] = 'wizard_completion' 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) CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: { put '/w/super-mega-fun-wizard/steps/step_1.json'
fields: { put '/w/super-mega-fun-wizard/steps/step_2.json'
step_1_field_1: "Condition wont pass" put '/w/super-mega-fun-wizard/steps/step_3.json'
}
}
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.parsed_body['redirect_on_complete']).to eq("https://google.com") expect(response.parsed_body['redirect_on_complete']).to eq("https://google.com")
end end
it "saves results of completion actions if user has completed wizard" do it "saves results of completion actions if user has completed wizard" do
new_template = wizard_template.dup new_template = wizard_template.dup
## Create group action
new_template['actions'].first['run_after'] = 'wizard_completion' 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) CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: { put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: { fields: {
step_1_field_1: "My cool group" step_1_field_1: "Topic title",
step_1_field_2: "Topic post"
} }
} }
expect(response.status).to eq(200) put '/w/super-mega-fun-wizard/steps/step_2.json'
put '/w/super-mega-fun-wizard/steps/step_3.json' put '/w/super-mega-fun-wizard/steps/step_3.json'
expect(response.status).to eq(200)
wizard_id = response.parsed_body['wizard']['id'] wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user) wizard = CustomWizard::Wizard.create(wizard_id, user)
group_name = wizard.submissions.first.fields['action_9'] topic_id = wizard.submissions.first.fields[new_template['actions'].first['id']]
group = Group.find_by(name: group_name) topic = Topic.find(topic_id)
expect(topic.present?).to eq(true)
expect(group.present?).to eq(true)
expect(group.full_name).to eq("My cool group")
end end
it "returns a final step without conditions" do it "returns a final step without conditions" do
@ -197,6 +119,36 @@ describe CustomWizard::StepsController do
expect(response.parsed_body['final']).to eq(true) expect(response.parsed_body['final']).to eq(true)
end end
context "pro" do
before do
enable_pro
end
it "raises an error 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
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 "returns the correct final step when the conditional final step and last step are the same" do 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 = wizard_template.dup
new_template['steps'][0]['condition'] = user_condition_template['condition'] new_template['steps'][0]['condition'] = user_condition_template['condition']
@ -281,4 +233,5 @@ describe CustomWizard::StepsController do
submission = wizard.current_submission submission = wizard.current_submission
expect(submission.fields.keys).not_to include("step_2_field_1") expect(submission.fields.keys).not_to include("step_2_field_1")
end end
end
end end

Datei anzeigen

@ -2,29 +2,12 @@
require_relative '../../plugin_helper' require_relative '../../plugin_helper'
describe CustomWizard::WizardController do describe CustomWizard::WizardController do
fab!(:user) { fab!(:user) { Fabricate(:user, username: 'angus', email: "angus@email.com", trust_level: TrustLevel[3]) }
Fabricate( let(:wizard_template) { get_wizard_fixture("wizard") }
:user, let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
username: 'angus',
email: "angus@email.com",
trust_level: TrustLevel[3]
)
}
let(:permitted_json) {
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json"
).read
)
}
before do before do
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")
sign_in(user) sign_in(user)
end end

Datei anzeigen

@ -4,13 +4,10 @@ require_relative '../../plugin_helper'
describe CustomWizard::BasicWizardSerializer do describe CustomWizard::BasicWizardSerializer do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
let(:template) { get_wizard_fixture("wizard") }
it 'should return basic wizard attributes' do it 'should return basic wizard attributes' do
CustomWizard::Template.save( CustomWizard::Template.save(template, skip_jobs: true)
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read),
skip_jobs: true)
json = CustomWizard::BasicWizardSerializer.new( json = CustomWizard::BasicWizardSerializer.new(
CustomWizard::Builder.new("super_mega_fun_wizard", user).build, CustomWizard::Builder.new("super_mega_fun_wizard", user).build,
scope: Guardian.new(user) scope: Guardian.new(user)

Datei anzeigen

@ -4,12 +4,7 @@ require_relative '../../plugin_helper'
describe CustomWizard::CustomFieldSerializer do describe CustomWizard::CustomFieldSerializer do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") }
let(:custom_field_json) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json"
).read)
}
it 'should return custom field attributes' do it 'should return custom field attributes' do
custom_field_json['custom_fields'].each do |field_json| custom_field_json['custom_fields'].each do |field_json|

Datei anzeigen

@ -0,0 +1,17 @@
# frozen_string_literal: true
require_relative '../../../plugin_helper'
describe CustomWizard::ProAuthenticationSerializer do
fab!(:user) { Fabricate(:user) }
it 'should return pro authentication attributes' do
auth = CustomWizard::ProAuthentication.new(OpenStruct.new(key: '1234', auth_at: Time.now, auth_by: user.id))
serialized = described_class.new(auth, root: false).as_json
expect(serialized[:active]).to eq(true)
expect(serialized[:client_id]).to eq(auth.client_id)
expect(serialized[:auth_by]).to eq(auth.auth_by)
expect(serialized[:auth_at]).to eq(auth.auth_at)
end
end

Datei anzeigen

@ -0,0 +1,14 @@
# frozen_string_literal: true
require_relative '../../../plugin_helper'
describe CustomWizard::ProSubscriptionSerializer do
it 'should return pro subscription attributes' do
sub = CustomWizard::ProSubscription.new(OpenStruct.new(type: 'community', updated_at: Time.now))
serialized = described_class.new(sub, root: false).as_json
expect(serialized[:active]).to eq(true)
expect(serialized[:type]).to eq('community')
expect(serialized[:updated_at]).to eq(sub.updated_at)
end
end

Datei anzeigen

@ -0,0 +1,14 @@
# frozen_string_literal: true
require_relative '../../plugin_helper'
describe CustomWizard::ProSerializer do
it 'should return pro attributes' do
pro = CustomWizard::Pro.new
serialized = described_class.new(pro, root: false)
expect(serialized.server).to eq(pro.server)
expect(serialized.authentication.class).to eq(CustomWizard::ProAuthentication)
expect(serialized.subscription.class).to eq(CustomWizard::ProSubscription)
end
end

Datei anzeigen

@ -4,13 +4,10 @@ require_relative '../../plugin_helper'
describe CustomWizard::FieldSerializer do describe CustomWizard::FieldSerializer do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
let(:template) { get_wizard_fixture("wizard") }
before do before do
CustomWizard::Template.save( CustomWizard::Template.save(template, skip_jobs: true)
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 @wizard = CustomWizard::Builder.new("super_mega_fun_wizard", user).build
end end

Datei anzeigen

@ -5,19 +5,11 @@ require_relative '../../plugin_helper'
describe CustomWizard::WizardSerializer do describe CustomWizard::WizardSerializer do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
fab!(:category) { Fabricate(:category) } fab!(:category) { Fabricate(:category) }
let(:template) { get_wizard_fixture("wizard") }
let(:similar_topics_validation) { let(:similar_topics_validation) { get_wizard_fixture("field/validation/similar_topics") }
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/field/validation/similar_topics.json"
).read)
}
before do before do
CustomWizard::Template.save( CustomWizard::Template.save(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

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden Mehr anzeigen