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

Remove more subscription related things and integrate with subscription client

Dieser Commit ist enthalten in:
Angus McLeod 2022-03-25 12:18:54 +01:00
Ursprung 5edfb4c41e
Commit f607863510
49 geänderte Dateien mit 522 neuen und 1129 gelöschten Zeilen

Datei anzeigen

@ -3,8 +3,12 @@ class CustomWizard::AdminController < ::Admin::AdminController
before_action :ensure_admin before_action :ensure_admin
def index def index
subcription = CustomWizard::Subscription.new
render_json_dump( render_json_dump(
api_section: ["business"].include?(CustomWizard::Subscription.type) subscribed: subcription.subscribed?,
subscription_type: subcription.type,
subscription_attributes: CustomWizard::Subscription.attributes,
subscription_client_installed: subcription.client_installed?
) )
end end

Datei anzeigen

@ -2,9 +2,7 @@
class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController
def index def index
render_json_dump( render_json_dump(
custom_fields: custom_field_list, custom_fields: custom_field_list
subscribed: CustomWizard::Subscription.subscribed?,
subscription: CustomWizard::Subscription.type
) )
end end

Datei anzeigen

@ -10,9 +10,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
), ),
field_types: CustomWizard::Field.types, field_types: CustomWizard::Field.types,
realtime_validations: CustomWizard::RealtimeValidation.types, realtime_validations: CustomWizard::RealtimeValidation.types,
custom_fields: custom_field_list, custom_fields: custom_field_list
subscribed: CustomWizard::Subscription.subscribed?,
subscription: CustomWizard::Subscription.type
) )
end end

Datei anzeigen

@ -3,29 +3,6 @@ import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { alias, equal, or } from "@ember/object/computed"; import { alias, equal, or } from "@ember/object/computed";
import I18n from "I18n"; import I18n from "I18n";
import wizardSchema, {
requiringAdditionalSubscription,
subscriptionLevel,
} from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
const generateContent = function (kategory, subscription) {
let unsubscribedCustomFields = requiringAdditionalSubscription(
subscription,
"custom_fields",
kategory
);
return wizardSchema.custom_field[kategory].reduce((result, item) => {
let disabled = unsubscribedCustomFields.includes(item);
result.push({
id: item,
name: I18n.t(`admin.wizard.custom_field.${kategory}.${item}`),
subscription: subscriptionLevel(item, "custom_fields", kategory),
disabled,
});
return result;
}, []);
};
export default Component.extend({ export default Component.extend({
tagName: "tr", tagName: "tr",
topicSerializers: ["topic_view", "topic_list_item"], topicSerializers: ["topic_view", "topic_list_item"],
@ -58,16 +35,6 @@ export default Component.extend({
} }
}, },
@discourseComputed("subscription")
customFieldTypes(subscription) {
return generateContent("type", subscription);
},
@discourseComputed("subscription")
customFieldKlasses(subscription) {
return generateContent("klass", subscription);
},
@observes("field.klass") @observes("field.klass")
clearSerializersWhenClassChanges() { clearSerializersWhenClassChanges() {
this.set("field.serializers", null); this.set("field.serializers", null);

Datei anzeigen

@ -1,8 +1,6 @@
import { default as discourseComputed } from "discourse-common/utils/decorators"; import { default as discourseComputed } from "discourse-common/utils/decorators";
import wizardSchema, { import wizardSchema from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
requiringAdditionalSubscription, import { subscriptionSelectKitContent } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-subscription";
subscriptionLevel,
} from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
import { empty, equal, or } from "@ember/object/computed"; import { empty, equal, or } from "@ember/object/computed";
import { notificationLevels, selectKitContent } from "../lib/wizard"; import { notificationLevels, selectKitContent } from "../lib/wizard";
import { computed } from "@ember/object"; import { computed } from "@ember/object";
@ -97,22 +95,8 @@ export default Component.extend(UndoChanges, {
return apis.find((a) => a.name === api).endpoints; return apis.find((a) => a.name === api).endpoints;
}, },
@discourseComputed("subscription") @discourseComputed
actionTypes(subscription) { actionTypes() {
let unsubscribedActions = requiringAdditionalSubscription( return subscriptionSelectKitContent("action", "types");
subscription,
"actions",
""
);
return Object.keys(wizardSchema.action.types).reduce((result, type) => {
let disabled = unsubscribedActions.includes(type);
result.push({
id: type,
name: I18n.t(`admin.wizard.action.${type}.label`),
subscription: subscriptionLevel(type, "actions", ""),
disabled,
});
return result;
}, []);
}, },
}); });

Datei anzeigen

@ -0,0 +1,30 @@
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
import Subscription from "../mixins/subscription";
import DiscourseURL from "discourse/lib/url";
import I18n from "I18n";
export default Component.extend(Subscription, {
tagName: 'a',
classNameBindings: [":wizard-subscription-badge", "subscriptionType"],
attributeBindings: ["title"],
@discourseComputed("subscriptionType")
i18nKey(type) {
return `admin.wizard.subscription_container.type.${type ? type : "none"}`;
},
@discourseComputed("i18nKey")
title(i18nKey) {
return I18n.t(`${i18nKey}.title`);
},
@discourseComputed("i18nKey")
label(i18nKey) {
return I18n.t(`${i18nKey}.label`);
},
click() {
DiscourseURL.routeTo(this.subscriptionLink);
}
});

Datei anzeigen

@ -0,0 +1,26 @@
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
import Subscription from "../mixins/subscription";
export default Component.extend(Subscription, {
classNameBindings: [":wizard-subscription-container", "subscribed"],
@discourseComputed("subscribed")
subscribedIcon(subscribed) {
return subscribed ? "check" : "dash";
},
@discourseComputed("subscribed")
subscribedLabel(subscribed) {
return `admin.wizard.subscription_container.${
subscribed ? "subscribed" : "not_subscribed"
}.label`;
},
@discourseComputed("subscribed")
subscribedTitle(subscribed) {
return `admin.wizard.subscription_container.${
subscribed ? "subscribed" : "not_subscribed"
}.title`;
}
});

Datei anzeigen

