1
0
Fork 0

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 discourseComputed, { observes } from "discourse-common/utils/decorators";
import { alias, equal, or } from "@ember/object/computed";
import { computed } from "@ember/object";
import I18n from "I18n";
const generateContent = function (array, type) {
return array.map((key) => ({
id: key,
name: I18n.t(`admin.wizard.custom_field.${type}.${key}`),
}));
const klasses = ["topic", "post", "group", "category"];
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,
name: I18n.t(`admin.wizard.custom_field.${type}.${key}`),
pro,
});
}
return result;
}, []);
};
export default Component.extend({
@ -16,14 +32,12 @@ export default Component.extend({
postSerializers: ["post"],
groupSerializers: ["basic_group"],
categorySerializers: ["basic_category"],
klassContent: generateContent(
["topic", "post", "group", "category"],
"klass"
),
typeContent: generateContent(
["string", "boolean", "integer", "json"],
"type"
),
klassContent: computed("proSubscribed", function () {
return generateContent(klasses, "klass", this.proSubscribed);
}),
typeContent: computed("proSubscribed", function () {
return generateContent(types, "type", this.proSubscribed);
}),
showInputs: or("field.new", "field.edit"),
classNames: ["custom-field-input"],
loading: or("saving", "destroying"),
@ -40,7 +54,7 @@ export default Component.extend({
const serializers = this.get(`${klass}Serializers`);
if (serializers) {
return generateContent(serializers, "serializers");
return generateContent(serializers, "serializers", this.proSubscribed);
} else {
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 { 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 { computed } from "@ember/object";
import wizardSchema from "../lib/wizard-schema";
import UndoChanges from "../mixins/undo-changes";
import Component from "@ember/component";
import I18n from "I18n";
@ -25,8 +25,6 @@ export default Component.extend(UndoChanges, {
createGroup: equal("action.type", "create_group"),
apiEmpty: empty("action.api"),
groupPropertyTypes: selectKitContent(["id", "name"]),
hasAdvanced: or("hasCustomFields", "routeTo"),
showAdvanced: and("hasAdvanced", "action.type"),
hasCustomFields: or(
"basicTopicFields",
"updateProfile",
@ -36,12 +34,6 @@ export default Component.extend(UndoChanges, {
basicTopicFields: or("createTopic", "sendMessage", "openComposer"),
publicTopicFields: or("createTopic", "openComposer"),
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) => {
return {
id: type,
@ -101,4 +93,19 @@ export default Component.extend(UndoChanges, {
}
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 { alias, equal, or } from "@ember/object/computed";
import { equal, or } from "@ember/object/computed";
import { computed } from "@ember/object";
import { selectKitContent } from "../lib/wizard";
import UndoChanges from "../mixins/undo-changes";
@ -27,7 +27,6 @@ export default Component.extend(UndoChanges, {
isTextType: or("isText", "isTextarea", "isComposer"),
isComposerPreview: equal("field.type", "composer_preview"),
categoryPropertyTypes: selectKitContent(["id", "slug"]),
showAdvanced: alias("field.type"),
messageUrl: "https://thepavilion.io/t/2809",
@discourseComputed("field.type")

Datei anzeigen

@ -6,6 +6,7 @@ import I18n from "I18n";
const icons = {
error: "times-circle",
success: "check-circle",
warn: "exclamation-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";
export default Component.extend({
classNames: ["realtime-validations"],
classNames: ["realtime-validations", "setting", "full", "pro"],
@discourseComputed
timeUnits() {
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
.save(opts)
.then((result) => {
this.send("afterSave", result.wizard_id);
if (result.wizard_id) {
this.send("afterSave", result.wizard_id);
} else if (result.errors) {
this.set("error", result.errors.join(", "));
}
})
.catch((result) => {
let errorType = "failed";
@ -114,10 +118,6 @@ export default Controller.extend({
controller.setup();
},
toggleAdvanced() {
this.toggleProperty("wizard.showAdvanced");
},
copyUrl() {
const $copyRange = $('<p id="copy-range"></p>');
$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", {
path: "/manager",
resetNamespace: true,
});
this.route("adminWizardsPro", {
path: "/pro",
resetNamespace: true,
});
}
);
},

Datei anzeigen

@ -97,11 +97,6 @@ function buildObjectArray(json, type) {
if (present(json)) {
json.forEach((objJson, objectIndex) => {
let object = buildObject(objJson, type, objectIndex);
if (hasAdvancedProperties(object, type)) {
object.set("showAdvanced", true);
}
array.pushObject(object);
});
}
@ -112,21 +107,11 @@ function buildObjectArray(json, type) {
function buildBasicProperties(json, type, props, objectIndex = null) {
listProperties(type).forEach((p) => {
props[p] = buildProperty(json, p, type, objectIndex);
if (hasAdvancedProperties(json, type)) {
props.showAdvanced = true;
}
});
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
function actionPatch(json) {
let actions = json.actions || [];

Datei anzeigen

@ -18,7 +18,6 @@ const wizard = {
permitted: null,
},
mapped: ["permitted"],
advanced: ["restart_on_revisit"],
required: ["id"],
dependent: {
after_time: "after_time_scheduled",
@ -50,7 +49,6 @@ const step = {
force_final: false,
},
mapped: ["required_data", "permitted_params", "condition", "index"],
advanced: ["required_data", "permitted_params", "condition", "index"],
required: ["id"],
dependent: {},
objectArrays: {
@ -68,6 +66,7 @@ const field = {
label: null,
image: null,
description: null,
property: null,
required: null,
key: null,
type: null,
@ -75,7 +74,6 @@ const field = {
},
types: {},
mapped: ["prefill", "content", "condition", "index"],
advanced: ["property", "key", "condition", "index"],
required: ["id", "type"],
dependent: {},
objectArrays: {},
@ -196,14 +194,14 @@ const action = {
"visibility_level",
"members_visibility_level",
],
advanced: [
"code",
"custom_fields",
"skip_redirect",
"suppress_notifications",
"required",
],
required: ["id", "type"],
proTypes: [
"send_message",
"add_to_group",
"create_category",
"create_group",
"send_to_api",
],
dependent: {},
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) {
const customFields = A(model || []);
controller.set("customFields", customFields);
const customFields = A(model.custom_fields || []);
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],
currentAction: wizard.actions[0],
creating: model.create,
proSubscribed: parentModel.pro_subscribed,
};
controller.setProperties(props);

Datei anzeigen

@ -32,7 +32,8 @@
{{custom-field-input
field=field
removeField=(action "removeField")
saveField=(action "saveField")}}
saveField=(action "saveField")
proSubscribed=proSubscribed}}
{{/each}}
</tbody>
</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,33 +126,25 @@
</div>
</div>
{{wizard-advanced-toggle showAdvanced=wizard.showAdvanced}}
{{#if wizard.showAdvanced}}
<div class="advanced-settings">
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.save_submissions"}}</label>
</div>
<div class="setting-value">
{{input type="checkbox" checked=wizard.save_submissions}}
<span>{{i18n "admin.wizard.save_submissions_label"}}</span>
</div>
</div>
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.restart_on_revisit"}}</label>
</div>
<div class="setting-value">
{{input type="checkbox" checked=wizard.restart_on_revisit}}
<span>{{i18n "admin.wizard.restart_on_revisit_label"}}</span>
</div>
</div>
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.save_submissions"}}</label>
</div>
{{/if}}
<div class="setting-value">
{{input type="checkbox" checked=wizard.save_submissions}}
<span>{{i18n "admin.wizard.save_submissions_label"}}</span>
</div>
</div>
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.restart_on_revisit"}}</label>
</div>
<div class="setting-value">
{{input type="checkbox" checked=wizard.restart_on_revisit}}
<span>{{i18n "admin.wizard.restart_on_revisit_label"}}</span>
</div>
</div>
</div>
{{wizard-links
@ -166,7 +158,8 @@
wizard=wizard
currentField=currentField
wizardFields=wizardFields
fieldTypes=fieldTypes}}
fieldTypes=fieldTypes
proSubscribed=proSubscribed}}
{{/if}}
{{wizard-links
@ -182,7 +175,8 @@
wizard=wizard
apis=apis
removeAction="removeAction"
wizardFields=wizardFields}}
wizardFields=wizardFields
proSubscribed=proSubscribed}}
{{/each}}
<div class="admin-wizard-buttons">

Datei anzeigen

@ -7,6 +7,7 @@
{{/if}}
{{nav-item route="adminWizardsLogs" label="admin.wizard.log.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">
<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}}
<td>
{{combo-box
{{wizard-pro-selector
value=field.klass
content=klassContent
none="admin.wizard.custom_field.klass.select"
onChange=(action (mut field.klass))}}
</td>
<td>
{{combo-box
{{wizard-pro-selector
value=field.type
content=typeContent
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 class="setting-value">
{{combo-box
{{wizard-pro-selector
value=action.type
content=actionTypes
onChange=(action "changeType")
options=(hash
none="admin.wizard.select_type"
)}}
)
}}
</div>
</div>
@ -714,99 +715,90 @@
</div>
{{/if}}
{{#if showAdvanced}}
{{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-label">
<label>{{i18n "admin.wizard.action.custom_fields.label"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=action.custom_fields
property="custom_fields"
onUpdate=(action "mappedFieldUpdated")
options=(hash
inputTypes="association"
customFieldSelection="key"
wizardFieldSelection="value"
wizardActionSelection="value"
userFieldSelection="value"
keyPlaceholder="admin.wizard.action.custom_fields.key"
context=customFieldsContext
)}}
</div>
</div>
{{/if}}
{{#if sendMessage}}
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.required"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=action.required
property="required"
onUpdate=(action "mappedFieldUpdated")
options=(hash
textSelection="value"
wizardFieldSelection=true
userFieldSelection=true
groupSelection=true
context="action"
)}}
</div>
</div>
{{/if}}
{{#if showPostAdvanced}}
<div class="setting full">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.skip_redirect.label"}}</label>
</div>
<div class="setting-value">
{{input type="checkbox" checked=action.skip_redirect}}
<span>
{{i18n "admin.wizard.action.skip_redirect.description" type="topic"}}
</span>
</div>
</div>
<div class="setting full">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.suppress_notifications.label"}}</label>
</div>
<div class="setting-value">
{{input type="checkbox" checked=action.suppress_notifications}}
<span>
{{i18n "admin.wizard.action.suppress_notifications.description" type="topic"}}
</span>
</div>
</div>
{{/if}}
{{#if routeTo}}
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.route_to.code"}}</label>
</div>
<div class="setting-value">
{{input value=action.code}}
</div>
</div>
{{/if}}
{{#if hasCustomFields}}
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.custom_fields.label"}}</label>
</div>
{{/if}}
<div class="setting-value">
{{wizard-mapper
inputs=action.custom_fields
property="custom_fields"
onUpdate=(action "mappedFieldUpdated")
options=(hash
inputTypes="association"
customFieldSelection="key"
wizardFieldSelection="value"
wizardActionSelection="value"
userFieldSelection="value"
keyPlaceholder="admin.wizard.action.custom_fields.key"
context=customFieldsContext
)}}
</div>
</div>
{{/if}}
{{#if sendMessage}}
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.required"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=action.required
property="required"
onUpdate=(action "mappedFieldUpdated")
options=(hash
textSelection="value"
wizardFieldSelection=true
userFieldSelection=true
groupSelection=true
context="action"
)}}
</div>
</div>
{{/if}}
{{#if showPostAdvanced}}
<div class="setting full">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.skip_redirect.label"}}</label>
</div>
<div class="setting-value">
{{input type="checkbox" checked=action.skip_redirect}}
<span>
{{i18n "admin.wizard.action.skip_redirect.description" type="topic"}}
</span>
</div>
</div>
<div class="setting full">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.suppress_notifications.label"}}</label>
</div>
<div class="setting-value">
{{input type="checkbox" checked=action.suppress_notifications}}
<span>
{{i18n "admin.wizard.action.suppress_notifications.description" type="topic"}}
</span>
</div>
</div>
{{/if}}
{{#if routeTo}}
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.route_to.code"}}</label>
</div>
<div class="setting-value">
{{input value=action.code}}
</div>
</div>
{{/if}}

Datei anzeigen

@ -207,70 +207,66 @@
</div>
{{/if}}
{{#if showAdvanced}}
{{wizard-advanced-toggle showAdvanced=field.showAdvanced}}
{{#if proSubscribed}}
<div class="setting full field-mapper-setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.condition"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
{{#if field.showAdvanced}}
<div class="advanced-settings">
<div class="setting-value">
{{wizard-mapper
inputs=field.condition
options=fieldConditionOptions}}
</div>
</div>
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.condition"}}</label>
</div>
<div class="setting full field-mapper-setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.index"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=field.condition
options=fieldConditionOptions}}
</div>
<div class="setting-value">
{{wizard-mapper
inputs=field.index
options=fieldIndexOptions}}
</div>
</div>
{{#if isCategory}}
<div class="setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.field.property"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.index"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=field.index
options=fieldIndexOptions}}
</div>
<div class="setting-value">
{{combo-box
value=field.property
content=categoryPropertyTypes
onChange=(action (mut field.property))
options=(hash
none="admin.wizard.selector.placeholder.property"
)}}
</div>
{{#if isCategory}}
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.field.property"}}</label>
</div>
<div class="setting-value">
{{combo-box
value=field.property
content=categoryPropertyTypes
onChange=(action (mut field.property))
options=(hash
none="admin.wizard.selector.placeholder.property"
)}}
</div>
</div>
{{/if}}
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.translation"}}</label>
</div>
<div class="setting-value medium">
{{input
name="key"
value=field.key
class="medium"
placeholderKey="admin.wizard.translation_placeholder"}}
</div>
</div>
{{#if validations}}
{{wizard-realtime-validations field=field validations=validations}}
{{/if}}
</div>
{{/if}}
<div class="setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.translation"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting-value">
{{input
name="key"
value=field.key
placeholderKey="admin.wizard.translation_placeholder"}}
</div>
</div>
{{#if validations}}
{{wizard-realtime-validations field=field validations=validations}}
{{/if}}
{{/if}}

Datei anzeigen

@ -33,85 +33,84 @@
</div>
</div>
{{wizard-advanced-toggle showAdvanced=step.showAdvanced}}
{{#if step.showAdvanced}}
<div class="advanced-settings">
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.condition"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=step.condition
options=stepConditionOptions}}
</div>
{{#if proSubscribed}}
<div class="setting full field-mapper-setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.condition"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting full">
<div class="setting-label"></div>
<div class="setting-value force-final">
<h4>{{i18n "admin.wizard.step.force_final.label"}}</h4>
{{input type="checkbox" checked=step.force_final}}
<span>{{i18n "admin.wizard.step.force_final.description"}}</span>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=step.condition
options=stepConditionOptions}}
</div>
</div>
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.step.required_data.label"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=step.required_data
options=(hash
inputTypes="validation"
inputConnector="and"
wizardFieldSelection="value"
userFieldSelection="value"
keyPlaceholder="admin.wizard.submission_key"
context="step"
)}}
{{#if step.required_data}}
<div class="required-data-message">
<div class="label">
{{i18n "admin.wizard.step.required_data.not_permitted_message"}}
</div>
{{input value=step.required_data_message}}
<div class="setting full">
<div class="setting-label"></div>
<div class="setting-value force-final">
<h4>{{i18n "admin.wizard.step.force_final.label"}}</h4>
{{input type="checkbox" checked=step.force_final}}
<span>{{i18n "admin.wizard.step.force_final.description"}}</span>
</div>
</div>
<div class="setting full field-mapper-setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.step.required_data.label"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=step.required_data
options=(hash
inputTypes="validation"
inputConnector="and"
wizardFieldSelection="value"
userFieldSelection="value"
keyPlaceholder="admin.wizard.submission_key"
context="step"
)}}
{{#if step.required_data}}
<div class="required-data-message">
<div class="label">
{{i18n "admin.wizard.step.required_data.not_permitted_message"}}
</div>
{{/if}}
</div>
{{input value=step.required_data_message}}
</div>
{{/if}}
</div>
</div>
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.step.permitted_params.label"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=step.permitted_params
options=(hash
pairConnector="set"
inputTypes="association"
keyPlaceholder="admin.wizard.param_key"
valuePlaceholder="admin.wizard.submission_key"
context="step"
)}}
</div>
<div class="setting full field-mapper-setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.step.permitted_params.label"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=step.permitted_params
options=(hash
pairConnector="set"
inputTypes="association"
keyPlaceholder="admin.wizard.param_key"
valuePlaceholder="admin.wizard.submission_key"
context="step"
)}}
</div>
</div>
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.translation"}}</label>
</div>
<div class="setting-value">
{{input
name="key"
value=step.key
placeholderKey="admin.wizard.translation_placeholder"}}
</div>
<div class="setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.translation"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting-value">
{{input
name="key"
value=step.key
placeholderKey="admin.wizard.translation_placeholder"}}
</div>
</div>
{{/if}}
@ -129,5 +128,6 @@
currentFieldId=currentField.id
fieldTypes=fieldTypes
removeField="removeField"
wizardFields=wizardFields}}
wizardFields=wizardFields
proSubscribed=proSubscribed}}
{{/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,50 +1,54 @@
<h3>{{i18n "admin.wizard.field.validations.header"}}</h3>
<ul>
{{#each-in field.validations as |type props|}}
<li>
<span class="setting-title">
<h4>{{i18n (concat "admin.wizard.field.validations." type)}}</h4>
{{input type="checkbox" checked=props.status}}
{{i18n "admin.wizard.field.validations.enabled"}}
</span>
<div class="validation-container">
<div class="validation-section">
<div class="setting-label">
<label>{{i18n "admin.wizard.field.validations.categories"}}</label>
<div class="setting-label">
<label>{{i18n "admin.wizard.field.validations.header"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting-value full">
<ul>
{{#each-in field.validations as |type props|}}
<li>
<span class="setting-title">
<h4>{{i18n (concat "admin.wizard.field.validations." type)}}</h4>
{{input type="checkbox" checked=props.status}}
{{i18n "admin.wizard.field.validations.enabled"}}
</span>
<div class="validation-container">
<div class="validation-section">
<div class="setting-label">
<label>{{i18n "admin.wizard.field.validations.categories"}}</label>
</div>
<div class="setting-value">
{{category-selector
categories=(get this (concat "validationBuffer." type ".categories"))
onChange=(action "updateValidationCategories" type props)
class="wizard"}}
</div>
</div>
<div class="setting-value">
{{category-selector
categories=(get this (concat "validationBuffer." type ".categories"))
onChange=(action "updateValidationCategories" type props)
class="wizard"}}
<div class="validation-section">
<div class="setting-label">
<label>{{i18n "admin.wizard.field.validations.max_topic_age"}}</label>
</div>
<div class="setting-value">
{{input type="number" class="time-n-value" value=props.time_n_value}}
{{combo-box
value=(readonly props.time_unit)
content=timeUnits
class="time-unit-selector"
onChange=(action (mut props.time_unit))}}
</div>
</div>
<div class="validation-section">
<div class="setting-label">
<label>{{i18n "admin.wizard.field.validations.position"}}</label>
</div>
<div class="setting-value">
{{radio-button name=(concat type field.id) value="above" selection=props.position}}
<span>{{i18n "admin.wizard.field.validations.above"}}</span>
{{radio-button name=(concat type field.id) value="below" selection=props.position}}
<span>{{i18n "admin.wizard.field.validations.below"}}</span>
</div>
</div>
</div>
<div class="validation-section">
<div class="setting-label">
<label>{{i18n "admin.wizard.field.validations.max_topic_age"}}</label>
</div>
<div class="setting-value">
{{input type="number" class="time-n-value" value=props.time_n_value}}
{{combo-box
value=(readonly props.time_unit)
content=timeUnits
class="time-unit-selector"
onChange=(action (mut props.time_unit))}}
</div>
</div>
<div class="validation-section">
<div class="setting-label">
<label>{{i18n "admin.wizard.field.validations.position"}}</label>
</div>
<div class="setting-value">
{{radio-button name=(concat type field.id) value="above" selection=props.position}}
{{i18n "admin.wizard.field.validations.above"}}
{{radio-button name=(concat type field.id) value="below" selection=props.position}}
{{i18n "admin.wizard.field.validations.below"}}
</div>
</div>
</div>
</li>
{{/each-in}}
</ul>
</li>
{{/each-in}}
</ul>
</div>

Datei anzeigen

@ -8,7 +8,7 @@
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
margin-bottom: 10px;
& + .wizard-message + div {
margin-top: 20px;
@ -294,7 +294,7 @@
font-size: 0.85em;
}
span {
> span {
font-size: 0.929em;
}
@ -423,6 +423,17 @@
.setting-gutter {
margin-top: 5px;
}
&.pro {
.setting-label {
display: flex;
flex-direction: column;
label {
margin: 0;
}
}
}
}
.advanced-settings {
@ -729,27 +740,59 @@
}
}
.realtime-validations > ul {
.admin-wizard-container.settings .realtime-validations .setting-value > ul {
list-style: none;
margin: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
> li {
background-color: var(--primary-low);
padding: 1em;
margin: 0 0 1em 0;
input {
margin-bottom: 0;
.setting-title {
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 {
display: flex;
flex-direction: column;
padding: 1em 0;
.validation-section {
width: 250px;
min-width: 250px;
margin: 0.5em 0;
}
}
@ -767,6 +810,82 @@
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 {
background: var(--pavilion-primary);
color: var(--pavilion-secondary);

Datei anzeigen

@ -58,6 +58,7 @@ en:
select_type: "Select a type"
condition: "Condition"
index: "Index"
pro_support_button:
title: "Request Pro Support"
label: "Pro Support"
@ -89,11 +90,19 @@ en:
no_file: Please choose a file to import
file_size_error: The file size must be 512kb or less
file_format_error: The file must be a .json file
server_error: "Error: {{message}}"
importing: Importing wizards...
destroying: Destroying wizards...
import_complete: Import 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:
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."
@ -102,7 +111,6 @@ en:
viewing: "View recent logs for wizards on the forum"
documentation: "Check out the logs documentation"
editor:
show: "Show"
hide: "Hide"
@ -182,9 +190,9 @@ en:
min_length_placeholder: "Minimum length in characters"
max_length: "Max Length"
max_length_placeholder: "Maximum length in characters"
char_counter: "Character Counter"
char_counter: "Counter"
char_counter_placeholder: "Display Character Counter"
field_placeholder: "Field Placeholder"
field_placeholder: "Placeholder"
file_types: "File Types"
preview_template: "Preview Template"
limit: "Limit"
@ -195,7 +203,7 @@ en:
label: "Format"
instructions: "<a href='https://momentjs.com/docs/#/displaying/format/' target='_blank'>Moment.js format</a>"
validations:
header: "Realtime Validations"
header: "Validations"
enabled: "Enabled"
similar_topics: "Similar Topics"
position: "Position"
@ -443,6 +451,24 @@ en:
destroy: Destroy
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:
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_already_taken: "'%{name}' is already taken as a custom field name"
save_default: "Failed to save custom field '%{name}'"
pro_type: "%{type} custom fields require PRO Subscription"
field:
too_short: "%{label} must be at least %{min} characters"
@ -49,6 +50,7 @@ en:
required: "%{property} is required"
conflict: "Wizard with id '%{wizard_id}' already exists"
after_time: "After time setting is invalid"
pro: "%{type} %{property} is PRO only"
site_settings:
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'
post 'admin/wizards/manager/import' => 'admin_manager#import'
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

Datei anzeigen

@ -1,7 +1,10 @@
# frozen_string_literal: true
class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController
def index
render_json_dump(custom_field_list)
render_json_dump(
custom_fields: custom_field_list,
pro_subscribed: CustomWizard::Pro.subscribed?
)
end
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,
realtime_validations: CustomWizard::RealtimeValidation.types,
custom_fields: custom_field_list
custom_fields: custom_field_list,
pro_subscribed: CustomWizard::Pro.subscribed?
)
end

Datei anzeigen

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

Datei anzeigen

@ -1,5 +1,5 @@
{
"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('; ')
)
end
def pro_actions
%w[send_message watch_categories send_to_api create_group create_category]
end
end

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -47,6 +47,7 @@ class CustomWizard::Mapper
@data = params[:data] || {}
@user = params[:user]
@opts = params[:opts] || {}
@pro = CustomWizard::Pro.new
end
def perform
@ -251,7 +252,7 @@ class CustomWizard::Mapper
end
end
if opts[:template]
if opts[:template] && @pro.subscribed?
template = Liquid::Template.parse(string)
string = template.render(data)
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 = {})
@data = data
@opts = opts
@pro = CustomWizard::Pro.new
end
def perform
@ -14,12 +15,15 @@ class CustomWizard::TemplateValidator
check_id(data, :wizard)
check_required(data, :wizard)
validate_after_time
validate_pro(data, :wizard)
data[:steps].each do |step|
check_required(step, :step)
validate_pro(step, :step)
if data[:fields].present?
data[:fields].each do |field|
if step[:fields].present?
step[:fields].each do |field|
validate_pro(field, :field)
check_required(field, :field)
end
end
@ -27,6 +31,7 @@ class CustomWizard::TemplateValidator
if data[:actions].present?
data[:actions].each do |action|
validate_pro(action, :action)
check_required(action, :action)
end
end
@ -47,16 +52,53 @@ class CustomWizard::TemplateValidator
}
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
def check_required(object, type)
CustomWizard::TemplateValidator.required[type].each do |property|
self.class.required[type].each do |property|
if object[property].blank?
errors.add :base, I18n.t("wizard.validation.required", property: property)
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)
if type === :wizard && @opts[:create] && CustomWizard::Template.exists?(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
def self.list(user, template_opts: {}, not_completed: false)
def self.list(user, template_opts = {}, not_completed = false)
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)
result.push(wizard) if wizard.can_access? && (
!not_completed || !wizard.completed?
@ -325,7 +325,7 @@ class CustomWizard::Wizard
def self.after_signup(user)
wizards = list(
user,
template_opts: {
{
setting: 'after_signup',
order: "(value::json ->> 'permitted') IS NOT NULL DESC"
}
@ -336,11 +336,11 @@ class CustomWizard::Wizard
def self.prompt_completion(user)
wizards = list(
user,
template_opts: {
{
setting: 'prompt_completion',
order: "(value::json ->> 'permitted') IS NOT NULL DESC"
},
not_completed: true
true
)
if wizards.any?
wizards.map do |w|

Datei anzeigen

@ -71,11 +71,13 @@ after_initialize do
../controllers/custom_wizard/admin/logs.rb
../controllers/custom_wizard/admin/manager.rb
../controllers/custom_wizard/admin/custom_fields.rb
../controllers/custom_wizard/admin/pro.rb
../controllers/custom_wizard/wizard.rb
../controllers/custom_wizard/steps.rb
../controllers/custom_wizard/realtime_validations.rb
../jobs/refresh_api_access_token.rb
../jobs/set_after_time_wizard.rb
../jobs/regular/refresh_api_access_token.rb
../jobs/regular/set_after_time_wizard.rb
../jobs/scheduled/update_pro_subscription.rb
../lib/custom_wizard/validators/template.rb
../lib/custom_wizard/validators/update.rb
../lib/custom_wizard/action_result.rb
@ -94,6 +96,9 @@ after_initialize do
../lib/custom_wizard/submission.rb
../lib/custom_wizard/template.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/authorization.rb
../lib/custom_wizard/api/endpoint.rb
@ -114,6 +119,9 @@ after_initialize do
../serializers/custom_wizard/log_serializer.rb
../serializers/custom_wizard/submission_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/invites_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!(:group) { Fabricate(:group) }
let(:wizard_template) {
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read
)
}
let(:wizard_template) { get_wizard_fixture("wizard") }
let(:open_composer) { get_wizard_fixture("actions/open_composer") }
let(:create_category) { get_wizard_fixture("actions/create_category") }
let(:create_group) { get_wizard_fixture("actions/create_group") }
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) {
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/actions/open_composer.json"
).read
)
}
def update_template(template)
CustomWizard::Template.save(template, skip_jobs: true)
@template = CustomWizard::Template.find('super_mega_fun_wizard')
end
before do
Group.refresh_automatic_group!(:trust_level_2)
CustomWizard::Template.save(wizard_template, skip_jobs: true)
@template = CustomWizard::Template.find('super_mega_fun_wizard')
update_template(wizard_template)
end
context 'creating a topic' do
@ -110,54 +106,6 @@ describe CustomWizard::Action do
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
wizard = CustomWizard::Builder.new(@template[:id], user).build
upload = Upload.create!(
@ -182,10 +130,8 @@ describe CustomWizard::Action do
updater = wizard.create_updater(wizard.steps[1].id, {})
updater.update
category = Category.find_by(id: wizard.current_submission.fields['action_8'])
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
@ -210,35 +156,11 @@ describe CustomWizard::Action do
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
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(CategoryUser.where(
category_id: wizard.current_submission.fields['action_8'],
user_id: user.id
).first.notification_level).to eq(2)
expect(CategoryUser.where(
category_id: category.id,
user_id: user.id
@ -251,4 +173,97 @@ describe CustomWizard::Action do
updater.update
expect(updater.result[:redirect_on_next]).to eq("https://google.com")
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

Datei anzeigen

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

Datei anzeigen

@ -3,12 +3,8 @@
require_relative '../../plugin_helper'
describe CustomWizard::CustomField do
let(:custom_field_json) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json"
).read)
}
let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") }
let(:custom_field_pro_json) { get_wizard_fixture("custom_field/pro_custom_fields") }
before do
CustomWizard::CustomField.invalidate_cache
@ -104,8 +100,8 @@ describe CustomWizard::CustomField do
it "does not save with an unsupported serializer" do
invalid_field_json = custom_field_json['custom_fields'].first
invalid_field_json['klass'] = 'category'
invalid_field_json['serializers'] = ['category', 'site_category']
invalid_field_json['klass'] = 'post'
invalid_field_json['serializers'] = ['post', 'post_revision']
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.errors.full_messages.first).to eq(
I18n.t("wizard.custom_field.error.unsupported_serializers",
class: "category",
serializers: "category, site_category"
class: "post",
serializers: "post_revision"
)
)
expect(
@ -196,6 +192,50 @@ describe CustomWizard::CustomField do
).exists?
).to eq(false)
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
context "lists" do
@ -205,15 +245,15 @@ describe CustomWizard::CustomField do
end
end
it "lists saved custom field records" do
expect(CustomWizard::CustomField.list.length).to eq(4)
it "saved custom field records" do
expect(CustomWizard::CustomField.list.length).to eq(2)
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)
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['serializers'] = nil
@ -221,12 +261,12 @@ describe CustomWizard::CustomField do
expect(CustomWizard::CustomField.list_by(:serializers, ['post']).length).to eq(0)
end
it "lists custom field records added by other plugins " do
expect(CustomWizard::CustomField.external_list.length).to eq(11)
it "custom field records added by other plugins " do
expect(CustomWizard::CustomField.external_list.length).to be > 10
end
it "lists all custom field records" do
expect(CustomWizard::CustomField.full_list.length).to eq(15)
it "all custom field records" do
expect(CustomWizard::CustomField.full_list.length).to be > 12
end
end

Datei anzeigen

@ -2,11 +2,7 @@
require_relative '../../plugin_helper'
describe CustomWizard::Field do
let(:field_hash) do
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/field/field.json"
).read).with_indifferent_access
end
let(:field_hash) { get_wizard_fixture("field/field") }
before do
CustomWizard::Field.register(

Datei anzeigen

@ -31,16 +31,8 @@ describe CustomWizard::Mapper do
]
)
}
let(:inputs) {
JSON.parse(File.open(
"#{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(:inputs) { get_wizard_fixture("mapper/inputs") }
let(:data) { get_wizard_fixture("mapper/data") }
let(:template_params) {
{
"step_1_field_1" => "Hello"
@ -352,7 +344,7 @@ describe CustomWizard::Mapper do
expect(result).to eq(template_params["step_1_field_1"])
end
it "treats replaced values as string literals" do
it "requires a pro subscription" do
template = '{{ "w{step_1_field_1}" | size }}'
mapper = create_template_mapper(template_params, user1)
result = mapper.interpolate(
@ -362,60 +354,17 @@ describe CustomWizard::Mapper do
wizard: true,
value: true
)
expect(result).to eq(template_params["step_1_field_1"].size.to_s)
expect(result).to eq("{{ \"#{template_params["step_1_field_1"]}\" | size }}")
end
it "allows the wizard values to be used inside conditionals" do
template = <<-LIQUID
{%- if "w{step_1_field_1}" contains "ello" -%}
Correct
{%- else -%}
Incorrect
{%-endif-%}
LIQUID
mapper = create_template_mapper(template_params, user1)
result = mapper.interpolate(
template.dup,
template: true,
user: true,
wizard: true,
value: true
)
expect(result).to eq("Correct")
end
context "with a pro subscription" do
before do
enable_pro
end
it "can access data passed to render method as variable" do
template = "{{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.to_s)
end
it "doesn't parse the template when template param is false" do
template = <<-LIQUID.strip
{{ "w{step_1_field_1}" | size}}
LIQUID
mapper = create_template_mapper(template_params, user1)
result = mapper.interpolate(
template.dup,
template: false,
)
expect(result).to eq(template)
end
context "custom filter: 'first_non_empty'" do
it "gives first non empty element from list" do
template = <<-LIQUID.strip
{%- assign entry = "" | first_non_empty: step_1_field_1, step_1_field_2, step_1_field_3 -%}
{{ entry }}
LIQUID
mapper = create_template_mapper(template_params_non_empty, user1)
it "treats replaced values as string literals" do
template = '{{ "w{step_1_field_1}" | size }}'
mapper = create_template_mapper(template_params, user1)
result = mapper.interpolate(
template.dup,
template: true,
@ -423,15 +372,18 @@ describe CustomWizard::Mapper do
wizard: true,
value: true
)
expect(result).to eq(template_params_non_empty["step_1_field_3"])
expect(result).to eq(template_params["step_1_field_1"].size.to_s)
end
it "gives first non empty element from list when multiple non empty values present" do
template = <<-LIQUID.strip
{%- assign entry = "" | first_non_empty: step_1_field_1, step_1_field_2, step_1_field_3 -%}
{{ entry }}
it "allows the wizard values to be used inside conditionals" do
template = <<-LIQUID
{%- if "w{step_1_field_1}" contains "ello" -%}
Correct
{%- else -%}
Incorrect
{%-endif-%}
LIQUID
mapper = create_template_mapper(template_params_multiple_non_empty, user1)
mapper = create_template_mapper(template_params, user1)
result = mapper.interpolate(
template.dup,
template: true,
@ -439,25 +391,84 @@ describe CustomWizard::Mapper do
wizard: true,
value: true
)
expect(result).to eq(template_params_multiple_non_empty["step_1_field_2"])
expect(result).to eq("Correct")
end
it "gives empty if all elements are empty" do
it "can access data passed to render method as variable" do
template = "{{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.to_s)
end
it "doesn't parse the template when template param is false" do
template = <<-LIQUID.strip
{%- assign entry = "" | first_non_empty: step_1_field_1, step_1_field_2, step_1_field_3 -%}
{%- if entry -%}
{{ "w{step_1_field_1}" | size}}
LIQUID
mapper = create_template_mapper(template_params, user1)
result = mapper.interpolate(
template.dup,
template: false,
)
expect(result).to eq(template)
end
context "custom filter: 'first_non_empty'" do
it "gives first non empty element from list" do
template = <<-LIQUID.strip
{%- assign entry = "" | first_non_empty: step_1_field_1, step_1_field_2, step_1_field_3 -%}
{{ entry }}
{%- endif -%}
LIQUID
mapper = create_template_mapper(template_params_empty, user1)
result = mapper.interpolate(
template.dup,
template: true,
user: true,
wizard: true,
value: true
)
expect(result).to eq("")
LIQUID
mapper = create_template_mapper(template_params_non_empty, user1)
result = mapper.interpolate(
template.dup,
template: true,
user: true,
wizard: true,
value: true
)
expect(result).to eq(template_params_non_empty["step_1_field_3"])
end
it "gives first non empty element from list when multiple non empty values present" do
template = <<-LIQUID.strip
{%- assign entry = "" | first_non_empty: step_1_field_1, step_1_field_2, step_1_field_3 -%}
{{ entry }}
LIQUID
mapper = create_template_mapper(template_params_multiple_non_empty, user1)
result = mapper.interpolate(
template.dup,
template: true,
user: true,
wizard: true,
value: true
)
expect(result).to eq(template_params_multiple_non_empty["step_1_field_2"])
end
it "gives empty if all elements are empty" do
template = <<-LIQUID.strip
{%- assign entry = "" | first_non_empty: step_1_field_1, step_1_field_2, step_1_field_3 -%}
{%- if entry -%}
{{ entry }}
{%- endif -%}
LIQUID
mapper = create_template_mapper(template_params_empty, user1)
result = mapper.interpolate(
template.dup,
template: true,
user: true,
wizard: true,
value: true
)
expect(result).to eq("")
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'
describe CustomWizard::Step do
let(:step_hash) do
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/step.json"
).read
).with_indifferent_access
end
let(:field_hash) do
JSON.parse(
File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/field/field.json"
).read
).with_indifferent_access
end
let(:step_hash) { get_wizard_fixture("step/step") }
let(:field_hash) { get_wizard_fixture("field/field") }
before do
@step = CustomWizard::Step.new(step_hash[:id])

Datei anzeigen

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

Datei anzeigen

@ -3,17 +3,8 @@ require_relative '../../plugin_helper'
describe CustomWizard::Template do
fab!(:user) { Fabricate(:user) }
let(:template_json) {
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)
}
let(:template_json) { get_wizard_fixture("wizard") }
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
before do
CustomWizard::Template.save(template_json, skip_jobs: true)

Datei anzeigen

@ -3,12 +3,9 @@ require_relative '../../plugin_helper'
describe CustomWizard::TemplateValidator do
fab!(:user) { Fabricate(:user) }
let(:template) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read).with_indifferent_access
}
let(:template) { get_wizard_fixture("wizard") }
let(:create_category) { get_wizard_fixture("actions/create_category") }
let(:user_condition) { get_wizard_fixture("condition/user_condition") }
it "validates valid templates" do
expect(
@ -45,4 +42,52 @@ describe CustomWizard::TemplateValidator do
CustomWizard::TemplateValidator.new(template).perform
).to eq(false)
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

Datei anzeigen

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

Datei anzeigen

@ -6,22 +6,8 @@ describe CustomWizard::Wizard do
fab!(:user) { Fabricate(:user) }
fab!(:trusted_user) { Fabricate(:user, trust_level: TrustLevel[3]) }
fab!(:admin_user) { Fabricate(:user, admin: true) }
let(:template_json) {
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
)
}
let(:template_json) { get_wizard_fixture("wizard") }
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
before do
Group.refresh_automatic_group!(:trust_level_3)

Datei anzeigen

@ -9,11 +9,8 @@ describe "custom field extensions" do
fab!(:group) { Fabricate(:group) }
fab!(:user) { Fabricate(:user) }
let(:custom_field_json) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json"
).read)
}
let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") }
let(:pro_custom_field_json) { get_wizard_fixture("custom_field/pro_custom_fields") }
before do
custom_field_json['custom_fields'].each do |field_json|
@ -75,43 +72,54 @@ describe "custom field extensions" do
end
end
context "category" do
it "registers category custom fields" do
category
expect(Category.get_custom_field_type("category_field_1")).to eq(:json)
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
it "adds category custom fields to the basic category serializer" do
category.custom_fields["category_field_1"] = { a: 1, b: 2 }.to_json
category.save_custom_fields(true)
context "category" do
it "registers" do
category
expect(Category.get_custom_field_type("category_field_1")).to eq(:json)
end
serializer = BasicCategorySerializer.new(
category,
scope: Guardian.new(user),
root: false
).as_json
it "adds custom fields to the basic category serializer" do
category.custom_fields["category_field_1"] = { a: 1, b: 2 }.to_json
category.save_custom_fields(true)
expect(serializer[:category_field_1]).to eq({ a: 1, b: 2 }.to_json)
end
end
serializer = BasicCategorySerializer.new(
category,
scope: Guardian.new(user),
root: false
).as_json
context "group" do
it "registers group custom fields" do
group
expect(Group.get_custom_field_type("group_field_1")).to eq(:string)
expect(serializer[:category_field_1]).to eq({ a: 1, b: 2 }.to_json)
end
end
it "adds group custom fields to the basic group serializer" do
group.custom_fields["group_field_1"] = "Hello"
group.save_custom_fields(true)
context "group" do
it "registers" do
group
expect(Group.get_custom_field_type("group_field_1")).to eq(:string)
end
serializer = BasicGroupSerializer.new(
group,
scope: Guardian.new(user),
root: false
).as_json
it "adds custom fields to the basic group serializer" do
group.custom_fields["group_field_1"] = "Hello"
group.save_custom_fields(true)
expect(serializer[:group_field_1]).to eq("Hello")
serializer = BasicGroupSerializer.new(
group,
scope: Guardian.new(user),
root: false
).as_json
expect(serializer[:group_field_1]).to eq("Hello")
end
end
end
end

Datei anzeigen

@ -4,18 +4,8 @@ require_relative '../plugin_helper'
describe ExtraLocalesControllerCustomWizard, type: :request do
let(:new_user) { Fabricate(:user, trust_level: TrustLevel[0]) }
let(:staff_user) { Fabricate(:moderator) }
let(:template) {
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)
}
let(:template) { get_wizard_fixture("wizard") }
let(:permitted) { get_wizard_fixture("wizard/permitted") }
before do
CustomWizard::Template.save(template, skip_jobs: true)

Datei anzeigen

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

Datei anzeigen

@ -2,11 +2,7 @@
require_relative '../plugin_helper'
describe CustomWizardUsersController, type: :request do
let(:template) do
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read)
end
let(:template) { get_wizard_fixture("wizard") }
before do
@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": [
"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": [
{
"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",
"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",
"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",
"run_after": "step_2",
@ -529,24 +308,6 @@
"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": [
{
"type": "assignment",

Datei anzeigen

@ -7,11 +7,7 @@ describe Jobs::SetAfterTimeWizard do
fab!(:user2) { Fabricate(:user) }
fab!(:user3) { Fabricate(:user) }
let(:template) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read).with_indifferent_access
}
let(:template) { get_wizard_fixture("wizard") }
it "sets wizard redirect for all users " do
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)
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
fab!(:admin_user) { Fabricate(:user, admin: true) }
let(:custom_field_json) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json"
).read)
}
let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") }
before do
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
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
it "saves custom fields" do

Datei anzeigen

@ -3,12 +3,7 @@ require_relative '../../../plugin_helper'
describe CustomWizard::AdminManagerController do
fab!(:admin_user) { Fabricate(:user, admin: true) }
let(:template) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read)
}
let(:template) { get_wizard_fixture("wizard") }
before do
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!(:user3) { Fabricate(:user) }
let(:template) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read)
}
let(:template) { get_wizard_fixture("wizard") }
let(:template_2) {
temp = template.dup
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!(:user1) { Fabricate(:user) }
fab!(:user2) { Fabricate(:user) }
let(:template) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read)
}
let(:template) { get_wizard_fixture("wizard") }
before do
CustomWizard::Template.save(template, skip_jobs: true)

Datei anzeigen

@ -2,21 +2,11 @@
require_relative '../../plugin_helper'
describe ApplicationController do
fab!(:user) {
Fabricate(
:user,
username: 'angus',
email: "angus@email.com",
trust_level: TrustLevel[3]
)
}
fab!(:user) { Fabricate(:user, username: 'angus', email: "angus@email.com", trust_level: TrustLevel[3]) }
let(:wizard_template) { get_wizard_fixture("wizard") }
before do
CustomWizard::Template.save(
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read),
skip_jobs: true)
CustomWizard::Template.save(wizard_template, skip_jobs: true)
@template = CustomWizard::Template.find('super_mega_fun_wizard')
end

Datei anzeigen

@ -8,12 +8,8 @@ describe "custom field extensions" do
let!(:category) { Fabricate(:category) }
let!(:user) { Fabricate(:user) }
let!(:group) { Fabricate(:group, users: [user]) }
let(:custom_field_json) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json"
).read)
}
let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") }
let(:pro_custom_field_json) { get_wizard_fixture("custom_field/pro_custom_fields") }
before do
custom_field_json['custom_fields'].each do |field_json|
@ -32,27 +28,6 @@ describe "custom field extensions" do
expect(response.parsed_body["topic_field_1"]).to eq(true)
end
it "adds category custom fields to the show categories response" do
category.custom_fields["category_field_1"] = { a: 1, b: 2 }
category.save_custom_fields(true)
get "/c/#{category.id}/show.json"
expect(response.status).to eq(200)
expect(response.parsed_body["category"]["category_field_1"]).to eq({ a: 1, b: 2 }.as_json)
end
it "adds group custom fields to the show group response" do
group.custom_fields["group_field_1"] = "Group cf entry"
group.save_custom_fields(true)
sign_in(user)
get "/groups/#{group.name}.json"
expect(response.status).to eq(200)
expect(response.parsed_body['group']['group_field_1']).to eq("Group cf entry")
end
it "adds post custom fields to the show post response" do
post.custom_fields["post_field_1"] = 7
post.save_custom_fields(true)
@ -63,32 +38,64 @@ describe "custom field extensions" do
expect(response.parsed_body['post_field_1']).to eq(7)
end
context "preloaded" do
it "preloads category custom fields on site categories" do
Site.preloaded_category_custom_fields << "other_field"
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
category.custom_fields["category_field_1"] = { a: 1, b: 2 }
category.save_custom_fields(true)
get "/site.json"
expect(response.status).to eq(200)
get "/c/#{category.id}/show.json"
site_category = response.parsed_body['categories'].select { |c| c['id'] == category.id }.first
expect(site_category["category_field_1"]).to eq({ a: 1, b: 2 }.as_json)
expect(response.status).to eq(200)
expect(response.parsed_body["category"]["category_field_1"]).to eq({ a: 1, b: 2 }.as_json)
end
it "preloads group custom fields on group index" do
Group.preloaded_custom_field_names << "other_field"
group = Fabricate(:group)
it "adds group custom fields to the show group response" do
group.custom_fields["group_field_1"] = "Group cf entry"
group.save_custom_fields(true)
get "/groups.json"
expect(response.status).to eq(200)
sign_in(user)
get "/groups/#{group.name}.json"
group = response.parsed_body['groups'].select { |g| g['id'] == group.id }.first
expect(group['group_field_1']).to eq("Group cf entry")
expect(response.status).to eq(200)
expect(response.parsed_body['group']['group_field_1']).to eq("Group cf entry")
end
context "preloaded" do
it "preloads category custom fields on site categories" do
Site.preloaded_category_custom_fields << "other_field"
category.custom_fields["category_field_1"] = { a: 1, b: 2 }
category.save_custom_fields(true)
get "/site.json"
expect(response.status).to eq(200)
site_category = response.parsed_body['categories'].select { |c| c['id'] == category.id }.first
expect(site_category["category_field_1"]).to eq({ a: 1, b: 2 }.as_json)
end
it "preloads group custom fields on group index" do
Group.preloaded_custom_field_names << "other_field"
group = Fabricate(:group)
group.custom_fields["group_field_1"] = "Group cf entry"
group.save_custom_fields(true)
get "/groups.json"
expect(response.status).to eq(200)
group = response.parsed_body['groups'].select { |g| g['id'] == group.id }.first
expect(group['group_field_1']).to eq("Group cf entry")
end
end
end
end

Datei anzeigen

@ -2,55 +2,12 @@
require_relative '../../plugin_helper'
describe CustomWizard::StepsController do
fab!(:user) {
Fabricate(
:user,
username: 'angus',
email: "angus@email.com",
trust_level: TrustLevel[3]
)
}
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
)
}
fab!(:user) { Fabricate(:user, username: 'angus', email: "angus@email.com", trust_level: TrustLevel[3]) }
fab!(:user2) { Fabricate(:user, username: 'bob', email: "bob@email.com", trust_level: TrustLevel[2]) }
let(:wizard_template) { get_wizard_fixture("wizard") }
let(:wizard_field_condition_template) { get_wizard_fixture("condition/wizard_field_condition") }
let(:user_condition_template) { get_wizard_fixture("condition/user_condition") }
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
before do
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'
expect(response.status).to eq(400)
end
it "when user cant see the step due to conditions" do
sign_in(user2)
new_wizard_template = wizard_template.dup
new_wizard_template['steps'][0]['condition'] = user_condition_template['condition']
CustomWizard::Template.save(new_wizard_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(403)
end
end
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")
end
it "returns an updated wizard when condition doesnt pass" do
new_template = wizard_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition wont pass"
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_3")
end
it "runs completion actions if user has completed wizard" do
new_template = wizard_template.dup
## route_to action
new_template['actions'].last['run_after'] = 'wizard_completion'
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition wont pass"
}
}
put '/w/super-mega-fun-wizard/steps/step_1.json'
put '/w/super-mega-fun-wizard/steps/step_2.json'
put '/w/super-mega-fun-wizard/steps/step_3.json'
expect(response.status).to eq(200)
expect(response.parsed_body['redirect_on_complete']).to eq("https://google.com")
end
it "saves results of completion actions if user has completed wizard" do
new_template = wizard_template.dup
## Create group action
new_template['actions'].first['run_after'] = 'wizard_completion'
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "My cool group"
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'
expect(response.status).to eq(200)
wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user)
group_name = wizard.submissions.first.fields['action_9']
group = Group.find_by(name: group_name)
expect(group.present?).to eq(true)
expect(group.full_name).to eq("My cool group")
topic_id = wizard.submissions.first.fields[new_template['actions'].first['id']]
topic = Topic.find(topic_id)
expect(topic.present?).to eq(true)
end
it "returns a final step without conditions" do
@ -197,88 +119,119 @@ describe CustomWizard::StepsController do
expect(response.parsed_body['final']).to eq(true)
end
it "returns the correct final step when the conditional final step and last step are the same" do
new_template = wizard_template.dup
new_template['steps'][0]['condition'] = user_condition_template['condition']
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
context "pro" do
before do
enable_pro
end
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will pass"
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['final']).to eq(false)
expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_3")
end
put '/w/super-mega-fun-wizard/steps/step_2.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
it "returns the correct final step when the conditional final step and last step are the same" do
new_template = wizard_template.dup
new_template['steps'][0]['condition'] = user_condition_template['condition']
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_3.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
it "returns the correct final step when the conditional final step and last step are different" do
new_template = wizard_template.dup
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will not pass"
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will pass"
}
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_2.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
put '/w/super-mega-fun-wizard/steps/step_2.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
it "returns the correct final step when the conditional final step is determined in the same action" do
new_template = wizard_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_3.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will not pass"
it "returns the correct final step when the conditional final step and last step are different" do
new_template = wizard_template.dup
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will not pass"
}
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
it "excludes the non-included conditional fields from the submissions" do
new_template = wizard_template.dup
new_template['steps'][1]['fields'][0]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_2.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will pass"
it "returns the correct final step when the conditional final step is determined in the same action" do
new_template = wizard_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will not pass"
}
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
put '/w/super-mega-fun-wizard/steps/step_2.json', params: {
fields: {
step_2_field_1: "1995-04-23"
it "excludes the non-included conditional fields from the submissions" do
new_template = wizard_template.dup
new_template['steps'][1]['fields'][0]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will pass"
}
}
}
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will not pass"
put '/w/super-mega-fun-wizard/steps/step_2.json', params: {
fields: {
step_2_field_1: "1995-04-23"
}
}
}
wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user)
submission = wizard.current_submission
expect(submission.fields.keys).not_to include("step_2_field_1")
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will not pass"
}
}
wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user)
submission = wizard.current_submission
expect(submission.fields.keys).not_to include("step_2_field_1")
end
end
end

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -4,12 +4,7 @@ require_relative '../../plugin_helper'
describe CustomWizard::CustomFieldSerializer do
fab!(:user) { Fabricate(:user) }
let(:custom_field_json) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json"
).read)
}
let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") }
it 'should return custom field attributes' do
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
fab!(:user) { Fabricate(:user) }
let(:template) { get_wizard_fixture("wizard") }
before do
CustomWizard::Template.save(
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read),
skip_jobs: true)
CustomWizard::Template.save(template, skip_jobs: true)
@wizard = CustomWizard::Builder.new("super_mega_fun_wizard", user).build
end

Datei anzeigen

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

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