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
def index
subcription = CustomWizard::Subscription.new
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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -3,29 +3,6 @@ import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { alias, equal, or } from "@ember/object/computed";
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({
tagName: "tr",
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")
clearSerializersWhenClassChanges() {
this.set("field.serializers", null);

Datei anzeigen

@ -1,8 +1,6 @@
import { default as discourseComputed } from "discourse-common/utils/decorators";
import wizardSchema, {
requiringAdditionalSubscription,
subscriptionLevel,
} from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
import wizardSchema from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
import { subscriptionSelectKitContent } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-subscription";
import { empty, equal, or } from "@ember/object/computed";
import { notificationLevels, selectKitContent } from "../lib/wizard";
import { computed } from "@ember/object";
@ -97,22 +95,8 @@ export default Component.extend(UndoChanges, {
return apis.find((a) => a.name === api).endpoints;
},
@discourseComputed("subscription")
actionTypes(subscription) {
let unsubscribedActions = requiringAdditionalSubscription(
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;
}, []);
@discourseComputed
actionTypes() {
return subscriptionSelectKitContent("action", "types");
},
});

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 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"],
selectKitOptions: {
@ -13,6 +28,38 @@ export default SingleSelectComponent.extend({
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() {
return "wizard-subscription-selector/wizard-subscription-selector-row";
},

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -1,26 +1,7 @@
import Controller, { inject as controller } from "@ember/controller";
import { isPresent } from "@ember/utils";
import { A } from "@ember/array";
import Controller from "@ember/controller";
import { equal } from "@ember/object/computed";
export default Controller.extend({
adminWizardsNotices: controller(),
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();
}
});
},
businessSubscription: equal('subscriptionType', 'business'),
standardSubscription: equal('subscriptionType', 'standard')
});

Datei anzeigen

@ -58,16 +58,6 @@ export default {
path: "/manager",
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"],
};
const subscription_levels = {
standard: {
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: [],
},
},
};
field.type = Object.keys(field.types);
action.type = Object.keys(action.types);
const wizardSchema = {
wizard,
step,
field,
custom_field,
action,
subscription_levels,
action
};
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) {
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) {
const customFields = A(model.custom_fields || []);
const subscribed = model.subscribed;
const subscription = model.subscription;
controller.setProperties({
customFields,
subscribed,
subscription,
customFields
});
},
});

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -2,13 +2,14 @@
{{nav-item route="adminWizardsWizard" label="admin.wizard.nav_label"}}
{{nav-item route="adminWizardsCustomFields" label="admin.wizard.custom_field.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"}}
{{/if}}
{{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}}
{{nav-item route="adminWizardsManager" label="admin.wizard.manager.nav_label"}}
<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"}}>
{{d-icon "far-life-ring"}}{{i18n "admin.wizard.support_button.label"}}
</a>

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -25,6 +25,14 @@ $error: #ef1700;
}
}
.admin-wizards .admin-actions {
display: flex;
.btn-pavilion-support {
margin-left: 10px;
}
}
.wizard-message {
background-color: var(--primary-low);
width: 100%;
@ -156,6 +164,16 @@ $error: #ef1700;
@extend .wizard-settings-group;
}
.admin-wizard-container.settings {
.wizard-settings {
.wizard-subscription-container {
[class~="setting"] {
margin-bottom: 0;
}
}
}
}
.wizard-custom-field {
background: transparent;
background-color: var(--primary-very-low);
@ -802,84 +820,6 @@ $error: #ef1700;
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 {
background: var(--pavilion-primary);
color: var(--pavilion-secondary);
@ -899,7 +839,7 @@ $error: #ef1700;
}
}
.subscription-container {
.wizard-subscription-container {
width: 100%;
padding: 1em;
background-color: rgba($pavilion_primary, 0.1);
@ -907,158 +847,79 @@ $error: #ef1700;
.subscription-header {
display: flex;
justify-content: space-between;
margin-bottom: 1em;
margin-bottom: .25em;
h3 {
margin: 0;
}
a {
color: var(--pavilion-primary);
color: var(--primary);
}
}
&:not(.subscribed) .subscription-settings {
filter: blur(1px);
pointer-events: none;
}
}
.admin-wizards-notices {
.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 {
.wizard-subscription-badge {
display: inline-flex;
align-items: center;
min-height: 25px;
max-height: 34px;
box-sizing: border-box;
color: var(--primary) !important;
}
.admin-actions {
display: flex;
align-items: center;
}
.wizard-notices-link {
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 {
@include btn;
color: var(--secondary) !important;
background-color: var(--primary-medium);
&:hover {
color: $primary-medium;
}
&.active {
background-color: var(--tertiary) !important;
color: var(--secondary) !important;
svg {
width: 15px;
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 {
background-color: $danger;
color: $secondary;
border-radius: 50%;
width: 18px;
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;
}
.subscription-label {
margin-left: 0.75em;
padding-top: 0.25em;
color: var(--pavilion-primary);
font-size: 0.75em;
}
}

Datei anzeigen

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

Datei anzeigen

@ -47,7 +47,7 @@ en:
value: "Value"
profile: "profile"
type: "Type"
none: "Make a selection"
none: "Make a selection"
submission_key: 'submission key'
param_key: 'param'
group: "Group"
@ -126,9 +126,9 @@ en:
hide: "Hide"
preview: "{{action}} Preview"
popover: "{{action}} Fields"
input:
conditional:
conditional:
name: 'if'
output: 'then'
assignment:
@ -137,7 +137,7 @@ en:
name: 'map'
validation:
name: 'ensure'
selector:
label:
text: "text"
@ -175,7 +175,7 @@ en:
dependent: "{{property}} is dependent on {{dependent}}"
conflict: "{{type}} with {{property}} '{{value}}' already exists"
after_time: "After time invalid"
step:
header: "Steps"
title: "Title"
@ -189,7 +189,7 @@ en:
force_final:
label: "Conditional Final Step"
description: "Display this step as the final step if conditions on later steps have not passed when the user reaches this step."
field:
header: "Fields"
label: "Label"
@ -212,7 +212,7 @@ en:
prefill: "Prefill"
content: "Content"
tag_groups: "Tag Groups"
date_time_format:
date_time_format:
label: "Format"
instructions: "<a href='https://momentjs.com/docs/#/displaying/format/' target='_blank'>Moment.js format</a>"
validations:
@ -229,7 +229,7 @@ en:
weeks: "Weeks"
months: "Months"
years: "Years"
type:
text: "Text"
textarea: Textarea
@ -248,7 +248,7 @@ en:
date: Date
time: Time
date_time: Date & Time
connector:
and: "and"
or: "or"
@ -262,7 +262,7 @@ en:
regex: '=~'
association: '→'
is: 'is'
action:
header: "Actions"
include: "Include Fields"
@ -270,8 +270,8 @@ en:
post: "Post"
topic_attr: "Topic Attribute"
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"
wizard_completion: "Wizard Completion"
custom_fields:
@ -353,11 +353,11 @@ en:
messageable_level: Messageable Level
visibility_level: Visibility Level
members_visibility_level: Members Visibility Level
custom_field:
nav_label: "Custom Fields"
add: "Add"
external:
external:
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."
name:
@ -386,7 +386,7 @@ en:
basic_category: "Category"
basic_group: "Group"
post: "Post"
submissions:
nav_label: "Submissions"
title: "{{name}} Submissions"
@ -468,59 +468,22 @@ en:
subscription_container:
title: Subscriber Features
subscribed:
subscribed:
label: Subscribed
title: You're subscribed and can use these features
not_subscribed:
label: Not Subscribed
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:
label: Type
info: Information
warning: Warning
connection_error: Connection Error
notices:
nav_label: Notices
title: Plugin Notices
none:
label: Not Subscribed
title: There is no Custom Wizard subscription active on this forum.
business:
label: Business
title: There is a Custom Wizard Business subscription active on this forum.
standard:
label: Standard
title: There is a Custom Wizard Standard subscription active on this forum.
wizard_js:
group:
@ -636,7 +599,7 @@ en:
yourself_confirm:
title: "Did you forget to add recipients?"
body: "Right now this message is only being sent to yourself!"
realtime_validations:
similar_topics:
insufficient_characters: "Type a minimum 5 characters to start looking for similar topics"

Datei anzeigen

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

Datei anzeigen

@ -84,7 +84,7 @@ class ::CustomWizard::CustomField
next
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))
end
@ -99,7 +99,7 @@ class ::CustomWizard::CustomField
add_error(I18n.t("#{i18n_key}.unsupported_type", type: value))
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))
end

Datei anzeigen

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

Datei anzeigen

@ -1,8 +1,170 @@
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?
new.subscribed?
end
def self.business?
new.business?
end
def self.standard?
new.standard?
end
def self.type
new.type
end
end

Datei anzeigen

@ -56,29 +56,10 @@ class CustomWizard::TemplateValidator
def self.subscription
{
wizard: {
save_submissions: 'false',
restart_on_revisit: 'true',
},
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
]
}
wizard: ['save_submissions', 'restart_on_revisit'],
step: ['condition', 'index', 'required_data', 'permitted_params'],
field: ['condition', 'index'],
action: ['type']
}
end
@ -93,16 +74,8 @@ class CustomWizard::TemplateValidator
end
def validate_subscription(object, type)
self.class.subscription[type].each do |property, subscription_type|
val = object[property.to_s]
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?
self.class.subscription[type].each do |property|
if !@subscription.includes?(type, property, object[property])
errors.add :base, I18n.t("wizard.validation.subscription", type: type.to_s, property: property)
end
end
@ -148,10 +121,6 @@ class CustomWizard::TemplateValidator
end
end
def cast_bool(val)
ActiveRecord::Type::Boolean.new.cast(val)
end
def validate_liquid_template(object, type)
%w[
description

Datei anzeigen

@ -5,17 +5,13 @@
# authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George
# contact_emails: support@thepavilion.io
# url: https://github.com/paviliondev/discourse-custom-wizard
# subscription_url: https://coop.pavilion.tech
gem 'liquid', '5.0.1', require: true
register_asset 'stylesheets/admin/admin.scss', :desktop
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?
config.assets.precompile += %w{
wizard-custom-guest.js
@ -59,6 +55,24 @@ class ::Sprockets::DirectiveProcessor
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
%w[
../lib/custom_wizard/engine.rb
@ -91,6 +105,7 @@ after_initialize do
../lib/custom_wizard/step_updater.rb
../lib/custom_wizard/step.rb
../lib/custom_wizard/submission.rb
../lib/custom_wizard/subscription.rb
../lib/custom_wizard/template.rb
../lib/custom_wizard/wizard.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
).with_indifferent_access
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