@ -1,6 +1,21 @@
import SingleSelectComponent from "select-kit/components/single-select"; import SingleSelectComponent from "select-kit/components/single-select";
import Subscription from "../mixins/subscription";
import wizardSchema from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
import {
subscriptionTypeSufficient,
subscriptionTypes
} from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-subscription";
import discourseComputed from "discourse-common/utils/decorators";
export default SingleSelectComponent.extend({ const nameKey = function(feature, attribute, value) {
if (feature === 'action') {
return `admin.wizard.action.${value}.label`;
} else {
return `admin.wizard.${feature}.${attribute}.${value}`;
}
}
export default SingleSelectComponent.extend(Subscription, {
classNames: ["combo-box", "wizard-subscription-selector"], classNames: ["combo-box", "wizard-subscription-selector"],
selectKitOptions: { selectKitOptions: {
@ -13,6 +28,38 @@ export default SingleSelectComponent.extend({
caretDownIcon: "caret-down", caretDownIcon: "caret-down",
}, },
requiredSubscriptionType(feature, attribute, value) {
let attributes = this.subscriptionAttributes[feature];
if (!attributes || !attributes[attribute]) return null;
let requiredType = null;
Object.keys(attributes[attribute]).some(subscriptionType => {
let values = attributes[attribute][subscriptionType];
if (values[0] === "*" || values.includes(value)) {
if (subscriptionTypes.includes(subscriptionType)) {
requiredType = subscriptionType;
}
return true;
}
return false;
});
return requiredType;
},
@discourseComputed('feature', 'attribute')
content(feature, attribute) {
return wizardSchema[feature][attribute].map((value) => {
let requiredSubscriptionType = this.requiredSubscriptionType(feature, attribute, value);
return {
id: value,
name: I18n.t(nameKey(feature, attribute, value)),
subscriptionType: requiredSubscriptionType,
disabled: !subscriptionTypeSufficient(this.subscriptionType, requiredSubscriptionType)
};
});
},
modifyComponentForRow() { modifyComponentForRow() {
return "wizard-subscription-selector/wizard-subscription-selector-row"; return "wizard-subscription-selector/wizard-subscription-selector-row";
}, },

Datei anzeigen

@ -6,11 +6,11 @@
</label> </label>
<div class="controls"> <div class="controls">
{{combo-box {{combo-box
value=wizardListVal value=wizardListVal
content=wizardList content=wizardList
onChange=(action "changeWizard") onChange=(action "changeWizard")
options=(hash options=(hash
none="admin.wizard.select" none="admin.wizard.select"
)}} )}}
</div> </div>
</section> </section>

Datei anzeigen

@ -14,6 +14,7 @@ import I18n from "I18n";
export default Controller.extend({ export default Controller.extend({
hasName: notEmpty("wizard.name"), hasName: notEmpty("wizard.name"),
@observes("currentStep") @observes("currentStep")
resetCurrentObjects() { resetCurrentObjects() {
const currentStep = this.currentStep; const currentStep = this.currentStep;

Datei anzeigen

@ -1,26 +1,7 @@
import Controller, { inject as controller } from "@ember/controller"; import Controller from "@ember/controller";
import { isPresent } from "@ember/utils"; import { equal } from "@ember/object/computed";
import { A } from "@ember/array";
export default Controller.extend({ export default Controller.extend({
adminWizardsNotices: controller(), businessSubscription: equal('subscriptionType', 'business'),
standardSubscription: equal('subscriptionType', 'standard')
unsubscribe() {
this.messageBus.unsubscribe("/custom-wizard/notices");
},
subscribe() {
this.unsubscribe();
this.messageBus.subscribe("/custom-wizard/notices", (data) => {
if (isPresent(data.active_notice_count)) {
this.set("activeNoticeCount", data.active_notice_count);
this.adminWizardsNotices.setProperties({
notices: A(),
page: 0,
canLoadMore: true,
});
this.adminWizardsNotices.loadMoreNotices();
}
});
},
}); });

Datei anzeigen

@ -58,16 +58,6 @@ export default {
path: "/manager", path: "/manager",
resetNamespace: true, resetNamespace: true,
}); });
this.route("adminWizardsSubscription", {
path: "/subscription",
resetNamespace: true,
});
this.route("adminWizardsNotices", {
path: "/notices",
resetNamespace: true,
});
} }
); );
}, },

Datei anzeigen

@ -203,101 +203,17 @@ const custom_field = {
type: ["string", "boolean", "integer", "json"], type: ["string", "boolean", "integer", "json"],
}; };
const subscription_levels = { field.type = Object.keys(field.types);
standard: { action.type = Object.keys(action.types);
actions: ["send_message", "add_to_group", "watch_categories"],
custom_fields: {
klass: [],
type: ["json"],
},
},
business: {
actions: ["create_category", "create_group", "send_to_api"],
custom_fields: {
klass: ["group", "category"],
type: [],
},
},
};
const wizardSchema = { const wizardSchema = {
wizard, wizard,
step, step,
field, field,
custom_field, custom_field,
action, action
subscription_levels,
}; };
export function requiringAdditionalSubscription(
currentSubscription,
category,
subCategory
) {
switch (category) {
case "actions":
switch (currentSubscription) {
case "business":
return [];
case "standard":
return subscription_levels["business"][category];
default:
return subscription_levels["standard"][category].concat(
subscription_levels["business"][category]
);
}
case "custom_fields":
switch (currentSubscription) {
case "business":
return [];
case "standard":
return subscription_levels["business"][category][subCategory];
default:
return subscription_levels["standard"][category][subCategory].concat(
subscription_levels["business"][category][subCategory]
);
}
default:
return [];
}
}
export function subscriptionLevel(type, category, subCategory) {
switch (category) {
case "actions":
if (subscription_levels["business"].actions.includes(type)) {
return "business";
} else {
if (subscription_levels["standard"].actions.includes(type)) {
return "standard";
} else {
return "";
}
}
case "custom_fields":
if (
subscription_levels["business"].custom_fields[subCategory].includes(
type
)
) {
return "business";
} else {
if (
subscription_levels["standard"].custom_fields[subCategory].includes(
type
)
) {
return "standard";
} else {
return "";
}
}
default:
return "";
}
}
export function buildFieldTypes(types) { export function buildFieldTypes(types) {
wizardSchema.field.types = types; wizardSchema.field.types = types;
} }

Datei anzeigen

@ -0,0 +1,17 @@
const subscriptionTypes = [
'standard',
'business'
]
function subscriptionTypeSufficient(subscriptionType, requiredType) {
if (requiredType && !subscriptionType) return false;
if (requiredType === 'none' || requiredType === null) return true;
if (requiredType === 'standard' && subscriptionTypes.includes(subscriptionType)) return true;
if (requiredType === 'business' && subscriptionType === 'business') return true;
return false;
}
export {
subscriptionTypeSufficient,
subscriptionTypes
}

Datei anzeigen

@ -0,0 +1,26 @@
import Mixin from "@ember/object/mixin";
import { getOwner } from "discourse-common/lib/get-owner";
import { readOnly } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators";
export default Mixin.create({
subscriptionLandingUrl: "https://custom-wizard.pavilion.tech",
subscriptionClientUrl: "/admin/plugins/subscription-client",
@discourseComputed
adminWizards() {
return getOwner(this).lookup('controller:admin-wizards');
},
subscribed: readOnly('adminWizards.subscribed'),
subscriptionType: readOnly('adminWizards.subscriptionType'),
businessSubscription: readOnly('adminWizards.businessSubscription'),
standardSubscription: readOnly('adminWizards.standardSubscription'),
subscriptionAttributes: readOnly('adminWizards.subscriptionAttributes'),
subscriptionClientInstalled: readOnly('adminWizards.subscriptionClientInstalled'),
@discourseComputed("subscriptionClientInstalled")
subscriptionLink(subscriptionClientInstalled) {
return subscriptionClientInstalled ? this.subscriptionClientUrl : this.subscriptionLandingUrl;
}
});

Datei anzeigen

@ -9,13 +9,9 @@ export default DiscourseRoute.extend({
setupController(controller, model) { setupController(controller, model) {
const customFields = A(model.custom_fields || []); const customFields = A(model.custom_fields || []);
const subscribed = model.subscribed;
const subscription = model.subscription;
controller.setProperties({ controller.setProperties({
customFields, customFields
subscribed,
subscription,
}); });
}, },
}); });

Datei anzeigen

@ -38,9 +38,7 @@ export default DiscourseRoute.extend({
wizard, wizard,
currentStep: wizard.steps[0], currentStep: wizard.steps[0],
currentAction: wizard.actions[0], currentAction: wizard.actions[0],
creating: model.create, creating: model.create
subscribed: parentModel.subscribed,
subscription: parentModel.subscription,
}; };
controller.setProperties(props); controller.setProperties(props);

Datei anzeigen

