diff --git a/assets/javascripts/discourse/components/custom-field-input.js.es6 b/assets/javascripts/discourse/components/custom-field-input.js.es6 index e49c6f1d..877b83fb 100644 --- a/assets/javascripts/discourse/components/custom-field-input.js.es6 +++ b/assets/javascripts/discourse/components/custom-field-input.js.es6 @@ -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 []; } diff --git a/assets/javascripts/discourse/components/wizard-advanced-toggle.js.es6 b/assets/javascripts/discourse/components/wizard-advanced-toggle.js.es6 deleted file mode 100644 index c6e1fd9c..00000000 --- a/assets/javascripts/discourse/components/wizard-advanced-toggle.js.es6 +++ /dev/null @@ -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"); - }, - }, -}); diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index feb83754..c50be2ba 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -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; + }, []); + }, }); diff --git a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 index b5c10c65..37266a6b 100644 --- a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 @@ -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") diff --git a/assets/javascripts/discourse/components/wizard-message.js.es6 b/assets/javascripts/discourse/components/wizard-message.js.es6 index b273e78b..686a7254 100644 --- a/assets/javascripts/discourse/components/wizard-message.js.es6 +++ b/assets/javascripts/discourse/components/wizard-message.js.es6 @@ -6,6 +6,7 @@ import I18n from "I18n"; const icons = { error: "times-circle", success: "check-circle", + warn: "exclamation-circle", info: "info-circle", }; diff --git a/assets/javascripts/discourse/components/wizard-pro-selector.js.es6 b/assets/javascripts/discourse/components/wizard-pro-selector.js.es6 new file mode 100644 index 00000000..8e472782 --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-pro-selector.js.es6 @@ -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"; + }, +}); diff --git a/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-header.js.es6 b/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-header.js.es6 new file mode 100644 index 00000000..c1f7251c --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-header.js.es6 @@ -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; + } + ), +}); diff --git a/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-row.js.es6 b/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-row.js.es6 new file mode 100644 index 00000000..23034ac1 --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-row.js.es6 @@ -0,0 +1,3 @@ +import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row"; + +export default SelectKitRowComponent.extend(); diff --git a/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 b/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 new file mode 100644 index 00000000..7824cb83 --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 @@ -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); + }); + }, + }, +}); diff --git a/assets/javascripts/discourse/components/wizard-realtime-validations.js.es6 b/assets/javascripts/discourse/components/wizard-realtime-validations.js.es6 index 8332b86e..04123e3d 100644 --- a/assets/javascripts/discourse/components/wizard-realtime-validations.js.es6 +++ b/assets/javascripts/discourse/components/wizard-realtime-validations.js.es6 @@ -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) => { diff --git a/assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 new file mode 100644 index 00000000..61012d8f --- /dev/null +++ b/assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 @@ -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); + }); + }, + }, +}); diff --git a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 index 332efedd..6aa5dea4 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 @@ -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 = $('

'); $copyRange.html(this.wizardUrl); diff --git a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 index 90ab5359..5468e863 100644 --- a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 +++ b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 @@ -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, + }); } ); }, diff --git a/assets/javascripts/discourse/lib/wizard-json.js.es6 b/assets/javascripts/discourse/lib/wizard-json.js.es6 index 79da60cb..95eaba49 100644 --- a/assets/javascripts/discourse/lib/wizard-json.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-json.js.es6 @@ -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 || []; diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index 8b8200b0..a25ab6d0 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -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: {}, }; diff --git a/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 b/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 new file mode 100644 index 00000000..76429726 --- /dev/null +++ b/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 @@ -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; diff --git a/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 index a1c625ad..45ca9ae7 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 @@ -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, + }); }, }); diff --git a/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 new file mode 100644 index 00000000..d5c7fbd7 --- /dev/null +++ b/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 @@ -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(); + }, + }, +}); diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 index cb2d54c3..7032b974 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 @@ -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); diff --git a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs index 10501498..09a0d569 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs @@ -32,7 +32,8 @@ {{custom-field-input field=field removeField=(action "removeField") - saveField=(action "saveField")}} + saveField=(action "saveField") + proSubscribed=proSubscribed}} {{/each}} diff --git a/assets/javascripts/discourse/templates/admin-wizards-pro.hbs b/assets/javascripts/discourse/templates/admin-wizards-pro.hbs new file mode 100644 index 00000000..b4f0691b --- /dev/null +++ b/assets/javascripts/discourse/templates/admin-wizards-pro.hbs @@ -0,0 +1,34 @@ +
+

{{i18n "admin.wizard.pro.title"}}

+ +
+ {{#if model.authentication.active}} + {{conditional-loading-spinner size="small" condition=unauthorizing}} + + {{i18n "admin.wizard.pro.unauthorize"}} + + + {{else}} + {{d-button + icon="id-card" + label="admin.wizard.pro.authorize" + title="admin.wizard.pro.authorize" + action=(route-action "authorize")}} + {{/if}} +
+
+ +{{wizard-message + key=messageKey + url=messageUrl + type=messageType + opts=messageOpts + component="pro"}} + +
+ {{#if showSubscription}} + {{wizard-pro-subscription subscription=model.subscription}} + {{/if}} +
diff --git a/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs index 6f4996f6..df97513b 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs @@ -62,4 +62,4 @@ {{conditional-loading-spinner condition=loadingMore}} {{/load-more}} -{{/if}} \ No newline at end of file +{{/if}} diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs index 7e5b0ee0..7645c20e 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs @@ -126,33 +126,25 @@ - {{wizard-advanced-toggle showAdvanced=wizard.showAdvanced}} - - {{#if wizard.showAdvanced}} -
- -
-
- -
-
- {{input type="checkbox" checked=wizard.save_submissions}} - {{i18n "admin.wizard.save_submissions_label"}} -
-
- -
-
- -
-
- {{input type="checkbox" checked=wizard.restart_on_revisit}} - {{i18n "admin.wizard.restart_on_revisit_label"}} -
-
- +
+
+
- {{/if}} +
+ {{input type="checkbox" checked=wizard.save_submissions}} + {{i18n "admin.wizard.save_submissions_label"}} +
+
+ +
+
+ +
+
+ {{input type="checkbox" checked=wizard.restart_on_revisit}} + {{i18n "admin.wizard.restart_on_revisit_label"}} +
+
{{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}}
diff --git a/assets/javascripts/discourse/templates/admin-wizards.hbs b/assets/javascripts/discourse/templates/admin-wizards.hbs index 5baa9f52..0578e01f 100644 --- a/assets/javascripts/discourse/templates/admin-wizards.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards.hbs @@ -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"}}
{{d-icon "far-life-ring"}}{{i18n "admin.wizard.pro_support_button.label"}} diff --git a/assets/javascripts/discourse/templates/components/custom-field-input.hbs b/assets/javascripts/discourse/templates/components/custom-field-input.hbs index 43a97be8..2bb9acce 100644 --- a/assets/javascripts/discourse/templates/components/custom-field-input.hbs +++ b/assets/javascripts/discourse/templates/components/custom-field-input.hbs @@ -1,13 +1,13 @@ {{#if showInputs}} - {{combo-box + {{wizard-pro-selector value=field.klass content=klassContent none="admin.wizard.custom_field.klass.select" onChange=(action (mut field.klass))}} - {{combo-box + {{wizard-pro-selector value=field.type content=typeContent none="admin.wizard.custom_field.type.select" diff --git a/assets/javascripts/discourse/templates/components/submission-field.hbs b/assets/javascripts/discourse/templates/components/submission-field.hbs index 831dcc3a..c635ff47 100644 --- a/assets/javascripts/discourse/templates/components/submission-field.hbs +++ b/assets/javascripts/discourse/templates/components/submission-field.hbs @@ -160,4 +160,4 @@ {{d-icon "clock"}}{{format-date value format="tiny"}} -{{/if}} \ No newline at end of file +{{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-advanced-toggle.hbs b/assets/javascripts/discourse/templates/components/wizard-advanced-toggle.hbs deleted file mode 100644 index ec2bcb76..00000000 --- a/assets/javascripts/discourse/templates/components/wizard-advanced-toggle.hbs +++ /dev/null @@ -1,4 +0,0 @@ -{{d-button - action="toggleAdvanced" - label="admin.wizard.advanced" - class=toggleClass}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs index 4c645cf7..f5d53200 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs @@ -12,13 +12,14 @@
- {{combo-box + {{wizard-pro-selector value=action.type content=actionTypes onChange=(action "changeType") options=(hash none="admin.wizard.select_type" - )}} + ) + }}
@@ -714,99 +715,90 @@ {{/if}} -{{#if showAdvanced}} - {{wizard-advanced-toggle showAdvanced=action.showAdvanced}} - - {{#if action.showAdvanced}} -
- - {{#if hasCustomFields}} -
-
- -
- -
- {{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 - )}} -
-
- {{/if}} - - {{#if sendMessage}} -
-
- -
- -
- {{wizard-mapper - inputs=action.required - property="required" - onUpdate=(action "mappedFieldUpdated") - options=(hash - textSelection="value" - wizardFieldSelection=true - userFieldSelection=true - groupSelection=true - context="action" - )}} -
-
- {{/if}} - - {{#if showPostAdvanced}} -
-
- -
- -
- {{input type="checkbox" checked=action.skip_redirect}} - - - {{i18n "admin.wizard.action.skip_redirect.description" type="topic"}} - -
-
- -
-
- -
- -
- {{input type="checkbox" checked=action.suppress_notifications}} - - - {{i18n "admin.wizard.action.suppress_notifications.description" type="topic"}} - -
-
- {{/if}} - - {{#if routeTo}} -
-
- -
- -
- {{input value=action.code}} -
-
- {{/if}} +{{#if hasCustomFields}} +
+
+
- {{/if}} + +
+ {{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 + )}} +
+
+{{/if}} + +{{#if sendMessage}} +
+
+ +
+ +
+ {{wizard-mapper + inputs=action.required + property="required" + onUpdate=(action "mappedFieldUpdated") + options=(hash + textSelection="value" + wizardFieldSelection=true + userFieldSelection=true + groupSelection=true + context="action" + )}} +
+
+{{/if}} + +{{#if showPostAdvanced}} +
+
+ +
+ +
+ {{input type="checkbox" checked=action.skip_redirect}} + + + {{i18n "admin.wizard.action.skip_redirect.description" type="topic"}} + +
+
+ +
+
+ +
+ +
+ {{input type="checkbox" checked=action.suppress_notifications}} + + + {{i18n "admin.wizard.action.suppress_notifications.description" type="topic"}} + +
+
+{{/if}} + +{{#if routeTo}} +
+
+ +
+ +
+ {{input value=action.code}} +
+
{{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index 3b63cef7..8c8bb6d4 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -207,70 +207,66 @@
{{/if}} -{{#if showAdvanced}} - {{wizard-advanced-toggle showAdvanced=field.showAdvanced}} +{{#if proSubscribed}} +
+
+ + {{i18n "admin.wizard.pro.label"}} +
- {{#if field.showAdvanced}} -
+
+ {{wizard-mapper + inputs=field.condition + options=fieldConditionOptions}} +
+
+ +
+
+ + {{i18n "admin.wizard.pro.label"}} +
-
-
- -
+
+ {{wizard-mapper + inputs=field.index + options=fieldIndexOptions}} +
+
-
- {{wizard-mapper - inputs=field.condition - options=fieldConditionOptions}} -
+ {{#if isCategory}} +
+
+ + {{i18n "admin.wizard.pro.label"}}
- -
-
- -
- -
- {{wizard-mapper - inputs=field.index - options=fieldIndexOptions}} -
+ +
+ {{combo-box + value=field.property + content=categoryPropertyTypes + onChange=(action (mut field.property)) + options=(hash + none="admin.wizard.selector.placeholder.property" + )}}
- - {{#if isCategory}} -
-
- -
- -
- {{combo-box - value=field.property - content=categoryPropertyTypes - onChange=(action (mut field.property)) - options=(hash - none="admin.wizard.selector.placeholder.property" - )}} -
-
- {{/if}} - -
-
- -
-
- {{input - name="key" - value=field.key - class="medium" - placeholderKey="admin.wizard.translation_placeholder"}} -
-
- - {{#if validations}} - {{wizard-realtime-validations field=field validations=validations}} - {{/if}}
{{/if}} + +
+
+ + {{i18n "admin.wizard.pro.label"}} +
+
+ {{input + name="key" + value=field.key + placeholderKey="admin.wizard.translation_placeholder"}} +
+
+ + {{#if validations}} + {{wizard-realtime-validations field=field validations=validations}} + {{/if}} {{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs index 85adfe8a..91476ae3 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs @@ -33,85 +33,84 @@
-{{wizard-advanced-toggle showAdvanced=step.showAdvanced}} - -{{#if step.showAdvanced}} -
- -
-
- -
- -
- {{wizard-mapper - inputs=step.condition - options=stepConditionOptions}} -
+{{#if proSubscribed}} +
+
+ + {{i18n "admin.wizard.pro.label"}}
-
-
-
-

{{i18n "admin.wizard.step.force_final.label"}}

- {{input type="checkbox" checked=step.force_final}} - {{i18n "admin.wizard.step.force_final.description"}} -
+
+ {{wizard-mapper + inputs=step.condition + options=stepConditionOptions}}
+
-
-
- -
-
- {{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}} -
-
- {{i18n "admin.wizard.step.required_data.not_permitted_message"}} -
- {{input value=step.required_data_message}} +
+
+
+

{{i18n "admin.wizard.step.force_final.label"}}

+ {{input type="checkbox" checked=step.force_final}} + {{i18n "admin.wizard.step.force_final.description"}} +
+
+ +
+
+ + {{i18n "admin.wizard.pro.label"}} +
+
+ {{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}} +
+
+ {{i18n "admin.wizard.step.required_data.not_permitted_message"}}
- {{/if}} -
+ {{input value=step.required_data_message}} +
+ {{/if}}
+
-
-
- -
-
- {{wizard-mapper - inputs=step.permitted_params - options=(hash - pairConnector="set" - inputTypes="association" - keyPlaceholder="admin.wizard.param_key" - valuePlaceholder="admin.wizard.submission_key" - context="step" - )}} -
+
+
+ + {{i18n "admin.wizard.pro.label"}}
+
+ {{wizard-mapper + inputs=step.permitted_params + options=(hash + pairConnector="set" + inputTypes="association" + keyPlaceholder="admin.wizard.param_key" + valuePlaceholder="admin.wizard.submission_key" + context="step" + )}} +
+
-
-
- -
-
- {{input - name="key" - value=step.key - placeholderKey="admin.wizard.translation_placeholder"}} -
+
+
+ + {{i18n "admin.wizard.pro.label"}} +
+
+ {{input + name="key" + value=step.key + placeholderKey="admin.wizard.translation_placeholder"}}
{{/if}} @@ -129,5 +128,6 @@ currentFieldId=currentField.id fieldTypes=fieldTypes removeField="removeField" - wizardFields=wizardFields}} + wizardFields=wizardFields + proSubscribed=proSubscribed}} {{/each}} diff --git a/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-header.hbs b/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-header.hbs new file mode 100644 index 00000000..db467b02 --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-header.hbs @@ -0,0 +1,15 @@ +
+ + {{component selectKit.options.selectedNameComponent + tabindex=tabindex + item=selectedContent + selectKit=selectKit + shouldDisplayClearableButton=shouldDisplayClearableButton + }} + + {{#if selectedContent.pro}} + {{i18n "admin.wizard.pro.label"}} + {{/if}} + + {{d-icon caretIcon class="caret-icon"}} +
diff --git a/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-row.hbs b/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-row.hbs new file mode 100644 index 00000000..077265ef --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-row.hbs @@ -0,0 +1,15 @@ +{{#if icons}} +
+ + {{#each icons as |icon|}} + {{d-icon icon translatedtitle=(dasherize title)}} + {{/each}} +
+{{/if}} + +
+ {{html-safe label}} + {{#if item.pro}} + {{i18n "admin.wizard.pro.label"}} + {{/if}} +
diff --git a/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs b/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs new file mode 100644 index 00000000..8eca5996 --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs @@ -0,0 +1,31 @@ +
+

{{title}}

+ +
+ + {{#if updating}} + {{loading-spinner size="small"}} + {{else if updateIcon}} + {{d-icon updateIcon}} + {{/if}} + + {{d-button + icon="sync" + action=(action "update") + disabled=updating + title="admin.wizard.pro.subscription.update" + label="admin.wizard.pro.subscription.update"}} +
+
+ +{{#if subscribed}} +
+
{{stateLabel}}
+ + {{#if subscription.updated_at}} +
+ {{i18n "admin.wizard.pro.subscription.last_updated"}} {{format-date subscription.updated_at leaveAgo="true"}} +
+ {{/if}} +
+{{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs b/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs index cd1298a9..1aa0893b 100644 --- a/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs @@ -1,50 +1,54 @@ -

{{i18n "admin.wizard.field.validations.header"}}

- -
    - {{#each-in field.validations as |type props|}} -
  • - -

    {{i18n (concat "admin.wizard.field.validations." type)}}

    - {{input type="checkbox" checked=props.status}} - {{i18n "admin.wizard.field.validations.enabled"}} -
    -
    -
    -
    - +
    + + {{i18n "admin.wizard.pro.label"}} +
    +
    +
      + {{#each-in field.validations as |type props|}} +
    • + +

      {{i18n (concat "admin.wizard.field.validations." type)}}

      + {{input type="checkbox" checked=props.status}} + {{i18n "admin.wizard.field.validations.enabled"}} +
      +
      +
      +
      + +
      +
      + {{category-selector + categories=(get this (concat "validationBuffer." type ".categories")) + onChange=(action "updateValidationCategories" type props) + class="wizard"}} +
      -
      - {{category-selector - categories=(get this (concat "validationBuffer." type ".categories")) - onChange=(action "updateValidationCategories" type props) - class="wizard"}} +
      +
      + +
      +
      + {{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))}} +
      +
      +
      +
      + +
      +
      + {{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"}} +
      -
      -
      - -
      -
      - {{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))}} -
      -
      -
      -
      - -
      -
      - {{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"}} -
      -
      -
      -
    • - {{/each-in}} -
    +
  • + {{/each-in}} +
+
diff --git a/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs b/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs index 24743a92..a167a954 100644 --- a/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs +++ b/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs @@ -29,4 +29,4 @@ label="directory.edit_columns.reset_to_default" action=(action "resetToDefault") }} -
\ No newline at end of file +
diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index 02fab71b..7027950a 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -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); diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index aef049fb..e7103957 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -58,10 +58,11 @@ en: select_type: "Select a type" condition: "Condition" index: "Index" + pro_support_button: title: "Request Pro Support" label: "Pro Support" - + message: wizard: select: "Select a wizard, or create a new one" @@ -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 the documentation." + 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: "Moment.js format" validations: - header: "Realtime Validations" + header: "Validations" enabled: "Enabled" similar_topics: "Similar Topics" position: "Position" @@ -441,7 +449,25 @@ en: imported: imported upload: Select wizards.json destroy: Destroy - destroyed: destroyed + destroyed: destroyed + + pro: + nav_label: PRO + label: PRO + title: Custom Wizard PRO + authorize: Authorize + authorized: Authorized + unauthorize: cancel + not_subscribed: You're not currently subscribed + subscription: + title: + community: Community Subscription + business: Business Subscription + status: + active: Active + inactive: Inactive + update: Update + last_updated: Last updated wizard_js: group: diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 7e507450..fffa01cc 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -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." diff --git a/config/routes.rb b/config/routes.rb index 28fcbb82..abe36479 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 diff --git a/controllers/custom_wizard/admin/custom_fields.rb b/controllers/custom_wizard/admin/custom_fields.rb index c52759c9..40ff64be 100644 --- a/controllers/custom_wizard/admin/custom_fields.rb +++ b/controllers/custom_wizard/admin/custom_fields.rb @@ -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 diff --git a/controllers/custom_wizard/admin/pro.rb b/controllers/custom_wizard/admin/pro.rb new file mode 100644 index 00000000..650743e6 --- /dev/null +++ b/controllers/custom_wizard/admin/pro.rb @@ -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 diff --git a/controllers/custom_wizard/admin/wizard.rb b/controllers/custom_wizard/admin/wizard.rb index 8da64fac..e824398c 100644 --- a/controllers/custom_wizard/admin/wizard.rb +++ b/controllers/custom_wizard/admin/wizard.rb @@ -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 diff --git a/controllers/custom_wizard/wizard.rb b/controllers/custom_wizard/wizard.rb index 7125fc8a..732c5d5e 100644 --- a/controllers/custom_wizard/wizard.rb +++ b/controllers/custom_wizard/wizard.rb @@ -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 diff --git a/coverage/.last_run.json b/coverage/.last_run.json index 2d4d0378..cff5740b 100644 --- a/coverage/.last_run.json +++ b/coverage/.last_run.json @@ -1,5 +1,5 @@ { "result": { - "line": 91.83 + "line": 91.96 } } diff --git a/jobs/refresh_api_access_token.rb b/jobs/regular/refresh_api_access_token.rb similarity index 100% rename from jobs/refresh_api_access_token.rb rename to jobs/regular/refresh_api_access_token.rb diff --git a/jobs/set_after_time_wizard.rb b/jobs/regular/set_after_time_wizard.rb similarity index 100% rename from jobs/set_after_time_wizard.rb rename to jobs/regular/set_after_time_wizard.rb diff --git a/jobs/scheduled/update_pro_subscription.rb b/jobs/scheduled/update_pro_subscription.rb new file mode 100644 index 00000000..c790d529 --- /dev/null +++ b/jobs/scheduled/update_pro_subscription.rb @@ -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 diff --git a/lib/custom_wizard/action.rb b/lib/custom_wizard/action.rb index 113a0393..8ee11e54 100644 --- a/lib/custom_wizard/action.rb +++ b/lib/custom_wizard/action.rb @@ -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 diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index aede16a2..99370f19 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -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 diff --git a/lib/custom_wizard/custom_field.rb b/lib/custom_wizard/custom_field.rb index 9cc185ba..bc1a146d 100644 --- a/lib/custom_wizard/custom_field.rb +++ b/lib/custom_wizard/custom_field.rb @@ -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)) diff --git a/lib/custom_wizard/mapper.rb b/lib/custom_wizard/mapper.rb index 0c3543cf..6700e37d 100644 --- a/lib/custom_wizard/mapper.rb +++ b/lib/custom_wizard/mapper.rb @@ -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 diff --git a/lib/custom_wizard/pro.rb b/lib/custom_wizard/pro.rb new file mode 100644 index 00000000..61097069 --- /dev/null +++ b/lib/custom_wizard/pro.rb @@ -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 diff --git a/lib/custom_wizard/pro/authentication.rb b/lib/custom_wizard/pro/authentication.rb new file mode 100644 index 00000000..23603898 --- /dev/null +++ b/lib/custom_wizard/pro/authentication.rb @@ -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 diff --git a/lib/custom_wizard/pro/subscription.rb b/lib/custom_wizard/pro/subscription.rb new file mode 100644 index 00000000..7f5cf911 --- /dev/null +++ b/lib/custom_wizard/pro/subscription.rb @@ -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 diff --git a/lib/custom_wizard/validators/template.rb b/lib/custom_wizard/validators/template.rb index c90944e9..839a070f 100644 --- a/lib/custom_wizard/validators/template.rb +++ b/lib/custom_wizard/validators/template.rb @@ -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]) diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb index e52feec4..98e7938b 100644 --- a/lib/custom_wizard/wizard.rb +++ b/lib/custom_wizard/wizard.rb @@ -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| diff --git a/plugin.rb b/plugin.rb index 58811291..808975d6 100644 --- a/plugin.rb +++ b/plugin.rb @@ -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 diff --git a/serializers/custom_wizard/pro/authentication_serializer.rb b/serializers/custom_wizard/pro/authentication_serializer.rb new file mode 100644 index 00000000..0a2915e4 --- /dev/null +++ b/serializers/custom_wizard/pro/authentication_serializer.rb @@ -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 diff --git a/serializers/custom_wizard/pro/subscription_serializer.rb b/serializers/custom_wizard/pro/subscription_serializer.rb new file mode 100644 index 00000000..d3e04e4e --- /dev/null +++ b/serializers/custom_wizard/pro/subscription_serializer.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +class CustomWizard::ProSubscriptionSerializer < ApplicationSerializer + attributes :type, + :active, + :updated_at + + def active + object.active? + end +end diff --git a/serializers/custom_wizard/pro_serializer.rb b/serializers/custom_wizard/pro_serializer.rb new file mode 100644 index 00000000..2f141c6d --- /dev/null +++ b/serializers/custom_wizard/pro_serializer.rb @@ -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 diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 8b617c39..34a08461 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -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 diff --git a/spec/components/custom_wizard/builder_spec.rb b/spec/components/custom_wizard/builder_spec.rb index 8e80d806..9b9c6000 100644 --- a/spec/components/custom_wizard/builder_spec.rb +++ b/spec/components/custom_wizard/builder_spec.rb @@ -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 diff --git a/spec/components/custom_wizard/custom_field_spec.rb b/spec/components/custom_wizard/custom_field_spec.rb index b17e26c6..2204264f 100644 --- a/spec/components/custom_wizard/custom_field_spec.rb +++ b/spec/components/custom_wizard/custom_field_spec.rb @@ -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 diff --git a/spec/components/custom_wizard/field_spec.rb b/spec/components/custom_wizard/field_spec.rb index 871c42cd..0fcf9fc2 100644 --- a/spec/components/custom_wizard/field_spec.rb +++ b/spec/components/custom_wizard/field_spec.rb @@ -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( diff --git a/spec/components/custom_wizard/mapper_spec.rb b/spec/components/custom_wizard/mapper_spec.rb index ed66d7c1..b210c588 100644 --- a/spec/components/custom_wizard/mapper_spec.rb +++ b/spec/components/custom_wizard/mapper_spec.rb @@ -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 diff --git a/spec/components/custom_wizard/pro_spec.rb b/spec/components/custom_wizard/pro_spec.rb new file mode 100644 index 00000000..6499b668 --- /dev/null +++ b/spec/components/custom_wizard/pro_spec.rb @@ -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 diff --git a/spec/components/custom_wizard/step_spec.rb b/spec/components/custom_wizard/step_spec.rb index bf4613a4..9ad02176 100644 --- a/spec/components/custom_wizard/step_spec.rb +++ b/spec/components/custom_wizard/step_spec.rb @@ -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]) diff --git a/spec/components/custom_wizard/submission_spec.rb b/spec/components/custom_wizard/submission_spec.rb index 4e8d86c0..a6c8a0d3 100644 --- a/spec/components/custom_wizard/submission_spec.rb +++ b/spec/components/custom_wizard/submission_spec.rb @@ -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) diff --git a/spec/components/custom_wizard/template_spec.rb b/spec/components/custom_wizard/template_spec.rb index 0e3dbdbe..fca7f91e 100644 --- a/spec/components/custom_wizard/template_spec.rb +++ b/spec/components/custom_wizard/template_spec.rb @@ -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) diff --git a/spec/components/custom_wizard/template_validator_spec.rb b/spec/components/custom_wizard/template_validator_spec.rb index c8ce915a..7a84660c 100644 --- a/spec/components/custom_wizard/template_validator_spec.rb +++ b/spec/components/custom_wizard/template_validator_spec.rb @@ -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 diff --git a/spec/components/custom_wizard/update_validator_spec.rb b/spec/components/custom_wizard/update_validator_spec.rb index e976e1ff..c79a5b0b 100644 --- a/spec/components/custom_wizard/update_validator_spec.rb +++ b/spec/components/custom_wizard/update_validator_spec.rb @@ -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) diff --git a/spec/components/custom_wizard/wizard_spec.rb b/spec/components/custom_wizard/wizard_spec.rb index 67905f5a..9cccff97 100644 --- a/spec/components/custom_wizard/wizard_spec.rb +++ b/spec/components/custom_wizard/wizard_spec.rb @@ -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) diff --git a/spec/extensions/custom_field_extensions_spec.rb b/spec/extensions/custom_field_extensions_spec.rb index f0ce32f5..bf1b3ff2 100644 --- a/spec/extensions/custom_field_extensions_spec.rb +++ b/spec/extensions/custom_field_extensions_spec.rb @@ -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 diff --git a/spec/extensions/extra_locales_controller_spec.rb b/spec/extensions/extra_locales_controller_spec.rb index a71e39c4..bb451b7c 100644 --- a/spec/extensions/extra_locales_controller_spec.rb +++ b/spec/extensions/extra_locales_controller_spec.rb @@ -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) diff --git a/spec/extensions/invites_controller_spec.rb b/spec/extensions/invites_controller_spec.rb index 47c4ca84..93fdd9d7 100644 --- a/spec/extensions/invites_controller_spec.rb +++ b/spec/extensions/invites_controller_spec.rb @@ -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 diff --git a/spec/extensions/users_controller_spec.rb b/spec/extensions/users_controller_spec.rb index f4ba8e51..e4cd972f 100644 --- a/spec/extensions/users_controller_spec.rb +++ b/spec/extensions/users_controller_spec.rb @@ -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 diff --git a/spec/fixtures/actions/add_to_group.json b/spec/fixtures/actions/add_to_group.json new file mode 100644 index 00000000..2a5af1c3 --- /dev/null +++ b/spec/fixtures/actions/add_to_group.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/actions/create_category.json b/spec/fixtures/actions/create_category.json new file mode 100644 index 00000000..c5f3d5af --- /dev/null +++ b/spec/fixtures/actions/create_category.json @@ -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" + } + ] + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/actions/create_group.json b/spec/fixtures/actions/create_group.json new file mode 100644 index 00000000..e2e52ef2 --- /dev/null +++ b/spec/fixtures/actions/create_group.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/actions/send_message.json b/spec/fixtures/actions/send_message.json new file mode 100644 index 00000000..ddc9bf10 --- /dev/null +++ b/spec/fixtures/actions/send_message.json @@ -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" + ] + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/actions/send_message_multi.json b/spec/fixtures/actions/send_message_multi.json new file mode 100644 index 00000000..8ab0fdb7 --- /dev/null +++ b/spec/fixtures/actions/send_message_multi.json @@ -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" + ] + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/custom_field/custom_fields.json b/spec/fixtures/custom_field/custom_fields.json index 0e7741ce..c9b98476 100644 --- a/spec/fixtures/custom_field/custom_fields.json +++ b/spec/fixtures/custom_field/custom_fields.json @@ -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" - ] } ] } \ No newline at end of file diff --git a/spec/fixtures/custom_field/pro_custom_fields.json b/spec/fixtures/custom_field/pro_custom_fields.json new file mode 100644 index 00000000..b7060bdf --- /dev/null +++ b/spec/fixtures/custom_field/pro_custom_fields.json @@ -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" + ] + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/wizard.json b/spec/fixtures/wizard.json index a505c0d3..1560acef 100644 --- a/spec/fixtures/wizard.json +++ b/spec/fixtures/wizard.json @@ -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", diff --git a/spec/jobs/set_after_time_wizard_spec.rb b/spec/jobs/set_after_time_wizard_spec.rb index 35576f01..93e46c8b 100644 --- a/spec/jobs/set_after_time_wizard_spec.rb +++ b/spec/jobs/set_after_time_wizard_spec.rb @@ -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 diff --git a/spec/jobs/update_pro_subscription_spec.rb b/spec/jobs/update_pro_subscription_spec.rb new file mode 100644 index 00000000..0aae9668 --- /dev/null +++ b/spec/jobs/update_pro_subscription_spec.rb @@ -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 diff --git a/spec/plugin_helper.rb b/spec/plugin_helper.rb index 9e4bbbbe..4aa34029 100644 --- a/spec/plugin_helper.rb +++ b/spec/plugin_helper.rb @@ -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 diff --git a/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb b/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb index 8c1a8550..d2e086d2 100644 --- a/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb @@ -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 diff --git a/spec/requests/custom_wizard/admin/manager_controller_spec.rb b/spec/requests/custom_wizard/admin/manager_controller_spec.rb index 87c980f2..6218ea7e 100644 --- a/spec/requests/custom_wizard/admin/manager_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/manager_controller_spec.rb @@ -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) diff --git a/spec/requests/custom_wizard/admin/pro_controller_spec.rb b/spec/requests/custom_wizard/admin/pro_controller_spec.rb new file mode 100644 index 00000000..563572bd --- /dev/null +++ b/spec/requests/custom_wizard/admin/pro_controller_spec.rb @@ -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 diff --git a/spec/requests/custom_wizard/admin/submissions_controller_spec.rb b/spec/requests/custom_wizard/admin/submissions_controller_spec.rb index 36296e95..5a79679f 100644 --- a/spec/requests/custom_wizard/admin/submissions_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/submissions_controller_spec.rb @@ -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" diff --git a/spec/requests/custom_wizard/admin/wizard_controller_spec.rb b/spec/requests/custom_wizard/admin/wizard_controller_spec.rb index 82aa4fc5..9d7ed18d 100644 --- a/spec/requests/custom_wizard/admin/wizard_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/wizard_controller_spec.rb @@ -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) diff --git a/spec/requests/custom_wizard/application_controller_spec.rb b/spec/requests/custom_wizard/application_controller_spec.rb index 0835f246..f92ac7c0 100644 --- a/spec/requests/custom_wizard/application_controller_spec.rb +++ b/spec/requests/custom_wizard/application_controller_spec.rb @@ -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 diff --git a/spec/requests/custom_wizard/custom_field_extensions_spec.rb b/spec/requests/custom_wizard/custom_field_extensions_spec.rb index b991769a..64d7c755 100644 --- a/spec/requests/custom_wizard/custom_field_extensions_spec.rb +++ b/spec/requests/custom_wizard/custom_field_extensions_spec.rb @@ -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 diff --git a/spec/requests/custom_wizard/steps_controller_spec.rb b/spec/requests/custom_wizard/steps_controller_spec.rb index 5da75d8d..85353e4c 100644 --- a/spec/requests/custom_wizard/steps_controller_spec.rb +++ b/spec/requests/custom_wizard/steps_controller_spec.rb @@ -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 diff --git a/spec/requests/custom_wizard/wizard_controller_spec.rb b/spec/requests/custom_wizard/wizard_controller_spec.rb index 26f90040..f6211b55 100644 --- a/spec/requests/custom_wizard/wizard_controller_spec.rb +++ b/spec/requests/custom_wizard/wizard_controller_spec.rb @@ -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 diff --git a/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb b/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb index bf575827..6694e979 100644 --- a/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb +++ b/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb @@ -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) diff --git a/spec/serializers/custom_wizard/custom_field_serializer_spec.rb b/spec/serializers/custom_wizard/custom_field_serializer_spec.rb index 4f5ffd72..2cf92f52 100644 --- a/spec/serializers/custom_wizard/custom_field_serializer_spec.rb +++ b/spec/serializers/custom_wizard/custom_field_serializer_spec.rb @@ -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| diff --git a/spec/serializers/custom_wizard/pro/authentication_serializer_spec.rb b/spec/serializers/custom_wizard/pro/authentication_serializer_spec.rb new file mode 100644 index 00000000..53dd74c2 --- /dev/null +++ b/spec/serializers/custom_wizard/pro/authentication_serializer_spec.rb @@ -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 diff --git a/spec/serializers/custom_wizard/pro/subscription_serializer_spec.rb b/spec/serializers/custom_wizard/pro/subscription_serializer_spec.rb new file mode 100644 index 00000000..a775863e --- /dev/null +++ b/spec/serializers/custom_wizard/pro/subscription_serializer_spec.rb @@ -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 diff --git a/spec/serializers/custom_wizard/pro_serializer_spec.rb b/spec/serializers/custom_wizard/pro_serializer_spec.rb new file mode 100644 index 00000000..45c1956e --- /dev/null +++ b/spec/serializers/custom_wizard/pro_serializer_spec.rb @@ -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 diff --git a/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb index 1fa9671c..349b21f8 100644 --- a/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb +++ b/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb @@ -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 diff --git a/spec/serializers/custom_wizard/wizard_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_serializer_spec.rb index 2052639a..fe36d5a2 100644 --- a/spec/serializers/custom_wizard/wizard_serializer_spec.rb +++ b/spec/serializers/custom_wizard/wizard_serializer_spec.rb @@ -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 diff --git a/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb index 35ce0fd2..53afa8e5 100644 --- a/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb +++ b/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb @@ -4,22 +4,8 @@ require_relative '../../plugin_helper' describe CustomWizard::StepSerializer do fab!(:user) { Fabricate(:user) } - - let(:wizard_template) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read - ) - } - - let(:required_data_json) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/required_data.json" - ).read - ) - } + let(:wizard_template) { get_wizard_fixture("wizard") } + let(:required_data_json) { get_wizard_fixture("step/required_data") } before do CustomWizard::Template.save(wizard_template, skip_jobs: true)