@ -7,21 +7,17 @@ export default DiscourseRoute.extend({
}, },
setupController(controller, model) { setupController(controller, model) {
controller.set("api_section", model.api_section); controller.setProperties({
subscribed: model.subscribed,
if (model.active_notice_count) { subscriptionType: model.subscription_type,
controller.set("activeNoticeCount", model.active_notice_count); subscriptionAttributes: model.subscription_attributes,
} subscriptionClientInstalled: model.subscription_client_installed
if (model.featured_notices) { });
controller.set("featuredNotices", model.featured_notices);
}
controller.subscribe();
}, },
afterModel(model, transition) { afterModel(model, transition) {
if (transition.targetName === "adminWizards.index") { if (transition.targetName === "adminWizards.index") {
this.transitionTo("adminWizardsWizard"); this.transitionTo("adminWizardsWizard");
} }
}, }
}); });

Datei anzeigen

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

Datei anzeigen

@ -126,7 +126,7 @@
</div> </div>
</div> </div>
{{#subscription-container subscribed=subscribed}} {{#wizard-subscription-container}}
<div class="setting"> <div class="setting">
<div class="setting-label"> <div class="setting-label">
<label>{{i18n "admin.wizard.save_submissions"}}</label> <label>{{i18n "admin.wizard.save_submissions"}}</label>
@ -146,7 +146,7 @@
<span>{{i18n "admin.wizard.restart_on_revisit_label"}}</span> <span>{{i18n "admin.wizard.restart_on_revisit_label"}}</span>
</div> </div>
</div> </div>
{{/subscription-container}} {{/wizard-subscription-container}}
</div> </div>
{{wizard-links {{wizard-links
@ -177,9 +177,7 @@
wizard=wizard wizard=wizard
apis=apis apis=apis
removeAction="removeAction" removeAction="removeAction"
wizardFields=wizardFields wizardFields=wizardFields}}
subscribed=subscribed
subscription=subscription}}
{{/each}} {{/each}}
<div class="admin-wizard-buttons"> <div class="admin-wizard-buttons">

Datei anzeigen

@ -2,13 +2,14 @@
{{nav-item route="adminWizardsWizard" label="admin.wizard.nav_label"}} {{nav-item route="adminWizardsWizard" label="admin.wizard.nav_label"}}
{{nav-item route="adminWizardsCustomFields" label="admin.wizard.custom_field.nav_label"}} {{nav-item route="adminWizardsCustomFields" label="admin.wizard.custom_field.nav_label"}}
{{nav-item route="adminWizardsSubmissions" label="admin.wizard.submissions.nav_label"}} {{nav-item route="adminWizardsSubmissions" label="admin.wizard.submissions.nav_label"}}
{{#if api_section}} {{#if businessSubscription}}
{{nav-item route="adminWizardsApi" label="admin.wizard.api.nav_label"}} {{nav-item route="adminWizardsApi" label="admin.wizard.api.nav_label"}}
{{/if}} {{/if}}
{{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}} {{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}}
{{nav-item route="adminWizardsManager" label="admin.wizard.manager.nav_label"}} {{nav-item route="adminWizardsManager" label="admin.wizard.manager.nav_label"}}
<div class="admin-actions"> <div class="admin-actions">
{{wizard-subscription-badge}}
<a target="_blank" class="btn btn-pavilion-support" rel="noreferrer noopener" href="https://thepavilion.io/w/support" title={{i18n "admin.wizard.support_button.title"}}> <a target="_blank" class="btn btn-pavilion-support" rel="noreferrer noopener" href="https://thepavilion.io/w/support" title={{i18n "admin.wizard.support_button.title"}}>
{{d-icon "far-life-ring"}}{{i18n "admin.wizard.support_button.label"}} {{d-icon "far-life-ring"}}{{i18n "admin.wizard.support_button.label"}}
</a> </a>

Datei anzeigen

@ -2,16 +2,22 @@
<td> <td>
{{wizard-subscription-selector {{wizard-subscription-selector
value=field.klass value=field.klass
content=customFieldKlasses feature="custom_field"
none="admin.wizard.custom_field.klass.select" attribute="klass"
onChange=(action (mut field.klass))}} onChange=(action (mut field.klass))
options=(hash
none="admin.wizard.custom_field.klass.select"
)}}
</td> </td>
<td> <td>
{{wizard-subscription-selector {{wizard-subscription-selector
value=field.type value=field.type
content=customFieldTypes feature="custom_field"
none="admin.wizard.custom_field.type.select" attribute="type"
onChange=(action (mut field.type))}} onChange=(action (mut field.type))
options=(hash
none="admin.wizard.custom_field.type.select"
)}}
</td> </td>
<td class="input"> <td class="input">
{{input {{input
@ -22,8 +28,10 @@
{{multi-select {{multi-select
value=field.serializers value=field.serializers
content=serializerContent content=serializerContent
none="admin.wizard.custom_field.serializers.select" onChange=(action (mut field.serializers))
onChange=(action (mut field.serializers))}} options=(hash
none="admin.wizard.custom_field.serializers.select"
)}}
</td> </td>
<td class="actions"> <td class="actions">
{{#if loading}} {{#if loading}}

Datei anzeigen

@ -14,7 +14,8 @@
<div class="setting-value"> <div class="setting-value">
{{wizard-subscription-selector {{wizard-subscription-selector
value=action.type value=action.type
content=actionTypes feature="action"
attribute="type"
onChange=(action "changeType") onChange=(action "changeType")
options=(hash options=(hash
none="admin.wizard.select_type" none="admin.wizard.select_type"

Datei anzeigen

@ -222,7 +222,7 @@
</div> </div>
{{/if}} {{/if}}
{{#subscription-container subscribed=subscribed}} {{#wizard-subscription-container}}
<div class="setting full field-mapper-setting"> <div class="setting full field-mapper-setting">
<div class="setting-label"> <div class="setting-label">
<label>{{i18n "admin.wizard.condition"}}</label> <label>{{i18n "admin.wizard.condition"}}</label>
@ -237,7 +237,7 @@
<div class="setting full field-mapper-setting"> <div class="setting full field-mapper-setting">
<div class="setting-label"> <div class="setting-label">
<label>{{i18n "admin.wizard.index"}}</label>> <label>{{i18n "admin.wizard.index"}}</label>
</div> </div>
<div class="setting-value"> <div class="setting-value">
@ -248,10 +248,9 @@
</div> </div>
{{#if isCategory}} {{#if isCategory}}
<div class="setting pro"> <div class="setting">
<div class="setting-label"> <div class="setting-label">
<label>{{i18n "admin.wizard.field.property"}}</label> <label>{{i18n "admin.wizard.field.property"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div> </div>
<div class="setting-value"> <div class="setting-value">
@ -269,4 +268,4 @@
{{#if validations}} {{#if validations}}
{{wizard-realtime-validations field=field validations=validations}} {{wizard-realtime-validations field=field validations=validations}}
{{/if}} {{/if}}
{{/subscription-container}} {{/wizard-subscription-container}}

Datei anzeigen

@ -34,7 +34,7 @@
</div> </div>
</div> </div>
{{#subscription-container subscribed=subscribed}} {{#wizard-subscription-container}}
<div class="setting full field-mapper-setting"> <div class="setting full field-mapper-setting">
<div class="setting-label"> <div class="setting-label">
<label>{{i18n "admin.wizard.condition"}}</label> <label>{{i18n "admin.wizard.condition"}}</label>
@ -56,11 +56,11 @@
</div> </div>
</div> </div>
<div class="setting full field-mapper-setting pro"> <div class="setting full field-mapper-setting">
<div class="setting-label"> <div class="setting-label">
<label>{{i18n "admin.wizard.step.required_data.label"}}</label> <label>{{i18n "admin.wizard.step.required_data.label"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{wizard-mapper {{wizard-mapper
inputs=step.required_data inputs=step.required_data
@ -99,7 +99,7 @@
)}} )}}
</div> </div>
</div> </div>
{{/subscription-container}} {{/wizard-subscription-container}}
{{wizard-links {{wizard-links
itemType="field" itemType="field"

Datei anzeigen

@ -0,0 +1,6 @@
<svg width="300px" height="300px" viewBox="0 0 300 300" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="pavilion-logo" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path id="Combined-Shape" stroke="currentColor" stroke-width="35" d="M41.1381822,291.00006 L40.5778853,130.009744 M258.850727,291.638415 L259.290397,130.37133 M36.0002279,140.721678 L139.995368,36.2122772 M263.350577,141.009083 L138.927245,16.2478517"></path>
</g>
</svg>
<span>{{label}}</span>

Nachher

Breite:  |  Höhe:  |  Größe: 538 B

Datei anzeigen

@ -1,7 +1,7 @@
<div class="subscription-header"> <div class="subscription-header">
<h3>{{i18n "admin.wizard.subscription_container.title"}}</h3> <h4>{{i18n "admin.wizard.subscription_container.title"}}</h4>
<a href="/admin/wizards/subscription" title={{i18n subscribedTitle}}> <a href={{subscriptionLink}} title={{i18n subscribedTitle}}>
{{d-icon subscribedIcon}} {{d-icon subscribedIcon}}
{{i18n subscribedLabel}} {{i18n subscribedLabel}}
</a> </a>

Datei anzeigen

@ -7,8 +7,10 @@
shouldDisplayClearableButton=shouldDisplayClearableButton shouldDisplayClearableButton=shouldDisplayClearableButton
}} }}
{{#if selectedContent.subscription}} {{#if selectedContent.subscriptionType}}
<span class="subscription-label">{{i18n "admin.wizard.subscription.label"}}</span> <span class="subscription-label">
{{selectedContent.subscriptionType}}
</span>
{{/if}} {{/if}}
{{d-icon caretIcon class="caret-icon"}} {{d-icon caretIcon class="caret-icon"}}

Datei anzeigen

@ -9,9 +9,9 @@
<div class="texts"> <div class="texts">
<span class="name">{{html-safe label}}</span> <span class="name">{{html-safe label}}</span>
{{#if item.subscription}} {{#if item.subscriptionType}}
<span class="subscription-label"> <span class="subscription-label">
{{item.subscription}} {{item.subscriptionType}}
</span> </span>
{{/if}} {{/if}}
</div> </div>

Datei anzeigen

@ -25,6 +25,14 @@ $error: #ef1700;
} }
} }
.admin-wizards .admin-actions {
display: flex;
.btn-pavilion-support {
margin-left: 10px;
}
}
.wizard-message { .wizard-message {
background-color: var(--primary-low); background-color: var(--primary-low);
width: 100%; width: 100%;
@ -156,6 +164,16 @@ $error: #ef1700;
@extend .wizard-settings-group; @extend .wizard-settings-group;
} }
.admin-wizard-container.settings {
.wizard-settings {
.wizard-subscription-container {
[class~="setting"] {
margin-bottom: 0;
}
}
}
}
.wizard-custom-field { .wizard-custom-field {
background: transparent; background: transparent;
background-color: var(--primary-very-low); background-color: var(--primary-very-low);
@ -802,84 +820,6 @@ $error: #ef1700;
vertical-align: middle; vertical-align: middle;
} }
.admin-wizards-subscription {
.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-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-subscription-selector.select-kit.single-select {
.select-kit-row {
.texts {
display: flex;
align-items: center;
}
&.disabled {
background: var(--primary-low);
}
}
.subscription-label {
margin-left: 0.75em;
padding-top: 0.25em;
color: var(--tertiary);
font-size: 0.75em;
}
}
.btn.btn-pavilion-support { .btn.btn-pavilion-support {
background: var(--pavilion-primary); background: var(--pavilion-primary);
color: var(--pavilion-secondary); color: var(--pavilion-secondary);
@ -899,7 +839,7 @@ $error: #ef1700;
} }
} }
.subscription-container { .wizard-subscription-container {
width: 100%; width: 100%;
padding: 1em; padding: 1em;
background-color: rgba($pavilion_primary, 0.1); background-color: rgba($pavilion_primary, 0.1);
@ -907,158 +847,79 @@ $error: #ef1700;
.subscription-header { .subscription-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-bottom: 1em; margin-bottom: .25em;
h3 { h3 {
margin: 0; margin: 0;
} }
a { a {
color: var(--pavilion-primary); color: var(--primary);
} }
} }
&:not(.subscribed) .subscription-settings { &:not(.subscribed) .subscription-settings {
filter: blur(1px); filter: blur(1px);
pointer-events: none;
} }
} }
.admin-wizards-notices { .wizard-subscription-badge {
.wizard-table {
overflow: unset;
}
}
.wizard-notice {
padding: 0.75em;
margin-bottom: 1em;
border: 1px solid var(--primary-low);
&.dismissed {
display: none;
}
&.expired .notice-badge:not(.notice-expired-at),
&.expired a,
&.expired p {
color: var(--primary-medium) !important;
}
.notice-badge {
padding: 0 0.5em;
}
.notice-header {
display: flex;
.notice-title {
padding: 0;
}
.notice-header-right {
margin-left: auto;
display: flex;
align-items: center;
.notice-badge {
margin-left: 0.5em;
}
}
}
.dismiss-notice-container,
.hide-notice-container {
width: 40px;
display: flex;
justify-content: center;
align-items: center;
}
.dismiss-notice,
.hide-notice {
display: flex;
align-items: center;
.d-icon {
margin-right: 0;
color: var(--primary);
}
}
}
.notice-badge {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
min-height: 25px; max-height: 34px;
box-sizing: border-box; box-sizing: border-box;
color: var(--primary) !important;
}
.admin-actions {
display: flex;
align-items: center;
}
.wizard-notices-link {
position: relative; position: relative;
margin-right: 10px; cursor: pointer;
padding: .5em .65em;
background-color: rgba($primary-medium, 0.05);
border: 1.5px solid rgba($primary-medium, 0.5);
color: $primary-medium;
div > a { &:hover {
@include btn; color: $primary-medium;
color: var(--secondary) !important; }
background-color: var(--primary-medium);
&.active { svg {
background-color: var(--tertiary) !important; width: 15px;
color: var(--secondary) !important; height: 15px;
margin-right: 0.45em;
margin-bottom: 0.15em;
}
&.standard {
background-color: rgba($subscription_standard, 0.05);
border: 1.5px solid rgba($subscription_standard, 0.5);
color: $subscription_standard;
}
&.business {
background-color: $subscription_business;
border: 1.5px solid $subscription_business;
color: $secondary;
}
.d-icon {
margin-right: 0.75em;
}
}
.wizard-subscription-selector.select-kit.single-select {
.select-kit-row {
.texts {
display: flex;
align-items: center;
}
&.disabled {
background: var(--primary-low);
cursor: unset;
} }
} }
}
.active-notice-count { .subscription-label {
background-color: $danger; margin-left: 0.75em;
color: $secondary; padding-top: 0.25em;
border-radius: 50%; color: var(--pavilion-primary);
width: 18px; font-size: 0.75em;
height: 18px;
line-height: 18px;
text-align: center;
position: absolute;
top: -8px;
right: -8px;
font-size: 0.7em;
}
a.show-notice-message {
padding: 0.25em 0.5em;
color: var(--primary);
}
.wizard-notice,
.wizard-notice-row:not(.expired):not(.dismissed) {
&.info {
background-color: rgba($info, 0.1);
border: 1px solid rgba($info, 0.5);
}
&.warning,
&.connection-error {
background-color: rgba($warning, 0.1);
border: 1px solid rgba($warning, 0.5);
}
}
.notice-message {
position: relative;
.cooked-notice-message {
background-color: var(--secondary);
padding: 1em;
z-index: 1;
box-shadow: shadow("dropdown");
border-top: 1px solid var(--primary-low);
p {
margin: 0;
}
} }
} }

Datei anzeigen

@ -2,8 +2,13 @@ $pavilion_primary: #3c1c8c;
$pavilion_secondary: #ffffff; $pavilion_secondary: #ffffff;
$pavilion_warning: rgb(243, 163, 61); $pavilion_warning: rgb(243, 163, 61);
$subscription_standard: $pavilion_primary;
$subscription_business: #333;
:root { :root {
--pavilion-primary: #{$pavilion_primary}; --pavilion-primary: #{$pavilion_primary};
--pavilion-secondary: #{$pavilion_secondary}; --pavilion-secondary: #{$pavilion_secondary};
--pavilion-warning: #{$pavilion_warning}; --pavilion-warning: #{$pavilion_warning};
--subscription-standard: #{$subscription_standard};
--subscription-business: #{$subscription_business};
} }

Datei anzeigen

@ -47,7 +47,7 @@ en:
value: "Value" value: "Value"
profile: "profile" profile: "profile"
type: "Type" type: "Type"
none: "Make a selection" none: "Make a selection"
submission_key: 'submission key' submission_key: 'submission key'
param_key: 'param' param_key: 'param'
group: "Group" group: "Group"
@ -126,9 +126,9 @@ en:
hide: "Hide" hide: "Hide"
preview: "{{action}} Preview" preview: "{{action}} Preview"
popover: "{{action}} Fields" popover: "{{action}} Fields"
input: input:
conditional: conditional:
name: 'if' name: 'if'
output: 'then' output: 'then'
assignment: assignment:
@ -137,7 +137,7 @@ en:
name: 'map' name: 'map'
validation: validation:
name: 'ensure' name: 'ensure'
selector: selector:
label: label:
text: "text" text: "text"
@ -175,7 +175,7 @@ en:
dependent: "{{property}} is dependent on {{dependent}}" dependent: "{{property}} is dependent on {{dependent}}"
conflict: "{{type}} with {{property}} '{{value}}' already exists" conflict: "{{type}} with {{property}} '{{value}}' already exists"
after_time: "After time invalid" after_time: "After time invalid"
step: step:
header: "Steps" header: "Steps"
title: "Title" title: "Title"
@ -189,7 +189,7 @@ en:
force_final: force_final:
label: "Conditional Final Step" label: "Conditional Final Step"
description: "Display this step as the final step if conditions on later steps have not passed when the user reaches this step." description: "Display this step as the final step if conditions on later steps have not passed when the user reaches this step."
field: field:
header: "Fields" header: "Fields"
label: "Label" label: "Label"
@ -212,7 +212,7 @@ en:
prefill: "Prefill" prefill: "Prefill"
content: "Content" content: "Content"
tag_groups: "Tag Groups" tag_groups: "Tag Groups"
date_time_format: date_time_format:
label: "Format" label: "Format"
instructions: "<a href='https://momentjs.com/docs/#/displaying/format/' target='_blank'>Moment.js format</a>" instructions: "<a href='https://momentjs.com/docs/#/displaying/format/' target='_blank'>Moment.js format</a>"
validations: validations:
@ -229,7 +229,7 @@ en:
weeks: "Weeks" weeks: "Weeks"
months: "Months" months: "Months"
years: "Years" years: "Years"
type: type:
text: "Text" text: "Text"
textarea: Textarea textarea: Textarea
@ -248,7 +248,7 @@ en:
date: Date date: Date
time: Time time: Time
date_time: Date & Time date_time: Date & Time
connector: connector:
and: "and" and: "and"
or: "or" or: "or"
@ -262,7 +262,7 @@ en:
regex: '=~' regex: '=~'
association: '→' association: '→'
is: 'is' is: 'is'
action: action:
header: "Actions" header: "Actions"
include: "Include Fields" include: "Include Fields"
@ -270,8 +270,8 @@ en:
post: "Post" post: "Post"
topic_attr: "Topic Attribute" topic_attr: "Topic Attribute"
interpolate_fields: "Insert wizard fields using the field_id in w{}. Insert user fields using field key in u{}." interpolate_fields: "Insert wizard fields using the field_id in w{}. Insert user fields using field key in u{}."
run_after: run_after:
label: "Run After" label: "Run After"
wizard_completion: "Wizard Completion" wizard_completion: "Wizard Completion"
custom_fields: custom_fields:
@ -353,11 +353,11 @@ en:
messageable_level: Messageable Level messageable_level: Messageable Level
visibility_level: Visibility Level visibility_level: Visibility Level
members_visibility_level: Members Visibility Level members_visibility_level: Members Visibility Level
custom_field: custom_field:
nav_label: "Custom Fields" nav_label: "Custom Fields"
add: "Add" add: "Add"
external: external:
label: "from another plugin" label: "from another plugin"
title: "This custom field has been added by another plugin. You can use it in your wizards but you can't edit the field here." title: "This custom field has been added by another plugin. You can use it in your wizards but you can't edit the field here."
name: name:
@ -386,7 +386,7 @@ en:
basic_category: "Category" basic_category: "Category"
basic_group: "Group" basic_group: "Group"
post: "Post" post: "Post"
submissions: submissions:
nav_label: "Submissions" nav_label: "Submissions"
title: "{{name}} Submissions" title: "{{name}} Submissions"
@ -468,59 +468,22 @@ en:
subscription_container: subscription_container:
title: Subscriber Features title: Subscriber Features
subscribed: subscribed:
label: Subscribed label: Subscribed
title: You're subscribed and can use these features title: You're subscribed and can use these features
not_subscribed: not_subscribed:
label: Not Subscribed label: Not Subscribed
title: Subscribe to use these features title: Subscribe to use these features
subscription:
nav_label: Subscription
label: In subscription
additional_label: "Add subscription"
title: Custom Wizard Subscription
authorize: Authorize
authorized: Authorized
unauthorize: cancel
not_subscribed: You're not currently subscribed
subscription:
title:
standard: Standard Subscription
business: Business Subscription
status:
active: Active
inactive: Inactive
update: Update
last_updated: Last updated
notice:
plugin: Custom Wizard Plugin
message: Message
time: Time
status: Status
title: Title
dismiss:
label: Dismiss
title: Dismiss notice
dismiss_all:
label: Dismiss All
title: Dismiss all informational Custom Wizard notices
confirm: Are you sure you want to dismiss all informational Custom Wizard notices?
active: active
created_at: issued
updated_at: updated
expired_at: expired
dismissed_at: dismissed
type: type:
label: Type none:
info: Information label: Not Subscribed
warning: Warning title: There is no Custom Wizard subscription active on this forum.
connection_error: Connection Error business:
label: Business
notices: title: There is a Custom Wizard Business subscription active on this forum.
nav_label: Notices standard:
title: Plugin Notices label: Standard
title: There is a Custom Wizard Standard subscription active on this forum.
wizard_js: wizard_js:
group: group:
@ -636,7 +599,7 @@ en:
yourself_confirm: yourself_confirm:
title: "Did you forget to add recipients?" title: "Did you forget to add recipients?"
body: "Right now this message is only being sent to yourself!" body: "Right now this message is only being sent to yourself!"
realtime_validations: realtime_validations:
similar_topics: similar_topics:
insufficient_characters: "Type a minimum 5 characters to start looking for similar topics" insufficient_characters: "Type a minimum 5 characters to start looking for similar topics"

Datei anzeigen

@ -1,5 +1,5 @@
plugins: plugins:
custom_wizard_enabled: custom_wizard_enabled:
default: true default: true
client: true client: true
wizard_redirect_exclude_paths: wizard_redirect_exclude_paths:

Datei anzeigen

@ -84,7 +84,7 @@ class ::CustomWizard::CustomField
next next
end end
if attr == 'klass' && @subscription.requires_additional_subscription("custom_fields", "klass").include?(value) if attr == 'klass' && @subscription.includes?(:custom_field, :klass, value)
add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value)) add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value))
end end
@ -99,7 +99,7 @@ class ::CustomWizard::CustomField
add_error(I18n.t("#{i18n_key}.unsupported_type", type: value)) add_error(I18n.t("#{i18n_key}.unsupported_type", type: value))
end end
if attr == 'type' && @subscription.requires_additional_subscription("custom_fields", "type").include?(value) if attr == 'type' && @subscription.includes?(:custom_field, :type, value)
add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value)) add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value))
end end

Datei anzeigen

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

Datei anzeigen

@ -1,8 +1,170 @@
class CustomWizard::Subscription class CustomWizard::Subscription
STANDARD_PRODUCT_ID = 'prod_LNAGVAaIqDsHmB'
BUSINESS_PRODUCT_ID = 'prod_LNABQ50maBQ1pY'
def self.attributes
{
wizard: {
required: {
none: [],
standard: ['*'],
business: ['*']
},
permitted: {
none: [],
standard: ['*'],
business: ['*']
}
},
step: {
condition: {
none: [],
standard: ['*'],
business: ['*']
},
index: {
none: [],
standard: ['*'],
business: ['*']
},
required_data: {
none: [],
standard: ['*'],
business: ['*']
},
permitted_params: {
none: [],
standard: ['*'],
business: ['*']
}
},
field: {
condition: {
none: [],
standard: ['*'],
business: ['*']
},
index: {
none: [],
standard: ['*'],
business: ['*']
},
type: {
none: ['label', 'description', 'image', 'required', 'placeholder', 'file'],
standard: ['*'],
business: ['*']
},
prefill: {
standard: ['*'],
business: ['*']
},
content: {
none: [],
standard: ['*'],
business: ['*']
},
realtime_validations: {
none: [],
standard: ['*'],
business: ['*']
}
},
action: {
type: {
none: [],
standard: ['send_message', 'watch_categories', 'add_to_group'],
business: ['*']
}
},
custom_field: {
klass: {
none: ['topic', 'post'],
standard: ['topic', 'post'],
business: ['*']
},
type: {
none: ['string', 'boolean', 'integer'],
standard: ['string', 'boolean', 'integer'],
business: ['*']
}
}
}
end
def initialize
@subscription = find_subscription
end
def includes?(feature, attribute, value)
attributes = self.class.attributes[feature]
## Attribute is not part of a subscription
return true unless attributes.present? && attributes.key?(attribute)
values = attributes[attribute][type]
## Subscription type does not support the attribute.
return false if values.blank?
## Subscription type supports all values of the attribute.
return true if values === "*"
## Subscription type supports some values of the attributes.
values.include?(value)
end
def type
return :none unless subscribed?
return :standard if standard?
return :business if business?
end
def subscribed?
standard? || business?
end
def standard?
@subscription.product_id === STANDARD_PRODUCT_ID
end
def business?
@subscription.product_id === BUSINESS_PRODUCT_ID
end
def client_installed?
defined?(SubscriptionClient) == 'constant' && SubscriptionClient.class == Module
end
def find_subscription
subscription = nil
if client_installed?
subscription = SubscriptionClientSubscription.active
.where(product_id: [STANDARD_PRODUCT_ID, BUSINESS_PRODUCT_ID])
.order("product_id = '#{BUSINESS_PRODUCT_ID}' DESC")
.first
end
unless subscription
subscription = OpenStruct.new(product_id: nil)
end
subscription
end
def self.subscribed? def self.subscribed?
new.subscribed?
end
def self.business?
new.business?
end
def self.standard?
new.standard?
end end
def self.type def self.type
new.type
end end
end end

Datei anzeigen

@ -56,29 +56,10 @@ class CustomWizard::TemplateValidator
def self.subscription def self.subscription
{ {
wizard: { wizard: ['save_submissions', 'restart_on_revisit'],
save_submissions: 'false', step: ['condition', 'index', 'required_data', 'permitted_params'],
restart_on_revisit: 'true', field: ['condition', 'index'],
}, action: ['type']
step: {
condition: 'present',
index: 'conditional',
required_data: 'present',
permitted_params: 'present'
},
field: {
condition: 'present',
index: 'conditional'
},
action: {
type: %w[
send_message
add_to_group
create_category
create_group
send_to_api
]
}
} }
end end
@ -93,16 +74,8 @@ class CustomWizard::TemplateValidator
end end
def validate_subscription(object, type) def validate_subscription(object, type)
self.class.subscription[type].each do |property, subscription_type| self.class.subscription[type].each do |property|
val = object[property.to_s] if !@subscription.includes?(type, property, object[property])
is_subscription = (val != nil) && (
subscription_type === 'present' && val.present? ||
(['true', 'false'].include?(subscription_type) && cast_bool(val) == cast_bool(subscription_type)) ||
(subscription_type === 'conditional' && val.is_a?(Hash)) ||
(subscription_type.is_a?(Array) && subscription_type.include?(val))
)
if is_subscription && !@subscription.subscribed?
errors.add :base, I18n.t("wizard.validation.subscription", type: type.to_s, property: property) errors.add :base, I18n.t("wizard.validation.subscription", type: type.to_s, property: property)
end end
end end
@ -148,10 +121,6 @@ class CustomWizard::TemplateValidator
end end
end end
def cast_bool(val)
ActiveRecord::Type::Boolean.new.cast(val)
end
def validate_liquid_template(object, type) def validate_liquid_template(object, type)
%w[ %w[
description description

Datei anzeigen

@ -5,17 +5,13 @@
# authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George # authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George
# contact_emails: support@thepavilion.io # contact_emails: support@thepavilion.io
# url: https://github.com/paviliondev/discourse-custom-wizard # url: https://github.com/paviliondev/discourse-custom-wizard
# subscription_url: https://coop.pavilion.tech
gem 'liquid', '5.0.1', require: true gem 'liquid', '5.0.1', require: true
register_asset 'stylesheets/admin/admin.scss', :desktop register_asset 'stylesheets/admin/admin.scss', :desktop
enabled_site_setting :custom_wizard_enabled enabled_site_setting :custom_wizard_enabled
config = Rails.application.config
plugin_asset_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets"
config.assets.paths << "#{plugin_asset_path}/javascripts"
config.assets.paths << "#{plugin_asset_path}/stylesheets/wizard"
if Rails.env.production? if Rails.env.production?
config.assets.precompile += %w{ config.assets.precompile += %w{
wizard-custom-guest.js wizard-custom-guest.js
@ -59,6 +55,24 @@ class ::Sprockets::DirectiveProcessor
end end
end end
## Override necessary due to 'assets/javascripts/wizard', particularly its tests.
def each_globbed_asset
if @path
root_path = "#{File.dirname(@path)}/assets/javascripts/discourse"
Dir.glob(["#{root_path}/**/*"]).sort.each do |f|
f_str = f.to_s
if File.directory?(f)
yield [f, true]
elsif f_str.end_with?(".js.es6") || f_str.end_with?(".hbs") || f_str.end_with?(".hbr")
yield [f, false]
elsif transpile_js && f_str.end_with?(".js")
yield [f, false]
end
end
end
end
after_initialize do after_initialize do
%w[ %w[
../lib/custom_wizard/engine.rb ../lib/custom_wizard/engine.rb
@ -91,6 +105,7 @@ after_initialize do
../lib/custom_wizard/step_updater.rb ../lib/custom_wizard/step_updater.rb
../lib/custom_wizard/step.rb ../lib/custom_wizard/step.rb
../lib/custom_wizard/submission.rb ../lib/custom_wizard/submission.rb
../lib/custom_wizard/subscription.rb
../lib/custom_wizard/template.rb ../lib/custom_wizard/template.rb
../lib/custom_wizard/wizard.rb ../lib/custom_wizard/wizard.rb
../lib/custom_wizard/api/api.rb ../lib/custom_wizard/api/api.rb

Datei anzeigen

@ -1,159 +0,0 @@
# frozen_string_literal: true
require_relative '../../plugin_helper'
describe CustomWizard::Notice do
fab!(:user) { Fabricate(:user) }
let(:subscription_message) {
{
title: "Title of message about subscription",
message: "Message about subscription",
type: "info",
created_at: Time.now - 3.day,
expired_at: nil
}
}
let(:plugin_status) {
{
name: 'discourse-custom-wizard',
status: 'incompatible',
status_changed_at: Time.now - 3.day
}
}
context "subscription message" do
before do
freeze_time
stub_request(:get, described_class.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json)
described_class.update(skip_plugin: true)
end
it "converts subscription messages into notices" do
notice = described_class.list.first
expect(notice.type).to eq(described_class.types[:info])
expect(notice.message).to eq(subscription_message[:message])
expect(notice.created_at.to_datetime).to be_within(1.second).of (subscription_message[:created_at].to_datetime)
end
it "expires notice if subscription message is expired" do
subscription_message[:expired_at] = Time.now
stub_request(:get, described_class.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json)
described_class.update(skip_plugin: true)
notice = described_class.list(include_all: true).first
expect(notice.expired?).to eq(true)
end
it "dismisses informational subscription notices" do
notice = described_class.list(include_all: true).first
expect(notice.dismissed?).to eq(false)
notice.dismiss!
expect(notice.dismissed?).to eq(true)
end
it "dismisses all informational subscription notices" do
4.times do |index|
subscription_message[:title] += " #{index}"
stub_request(:get, described_class.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json)
described_class.update(skip_plugin: true)
end
expect(described_class.list.count).to eq(5)
described_class.dismiss_all
expect(described_class.list.count).to eq(0)
end
end
context "plugin status" do
before do
freeze_time
stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: plugin_status.to_json)
described_class.update(skip_subscription: true)
end
it "converts warning into notice" do
notice = described_class.list.first
expect(notice.type).to eq(described_class.types[:warning])
expect(notice.message).to eq(I18n.t("wizard.notice.compatibility_issue.message", domain: described_class.plugin_status_domain))
expect(notice.created_at.to_datetime).to be_within(1.second).of (plugin_status[:status_changed_at].to_datetime)
end
it "expires warning notices if status is recommended or compatible" do
plugin_status[:status] = 'compatible'
plugin_status[:status_changed_at] = Time.now
stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: plugin_status.to_json)
described_class.update(skip_subscription: true)
notice = described_class.list(type: described_class.types[:warning], include_all: true).first
expect(notice.expired?).to eq(true)
end
it "hides plugin status warnings" do
notice = described_class.list.first
expect(notice.hidden?).to eq(false)
notice.hide!
expect(notice.hidden?).to eq(true)
end
end
it "lists notices not expired more than a day ago" do
subscription_message[:expired_at] = Time.now - 8.hours
stub_request(:get, described_class.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json)
stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: plugin_status.to_json)
described_class.update
expect(described_class.list(include_all: true).length).to eq(2)
end
context "connection errors" do
before do
freeze_time
end
it "creates an error if connection to notice server fails" do
stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: plugin_status.to_json)
described_class.update(skip_subscription: true)
error = CustomWizard::Notice::ConnectionError.new(:plugin_status)
expect(error.current_error.present?).to eq(true)
end
it "only creates one connection error per type at a time" do
stub_request(:get, described_class.subscription_message_url).to_return(status: 400, body: { messages: [subscription_message] }.to_json)
stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: plugin_status.to_json)
5.times { described_class.update }
plugin_status_errors = CustomWizard::Notice::ConnectionError.new(:plugin_status)
subscription_message_errors = CustomWizard::Notice::ConnectionError.new(:subscription_message)
expect(plugin_status_errors.current_error[:count]).to eq(5)
expect(subscription_message_errors.current_error[:count]).to eq(5)
end
it "creates a connection error notice if connection errors reach limit" do
stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: plugin_status.to_json)
error = CustomWizard::Notice::ConnectionError.new(:plugin_status)
error.limit.times { described_class.update(skip_subscription: true) }
notice = described_class.list(type: described_class.types[:connection_error]).first
expect(error.current_error[:count]).to eq(error.limit)
expect(notice.type).to eq(described_class.types[:connection_error])
end
it "expires a connection error notice if connection succeeds" do
stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: plugin_status.to_json)
error = CustomWizard::Notice::ConnectionError.new(:plugin_status)
error.limit.times { described_class.update(skip_subscription: true) }
stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: plugin_status.to_json)
described_class.update(skip_subscription: true)
notice = described_class.list(type: described_class.types[:connection_error], include_all: true).first
expect(notice.type).to eq(described_class.types[:connection_error])
expect(notice.expired_at.present?).to eq(true)
end
end
end

Datei anzeigen

@ -1,125 +0,0 @@
# frozen_string_literal: true
require_relative '../../plugin_helper'
describe CustomWizard::Subscription do
fab!(:user) { Fabricate(:user) }
it "initializes subscription authentication and subscription" do
subscription = described_class.new
expect(subscription.authentication.class).to eq(CustomWizard::Subscription::Authentication)
expect(subscription.subscription.class).to eq(CustomWizard::Subscription::Subscription)
end
it "returns authorized and subscribed states" do
subscription = described_class.new
expect(subscription.authorized?).to eq(false)
expect(subscription.subscribed?).to eq(false)
end
context "subscription" do
before do
@subscription = described_class.new
end
it "updates valid subscriptions" do
stub_subscription_request(200, valid_subscription)
expect(@subscription.update).to eq(true)
expect(@subscription.subscribed?).to eq(true)
end
it "handles invalid subscriptions" do
stub_subscription_request(200, invalid_subscription)
expect(@subscription.update).to eq(false)
expect(@subscription.subscribed?).to eq(false)
end
it "handles subscription http errors" do
stub_subscription_request(404, {})
expect(@subscription.update).to eq(false)
expect(@subscription.subscribed?).to eq(false)
end
it "destroys subscriptions" do
stub_subscription_request(200, valid_subscription)
expect(@subscription.update).to eq(true)
expect(@subscription.destroy_subscription).to eq(true)
expect(@subscription.subscribed?).to eq(false)
end
it "has class aliases" do
authenticate_subscription
stub_subscription_request(200, valid_subscription)
expect(described_class.update).to eq(true)
expect(described_class.subscribed?).to eq(true)
end
end
context "authentication" do
before do
@subscription = described_class.new
user.update!(admin: true)
end
it "generates a valid authentication request url" do
request_id = SecureRandom.hex(32)
uri = URI(@subscription.authentication_url(user.id, request_id))
expect(uri.host).to eq(@subscription.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(@subscription.scope)
end
def generate_payload(request_id, user_id)
uri = URI(@subscription.authentication_url(user_id, request_id))
keys = @subscription.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(@subscription.authentication_response(request_id, payload)).to eq(true)
expect(@subscription.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(@subscription.authentication_response(request_id, payload)).to eq(false)
expect(@subscription.authorized?).to eq(false)
end
it "discards authentication response if request_id is invalid" do
payload = generate_payload(SecureRandom.hex(32), user.id)
expect(@subscription.authentication_response(SecureRandom.hex(32), payload)).to eq(false)
expect(@subscription.authorized?).to eq(false)
end
it "destroys authentication" do
request_id = SecureRandom.hex(32)
payload = generate_payload(request_id, user.id)
@subscription.authentication_response(request_id, payload)
expect(@subscription.destroy_authentication).to eq(true)
expect(@subscription.authorized?).to eq(false)
end
end
end

Datei anzeigen

@ -1,29 +0,0 @@
# frozen_string_literal: true
require_relative '../plugin_helper'
describe Jobs::CustomWizardUpdateNotices do
let(:subscription_message) {
{
message: "Message about subscription",
type: "info",
created_at: Time.now - 3.day,
expired_at: nil
}
}
let(:plugin_status) {
{
name: 'discourse-custom-wizard',
status: 'incompatible',
status_changed_at: Time.now - 3.day
}
}
it "updates the notices" do
stub_request(:get, CustomWizard::Notice.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json)
stub_request(:get, CustomWizard::Notice.plugin_status_url).to_return(status: 200, body: plugin_status.to_json)
described_class.new.execute
expect(CustomWizard::Notice.list.length).to eq(2)
end
end

Datei anzeigen

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

Datei anzeigen

@ -7,38 +7,3 @@ def get_wizard_fixture(path)
).read ).read
).with_indifferent_access ).with_indifferent_access
end end
def authenticate_subscription
CustomWizard::Subscription::Authentication.any_instance.stubs(:active?).returns(true)
end
def enable_subscription(type)
# CustomWizard::Subscription.new
CustomWizard::Subscription.any_instance.stubs(:subscribed?).returns(true)
CustomWizard::Subscription.any_instance.stubs(:type).returns(type)
end
def disable_subscription
CustomWizard::Subscription.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_subscription
sub = CustomWizard::Subscription.new
stub_request(:get, "https://#{sub.server}/subscription-server/user-subscriptions/#{sub.subscription_type}/#{sub.client_name}").to_return(status: status, body: { subscriptions: [subscription] }.to_json)
end

Datei anzeigen

@ -1,72 +0,0 @@
# frozen_string_literal: true
require_relative '../../../plugin_helper'
describe CustomWizard::AdminNoticeController do
fab!(:admin_user) { Fabricate(:user, admin: true) }
let(:subscription_message_notice) {
{
title: "Title of message about subscription",
message: "Message about subscription",
type: 0,
created_at: Time.now.iso8601(3),
expired_at: nil
}
}
let(:plugin_status_notice) {
{
title: "The Custom Wizard Plugin is incompatibile with the latest version of Discourse.",
message: "Please check the Custom Wizard Plugin status on [localhost:3000](http://localhost:3000) before updating Discourse.",
type: 1,
archetype: 1,
created_at: Time.now.iso8601(3),
expired_at: nil
}
}
before do
sign_in(admin_user)
end
it "lists notices" do
@notice = CustomWizard::Notice.new(subscription_message_notice)
@notice.save
get "/admin/wizards/notice.json"
expect(response.status).to eq(200)
expect(response.parsed_body.length).to eq(1)
end
it "dismisses notices" do
@notice = CustomWizard::Notice.new(subscription_message_notice)
@notice.save
put "/admin/wizards/notice/#{@notice.id}/dismiss.json"
expect(response.status).to eq(200)
updated = CustomWizard::Notice.find(@notice.id)
expect(updated.dismissed?).to eq(true)
end
it "dismisses all notices" do
5.times do |index|
subscription_message_notice[:title] += " #{index}"
@notice = CustomWizard::Notice.new(subscription_message_notice)
@notice.save
end
put "/admin/wizards/notice/dismiss.json"
expect(response.status).to eq(200)
expect(CustomWizard::Notice.list.size).to eq(0)
end
it "hides notices" do
@notice = CustomWizard::Notice.new(plugin_status_notice)
@notice.save
put "/admin/wizards/notice/#{@notice.id}/hide.json"
expect(response.status).to eq(200)
updated = CustomWizard::Notice.find(@notice.id)
expect(updated.hidden?).to eq(true)
end
end

Datei anzeigen

@ -1,71 +0,0 @@
# frozen_string_literal: true
require_relative '../../../plugin_helper'
describe CustomWizard::AdminSubscriptionController do
fab!(:admin_user) { Fabricate(:user, admin: true) }
def generate_payload(request_id, user_id)
uri = URI(@subscription.authentication_url(user_id, request_id))
keys = @subscription.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
@subscription = CustomWizard::Subscription.new
sign_in(admin_user)
end
it "#index" do
get "/admin/wizards/subscription.json"
expect(response.parsed_body['server']).to eq(@subscription.server)
expect(response.parsed_body['authentication'].deep_symbolize_keys).to eq(CustomWizard::Subscription::AuthenticationSerializer.new(@subscription.authentication, root: false).as_json)
expect(response.parsed_body['subscription'].deep_symbolize_keys).to eq(CustomWizard::Subscription::SubscriptionSerializer.new(@subscription.subscription, root: false).as_json)
end
it "#authorize" do
get "/admin/wizards/subscription/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)
@subscription.authentication_response(request_id, payload)
delete "/admin/wizards/subscription/authorize.json"
expect(response.status).to eq(200)
expect(CustomWizard::Subscription.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/subscription/authorize/callback", params: { payload: payload }
expect(response).to redirect_to("/admin/wizards/subscription")
expect(CustomWizard::Subscription.subscribed?).to eq(true)
end
it "updates the subscription" do
authenticate_subscription
post "/admin/wizards/subscription.json"
expect(response.status).to eq(200)
expect(CustomWizard::Subscription.subscribed?).to eq(true)
end
end
end

Datei anzeigen

@ -1,22 +0,0 @@
# frozen_string_literal: true
require_relative '../../plugin_helper'
describe CustomWizard::NoticeSerializer do
before do
@notice = CustomWizard::Notice.new(
message: "Message about subscription",
type: "info",
created_at: Time.now - 3.day,
expired_at: nil
)
@notice.save
end
it 'should return notice attributes' do
serialized_notice = described_class.new(@notice)
expect(serialized_notice.message).to eq(@notice.message)
expect(serialized_notice.type).to eq(CustomWizard::Notice.types.key(@notice.type))
expect(serialized_notice.dismissable).to eq(true)
end
end

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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