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

Merge pull request #192 from paviliondev/remove_subs_and_notices

Remove subs and notices
Dieser Commit ist enthalten in:
Angus McLeod 2022-03-29 16:49:21 +02:00 committet von GitHub
Commit c56e5dcbf8
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
100 geänderte Dateien mit 828 neuen und 3100 gelöschten Zeilen

Datei anzeigen

@ -3,17 +3,12 @@ class CustomWizard::AdminController < ::Admin::AdminController
before_action :ensure_admin
def index
subcription = CustomWizard::Subscription.new
render_json_dump(
#TODO replace with appropriate static?
api_section: ["business"].include?(CustomWizard::Subscription.type),
active_notice_count: CustomWizard::Notice.active_count,
featured_notices: ActiveModel::ArraySerializer.new(
CustomWizard::Notice.list(
type: CustomWizard::Notice.types[:info],
archetype: CustomWizard::Notice.archetypes[:subscription_message]
),
each_serializer: CustomWizard::NoticeSerializer
)
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

@ -1,68 +0,0 @@
# frozen_string_literal: true
class CustomWizard::AdminNoticeController < CustomWizard::AdminController
before_action :find_notice, only: [:dismiss, :hide]
def index
type = params[:type]
archetype = params[:archtype]
page = params[:page].to_i
include_all = ActiveRecord::Type::Boolean.new.cast(params[:include_all])
visible = ActiveRecord::Type::Boolean.new.cast(params[:visible])
if type
if type.is_a?(Array)
type = type.map { |t| CustomWizard::Notice.types[t.to_sym] }
else
type = CustomWizard::Notice.types[type.to_sym]
end
end
if archetype
if archetype.is_a?(Array)
archetype = archetype.map { |t| CustomWizard::Notice.archetypes[archetype.to_sym] }
else
archetype = CustomWizard::Notice.archetypes[archetype.to_sym]
end
end
notices = CustomWizard::Notice.list(
include_all: include_all,
page: page,
type: type,
archetype: archetype,
visible: visible
)
render_serialized(notices, CustomWizard::NoticeSerializer, root: :notices)
end
def dismiss
if @notice.dismissable? && @notice.dismiss!
render json: success_json.merge(dismissed_at: @notice.dismissed_at)
else
render json: failed_json
end
end
def hide
if @notice.can_hide? && @notice.hide!
render json: success_json.merge(hidden_at: @notice.hidden_at)
else
render json: failed_json
end
end
def dismiss_all
if CustomWizard::Notice.dismiss_all
render json: success_json
else
render json: failed_json
end
end
def find_notice
@notice = CustomWizard::Notice.find(params[:notice_id])
raise Discourse::InvalidParameters.new(:notice_id) unless @notice
end
end

Datei anzeigen

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

@ -11,7 +11,6 @@ class CustomWizard::WizardController < ::ActionController::Base
before_action :preload_wizard_json
before_action :ensure_plugin_enabled
before_action :update_subscription, only: [:index]
before_action :ensure_logged_in, only: [:skip]
helper_method :wizard_page_title
@ -45,7 +44,7 @@ class CustomWizard::WizardController < ::ActionController::Base
return render json: { error: I18n.t('wizard.no_skip') }
end
result = success_json
result = { success: 'OK' }
if current_user && wizard.can_access?
if redirect_to = wizard.current_submission&.redirect_to
@ -123,8 +122,4 @@ class CustomWizard::WizardController < ::ActionController::Base
redirect_to path("/")
end
end
def update_subscription
CustomWizard::Subscription.update
end
end

Datei anzeigen

@ -1,9 +0,0 @@
# frozen_string_literal: true
class Jobs::CustomWizardUpdateNotices < ::Jobs::Scheduled
every 5.minutes
def execute(args = {})
CustomWizard::Notice.update
end
end

Datei anzeigen

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

Datei anzeigen

@ -1,33 +0,0 @@
# frozen_string_literal: true
class CustomWizard::NoticeSerializer < ApplicationSerializer
attributes :id,
:title,
:message,
:type,
:archetype,
:created_at,
:expired_at,
:updated_at,
:dismissed_at,
:retrieved_at,
:hidden_at,
:dismissable,
:can_hide
def dismissable
object.dismissable?
end
def can_hide
object.can_hide?
end
def type
CustomWizard::Notice.types.key(object.type)
end
def messsage
PrettyText.cook(object.message)
end
end

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -1,6 +0,0 @@
# frozen_string_literal: true
class CustomWizard::SubscriptionSerializer < ApplicationSerializer
attributes :server
has_one :authentication, serializer: CustomWizard::Subscription::AuthenticationSerializer, embed: :objects
has_one :subscription, serializer: CustomWizard::Subscription::SubscriptionSerializer, embed: :objects
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,5 @@
import { default as discourseComputed } from "discourse-common/utils/decorators";
import wizardSchema, {
requiringAdditionalSubscription,
subscriptionLevel,
} 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 +94,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

@ -1,19 +0,0 @@
import Component from "@ember/component";
import NoticeMessage from "../mixins/notice-message";
export default Component.extend(NoticeMessage, {
tagName: "tr",
attributeBindings: ["notice.id:data-notice-id"],
classNameBindings: [
":wizard-notice-row",
"notice.typeClass",
"notice.expired:expired",
"notice.dismissed:dismissed",
],
actions: {
dismiss() {
this.notice.dismiss();
},
},
});

Datei anzeigen

@ -1,29 +0,0 @@
import Component from "@ember/component";
import NoticeMessage from "../mixins/notice-message";
export default Component.extend(NoticeMessage, {
attributeBindings: ["notice.id:data-notice-id"],
classNameBindings: [
":wizard-notice",
"notice.typeClass",
"notice.dismissed:dismissed",
"notice.expired:expired",
"notice.hidden:hidden",
],
actions: {
dismiss() {
this.set("dismissing", true);
this.notice.dismiss().then(() => {
this.set("dismissing", false);
});
},
hide() {
this.set("hiding", true);
this.notice.hide().then(() => {
this.set("hiding", false);
});
},
},
});

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

@ -1,8 +1,9 @@
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
import Subscription from "../mixins/subscription";
export default Component.extend({
classNameBindings: [":subscription-container", "subscribed"],
export default Component.extend(Subscription, {
classNameBindings: [":wizard-subscription-container", "subscribed"],
@discourseComputed("subscribed")
subscribedIcon(subscribed) {

Datei anzeigen

@ -1,6 +1,22 @@
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";
import I18n from "I18n";
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 +29,47 @@ 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

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

Datei anzeigen

@ -1,5 +0,0 @@
{{#if notices}}
{{#each notices as |notice|}}
{{wizard-notice notice=notice showPlugin=true}}
{{/each}}
{{/if}}

Datei anzeigen

@ -1,19 +0,0 @@
import { getOwner } from "discourse-common/lib/get-owner";
export default {
shouldRender(attrs, ctx) {
return ctx.siteSettings.wizard_critical_notices_on_dashboard;
},
setupComponent(attrs, component) {
const controller = getOwner(this).lookup("controller:admin-dashboard");
component.set("notices", controller.get("customWizardCriticalNotices"));
controller.addObserver("customWizardCriticalNotices.[]", () => {
if (this._state === "destroying") {
return;
}
component.set("notices", controller.get("customWizardCriticalNotices"));
});
},
};

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

@ -1,68 +0,0 @@
import Controller from "@ember/controller";
import CustomWizardNotice from "../models/custom-wizard-notice";
import discourseComputed from "discourse-common/utils/decorators";
import { notEmpty } from "@ember/object/computed";
import { A } from "@ember/array";
import I18n from "I18n";
export default Controller.extend({
messageUrl: "https://thepavilion.io/t/3652",
messageKey: "info",
messageIcon: "info-circle",
messageClass: "info",
hasNotices: notEmpty("notices"),
page: 0,
loadingMore: false,
canLoadMore: true,
@discourseComputed("notices.[]", "notices.@each.dismissed")
allDismisssed(notices) {
return notices.every((n) => !n.canDismiss || n.dismissed);
},
loadMoreNotices() {
if (!this.canLoadMore) {
return;
}
const page = this.get("page");
this.set("loadingMore", true);
CustomWizardNotice.list({ page, include_all: true })
.then((result) => {
if (result.notices.length === 0) {
this.set("canLoadMore", false);
return;
}
this.get("notices").pushObjects(
A(result.notices.map((notice) => CustomWizardNotice.create(notice)))
);
})
.finally(() => this.set("loadingMore", false));
},
actions: {
loadMore() {
if (this.canLoadMore) {
this.set("page", this.page + 1);
this.loadMoreNotices();
}
},
dismissAll() {
bootbox.confirm(
I18n.t("admin.wizard.notice.dismiss_all.confirm"),
I18n.t("no_value"),
I18n.t("yes_value"),
(result) => {
if (result) {
this.set("loadingMore", true);
CustomWizardNotice.dismissAll().finally(() =>
this.set("loadingMore", false)
);
}
}
);
},
},
});

Datei anzeigen

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

Datei anzeigen

@ -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

@ -1,43 +0,0 @@
import { autoUpdatingRelativeAge } from "discourse/lib/formatter";
import { iconHTML } from "discourse-common/lib/icon-library";
import I18n from "I18n";
import { registerUnbound } from "discourse-common/lib/helpers";
import { htmlSafe } from "@ember/template";
registerUnbound("notice-badge", function (attrs) {
let tag = attrs.url ? "a" : "div";
let attrStr = "";
if (attrs.title) {
attrStr += `title='${I18n.t(attrs.title)}'`;
}
if (attrs.url) {
attrStr += `href='${attrs.url}'`;
}
let html = `<${tag} class="${
attrs.class ? `${attrs.class} ` : ""
}notice-badge" ${attrStr}>`;
if (attrs.icon) {
html += iconHTML(attrs.icon);
}
if (attrs.label) {
if (attrs.icon) {
html += "&nbsp;";
}
html += `<span>${I18n.t(attrs.label)}</span>`;
}
if (attrs.date) {
if (attrs.icon || attrs.label) {
html += "&nbsp;";
}
let dateAttrs = {};
if (attrs.leaveAgo) {
dateAttrs = {
format: "medium",
leaveAgo: true,
};
}
html += autoUpdatingRelativeAge(new Date(attrs.date), dateAttrs);
}
html += `</${tag}>`;
return htmlSafe(html);
});

Datei anzeigen

@ -1,8 +1,5 @@
import DiscourseURL from "discourse/lib/url";
import { withPluginApi } from "discourse/lib/plugin-api";
import CustomWizardNotice from "../models/custom-wizard-notice";
import { isPresent } from "@ember/utils";
import { A } from "@ember/array";
import getUrl from "discourse-common/lib/get-url";
export default {
@ -23,50 +20,6 @@ export default {
};
withPluginApi("0.8.36", (api) => {
api.modifyClass("route:admin-dashboard", {
pluginId: "custom-wizard",
setupController(controller) {
this._super(...arguments);
controller.loadCriticalNotices();
controller.subscribe();
},
});
api.modifyClass("controller:admin-dashboard", {
pluginId: "custom-wizard",
criticalNotices: A(),
unsubscribe() {
this.messageBus.unsubscribe("/custom-wizard/notices");
},
subscribe() {
this.unsubscribe();
this.messageBus.subscribe("/custom-wizard/notices", (data) => {
if (isPresent(data.active_notice_count)) {
this.loadCriticalNotices();
}
});
},
loadCriticalNotices() {
CustomWizardNotice.list({
type: ["connection_error", "warning"],
archetype: "plugin_status",
visible: true,
}).then((result) => {
if (result.notices && result.notices.length) {
const criticalNotices = A(
result.notices.map((n) => CustomWizardNotice.create(n))
);
this.set("customWizardCriticalNotices", criticalNotices);
}
});
},
});
api.modifyClass("component:d-navigation", {
pluginId: "custom-wizard",
actions: {

Datei anzeigen

@ -203,23 +203,8 @@ 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,
@ -227,77 +212,8 @@ const wizardSchema = {
field,
custom_field,
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) {
wizardSchema.field.types = types;
}

Datei anzeigen

@ -0,0 +1,22 @@
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

@ -1,68 +0,0 @@
import Mixin from "@ember/object/mixin";
import { bind, scheduleOnce } from "@ember/runloop";
import { cookAsync } from "discourse/lib/text";
import { createPopper } from "@popperjs/core";
export default Mixin.create({
showCookedMessage: false,
didReceiveAttrs() {
const message = this.notice.message;
cookAsync(message).then((cooked) => {
this.set("cookedMessage", cooked);
});
},
createMessageModal() {
let container = this.element.querySelector(".notice-message");
let modal = this.element.querySelector(".cooked-notice-message");
this._popper = createPopper(container, modal, {
strategy: "absolute",
placement: "bottom-start",
modifiers: [
{
name: "preventOverflow",
},
{
name: "offset",
options: {
offset: [0, 5],
},
},
],
});
},
didInsertElement() {
$(document).on("click", bind(this, this.documentClick));
},
willDestroyElement() {
$(document).off("click", bind(this, this.documentClick));
},
documentClick(event) {
if (this._state === "destroying") {
return;
}
if (
!event.target.closest(
`[data-notice-id="${this.notice.id}"] .notice-message`
)
) {
this.set("showCookedMessage", false);
}
},
actions: {
toggleCookedMessage() {
this.toggleProperty("showCookedMessage");
if (this.showCookedMessage) {
scheduleOnce("afterRender", this, this.createMessageModal);
}
},
},
});

Datei anzeigen

@ -0,0 +1,30 @@
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

@ -1,74 +0,0 @@
import EmberObject from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { and, not, notEmpty } from "@ember/object/computed";
import { dasherize } from "@ember/string";
import I18n from "I18n";
const CustomWizardNotice = EmberObject.extend({
expired: notEmpty("expired_at"),
dismissed: notEmpty("dismissed_at"),
hidden: notEmpty("hidden_at"),
notHidden: not("hidden"),
notDismissed: not("dismissed"),
canDismiss: and("dismissable", "notDismissed"),
canHide: and("can_hide", "notHidden"),
@discourseComputed("type")
typeClass(type) {
return dasherize(type);
},
@discourseComputed("type")
typeLabel(type) {
return I18n.t(`admin.wizard.notice.type.${type}`);
},
dismiss() {
if (!this.get("canDismiss")) {
return;
}
return ajax(`/admin/wizards/notice/${this.get("id")}/dismiss`, {
type: "PUT",
})
.then((result) => {
if (result.success) {
this.set("dismissed_at", result.dismissed_at);
}
})
.catch(popupAjaxError);
},
hide() {
if (!this.get("canHide")) {
return;
}
return ajax(`/admin/wizards/notice/${this.get("id")}/hide`, { type: "PUT" })
.then((result) => {
if (result.success) {
this.set("hidden_at", result.hidden_at);
}
})
.catch(popupAjaxError);
},
});
CustomWizardNotice.reopenClass({
list(data = {}) {
return ajax("/admin/wizards/notice", {
type: "GET",
data,
}).catch(popupAjaxError);
},
dismissAll() {
return ajax("/admin/wizards/notice/dismiss", {
type: "PUT",
}).catch(popupAjaxError);
},
});
export default CustomWizardNotice;

Datei anzeigen

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

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,
});
},
});

Datei anzeigen

@ -1,17 +0,0 @@
import CustomWizardNotice from "../models/custom-wizard-notice";
import DiscourseRoute from "discourse/routes/discourse";
import { A } from "@ember/array";
export default DiscourseRoute.extend({
model() {
return CustomWizardNotice.list({ include_all: true });
},
setupController(controller, model) {
controller.setProperties({
notices: A(
model.notices.map((notice) => CustomWizardNotice.create(notice))
),
});
},
});

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -7,16 +7,12 @@ 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) {

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

@ -1,49 +0,0 @@
<div class="admin-wizard-controls">
<h3>{{i18n "admin.wizard.notices.title"}}</h3>
<div class="buttons">
{{d-button
label="admin.wizard.notice.dismiss_all.label"
title="admin.wizard.notice.dismiss_all.title"
action=(action "dismissAll")
disabled=allDismisssed
icon="check"}}
</div>
</div>
{{wizard-message
key=messageKey
url=messageUrl
type=messageType
opts=messageOpts
items=messageItems
loading=loading
component="notices"}}
<div class="wizard-table">
{{#load-more selector=".wizard-table tr" action=(action "loadMore")}}
{{#if hasNotices}}
<table>
<thead>
<tr>
<th>{{I18n "admin.wizard.notice.time"}}</th>
<th>{{I18n "admin.wizard.notice.type.label"}}</th>
<th>{{I18n "admin.wizard.notice.title"}}</th>
<th>{{I18n "admin.wizard.notice.status"}}</th>
</tr>
</thead>
<tbody>
{{#each notices as |notice|}}
{{wizard-notice-row notice=notice}}
{{/each}}
</tbody>
</table>
{{else}}
{{#unless loadingMore}}
<p>{{i18n "search.no_results"}}</p>
{{/unless}}
{{/if}}
{{conditional-loading-spinner condition=loadingMore}}
{{/load-more}}
</div>

Datei anzeigen

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

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,20 +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"}}
{{nav-item route="adminWizardsSubscription" label="admin.wizard.subscription.nav_label"}}
<div class="admin-actions">
<div class="wizard-notices-link">
{{nav-item tagName="div" route="adminWizardsNotices" label="admin.wizard.notices.nav_label"}}
{{#if activeNoticeCount}}
<span class="active-notice-count">{{activeNoticeCount}}</span>
{{/if}}
</div>
{{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

@ -1,30 +0,0 @@
<td class="small">
{{#if notice.updated_at}}
{{notice-badge class="notice-updated-at" date=notice.updated_at label="admin.wizard.notice.updated_at" leaveAgo=true}}
{{else}}
{{notice-badge class="notice-created-at" date=notice.created_at label="admin.wizard.notice.created_at" leaveAgo=true}}
{{/if}}
</td>
<td>{{notice.typeLabel}}</td>
<td class="notice-message">
<a role="button" {{action "toggleCookedMessage"}} class="show-notice-message">{{notice.title}}</a>
{{#if showCookedMessage}}
<span class="cooked-notice-message">{{cookedMessage}}</span>
{{/if}}
</td>
<td>
{{#if notice.canDismiss}}
{{d-button
action="dismiss"
label="admin.wizard.notice.dismiss.label"
title="admin.wizard.notice.dismiss.title"
class="btn-dismiss"
icon="check"}}
{{else if notice.dismissed}}
<span>{{i18n "admin.wizard.notice.dismissed_at"}}&nbsp;{{format-date notice.dismissed_at leaveAgo="true"}}</span>
{{else if notice.expired}}
<span>{{i18n "admin.wizard.notice.expired_at"}}&nbsp;{{format-date notice.expired_at leaveAgo="true"}}</span>
{{else}}
<span>{{i18n "admin.wizard.notice.active"}}</span>
{{/if}}
</td>

Datei anzeigen

@ -1,39 +0,0 @@
<div class="notice-header">
<div class="notice-title notice-badge notice-message">
<a role="button" {{action "toggleCookedMessage"}} class="show-notice-message">{{notice.title}}</a>
{{#if showCookedMessage}}
<span class="cooked-notice-message">{{cookedMessage}}</span>
{{/if}}
</div>
<div class="notice-header-right">
{{#if notice.expired}}
{{notice-badge class="notice-expired-at" icon="check" label="admin.wizard.notice.expired_at" date=notice.expired_at}}
{{/if}}
{{#if showPlugin}}
{{notice-badge class="notice-plugin" icon="plug" title="admin.wizard.notice.plugin" label="admin.wizard.notice.plugin" url="/admin/wizards/notices"}}
{{/if}}
{{notice-badge class="notice-created-at" icon="far-clock" label="admin.wizard.notice.created_at" date=notice.created_at leaveAgo=true}}
{{#if notice.updated_at}}
{{notice-badge class="notice-updated-at" icon="far-clock" label="admin.wizard.notice.updated_at" date=notice.updated_at}}
{{/if}}
{{#if notice.canDismiss}}
<div class="dismiss-notice-container">
{{#if dismissing}}
{{loading-spinner size="small"}}
{{else}}
<a {{action "dismiss"}} role="button" class="dismiss-notice">{{d-icon "times"}}</a>
{{/if}}
</div>
{{/if}}
{{#if notice.canHide}}
<div class="hide-notice-container">
{{#if hiding}}
{{loading-spinner size="small"}}
{{else}}
<a {{action "hide"}} role="button" class="hide-notice">{{d-icon "far-eye-slash"}}</a>
{{/if}}
</div>
{{/if}}
</div>
</div>

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

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

Datei anzeigen

@ -1,17 +1,19 @@
// discourse-skip-module
document.addEventListener("DOMContentLoaded", function () {
document.body.insertAdjacentHTML(
"afterbegin",
`
<div id="ember-testing-container"><div id="ember-testing"></div></div>
<style>#ember-testing-container { position: absolute; background: white; bottom: 0; right: 0; width: 640px; height: 384px; overflow: auto; z-index: 9999; border: 1px solid #ccc; } #ember-testing { zoom: 50%; }</style>
`
);
});
if (window.location.pathname.indexOf("/w/") > -1 && Ember.testing) {
document.addEventListener("DOMContentLoaded", function () {
document.body.insertAdjacentHTML(
"afterbegin",
`
<div id="ember-testing-container"><div id="ember-testing"></div></div>
<style>#ember-testing-container { position: absolute; background: white; bottom: 0; right: 0; width: 640px; height: 384px; overflow: auto; z-index: 9999; border: 1px solid #ccc; } #ember-testing { zoom: 50%; }</style>
`
);
});
Object.keys(requirejs.entries).forEach(function (entry) {
if (/\-test/.test(entry)) {
requirejs(entry);
}
});
Object.keys(requirejs.entries).forEach(function (entry) {
if (/\-test/.test(entry)) {
requirejs(entry);
}
});
}

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: 0.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: 0.5em 0.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

@ -53,20 +53,7 @@ en:
after_signup_after_time: "You can't use 'after time' and 'after signup' on the same wizard."
after_time: "After time setting is invalid."
liquid_syntax_error: "Liquid syntax error in %{attribute}: %{message}"
notice:
connection_error: "Failed to connect to http://%{domain}"
compatibility_issue:
title: The Custom Wizard Plugin is incompatibile with the latest version of Discourse.
message: Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse.
plugin_status:
connection_error:
title: Unable to connect to the Custom Wizard Plugin status server
message: Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse. If this issue persists contact <a href="mailto:support@thepavilion.io">support@thepavilion.io</a> for further assistance.
subscription_message:
connection_error:
title: Unable to connect to the Custom Wizard Plugin subscription server
message: If this issue persists contact <a href="mailto:support@thepavilion.io">support@thepavilion.io</a> for further assistance.
subscription: "%{type} %{property} is subscription only"
site_settings:
custom_wizard_enabled: "Enable custom wizards."

Datei anzeigen

@ -45,17 +45,5 @@ Discourse::Application.routes.append do
get 'admin/wizards/manager/export' => 'admin_manager#export'
post 'admin/wizards/manager/import' => 'admin_manager#import'
delete 'admin/wizards/manager/destroy' => 'admin_manager#destroy'
get 'admin/wizards/subscription' => 'admin_subscription#index'
post 'admin/wizards/subscription' => 'admin_subscription#update_subscription'
get 'admin/wizards/subscription/authorize' => 'admin_subscription#authorize'
get 'admin/wizards/subscription/authorize/callback' => 'admin_subscription#authorize_callback'
delete 'admin/wizards/subscription/authorize' => 'admin_subscription#destroy_authentication'
get 'admin/wizards/notice' => 'admin_notice#index'
put 'admin/wizards/notice/:notice_id/dismiss' => 'admin_notice#dismiss'
put 'admin/wizards/notice/:notice_id/hide' => 'admin_notice#hide'
put 'admin/wizards/notice/dismiss' => 'admin_notice#dismiss_all'
get 'admin/wizards/notices' => 'admin_notice#index'
end
end

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,358 +0,0 @@
# frozen_string_literal: true
class CustomWizard::Notice
include ActiveModel::Serialization
PLUGIN_STATUS_DOMAINS = {
"main" => "plugins.discourse.pavilion.tech",
"master" => "plugins.discourse.pavilion.tech",
"tests-passed" => "plugins.discourse.pavilion.tech",
"stable" => "stable.plugins.discourse.pavilion.tech"
}
SUBSCRIPTION_MESSAGE_DOMAIN = "test.thepavilion.io"
LOCALHOST_DOMAIN = "localhost:3000"
PLUGIN_STATUSES_TO_WARN = %w(incompatible tests_failing)
CHECK_PLUGIN_STATUS_ON_BRANCH = %w(tests-passed main stable)
PAGE_LIMIT = 30
attr_reader :id,
:title,
:message,
:type,
:archetype,
:created_at
attr_accessor :retrieved_at,
:updated_at,
:dismissed_at,
:expired_at,
:hidden_at
def initialize(attrs)
@id = self.class.generate_notice_id(attrs[:title], attrs[:created_at])
@title = attrs[:title]
@message = attrs[:message]
@type = attrs[:type].to_i
@archetype = attrs[:archetype].to_i
@created_at = attrs[:created_at]
@updated_at = attrs[:updated_at]
@retrieved_at = attrs[:retrieved_at]
@dismissed_at = attrs[:dismissed_at]
@expired_at = attrs[:expired_at]
@hidden_at = attrs[:hidden_at]
end
def dismiss!
if dismissable?
self.dismissed_at = DateTime.now.iso8601(3)
self.save_and_publish
end
end
def hide!
if can_hide?
self.hidden_at = DateTime.now.iso8601(3)
self.save_and_publish
end
end
def expire!
if !expired?
self.expired_at = DateTime.now.iso8601(3)
self.save_and_publish
end
end
def save_and_publish
if self.save
self.class.publish_notice_count
true
else
false
end
end
def expired?
expired_at.present?
end
def dismissed?
dismissed_at.present?
end
def dismissable?
!expired? && !dismissed? && type === self.class.types[:info]
end
def hidden?
hidden_at.present?
end
def can_hide?
!hidden? && (
type === self.class.types[:connection_error] ||
type === self.class.types[:warning]
) && (
archetype === self.class.archetypes[:plugin_status]
)
end
def save
attrs = {
expired_at: expired_at,
updated_at: updated_at,
retrieved_at: retrieved_at,
created_at: created_at,
title: title,
message: message,
type: type,
archetype: archetype
}
if current = self.class.find(self.id)
attrs[:dismissed_at] = current.dismissed_at || self.dismissed_at
attrs[:hidden_at] = current.hidden_at || self.hidden_at
end
self.class.store(id, attrs)
end
def self.types
@types ||= Enum.new(
info: 0,
warning: 1,
connection_error: 2
)
end
def self.archetypes
@archetypes ||= Enum.new(
subscription_message: 0,
plugin_status: 1
)
end
def self.update(skip_subscription: false, skip_plugin: false)
notices = []
if !skip_subscription
subscription_messages = request(:subscription_message)
if subscription_messages.present?
subscription_notices = convert_subscription_messages_to_notices(subscription_messages[:messages])
notices.push(*subscription_notices)
end
end
if !skip_plugin && request_plugin_status?
plugin_status = request(:plugin_status)
if plugin_status.present? && plugin_status[:status].present?
plugin_notice = convert_plugin_status_to_notice(plugin_status)
notices.push(plugin_notice) if plugin_notice
end
end
if notices.any?
notices.each do |notice_data|
notice = new(notice_data)
notice.retrieved_at = DateTime.now.iso8601(3)
notice.save
end
end
publish_notice_count
end
def self.publish_notice_count
payload = {
active_notice_count: CustomWizard::Notice.active_count
}
MessageBus.publish("/custom-wizard/notices", payload, group_ids: [Group::AUTO_GROUPS[:admins]])
end
def self.convert_subscription_messages_to_notices(messages)
messages.reduce([]) do |result, message|
id = generate_notice_id(message[:title], message[:created_at])
result.push(
id: id,
title: message[:title],
message: message[:message],
type: types[message[:type].to_sym],
archetype: archetypes[:subscription_message],
created_at: message[:created_at],
expired_at: message[:expired_at]
)
result
end
end
def self.convert_plugin_status_to_notice(plugin_status)
notice = nil
if PLUGIN_STATUSES_TO_WARN.include?(plugin_status[:status])
title = I18n.t('wizard.notice.compatibility_issue.title')
created_at = plugin_status[:status_changed_at]
id = generate_notice_id(title, created_at)
unless exists?(id)
message = I18n.t('wizard.notice.compatibility_issue.message', domain: plugin_status_domain)
notice = {
id: id,
title: title,
message: message,
type: types[:warning],
archetype: archetypes[:plugin_status],
created_at: created_at
}
end
else
expire_all(types[:warning], archetypes[:plugin_status])
end
notice
end
def self.notify_connection_errors(archetype)
domain = self.send("#{archetype.to_s}_domain")
title = I18n.t("wizard.notice.#{archetype.to_s}.connection_error.title")
notices = list(type: types[:connection_error], archetype: archetypes[archetype.to_sym], title: title)
if notices.any?
notice = notices.first
notice.updated_at = DateTime.now.iso8601(3)
notice.save
else
notice = new(
title: title,
message: I18n.t("wizard.notice.#{archetype.to_s}.connection_error.message", domain: domain),
archetype: archetypes[archetype.to_sym],
type: types[:connection_error],
created_at: DateTime.now.iso8601(3),
updated_at: DateTime.now.iso8601(3)
)
notice.save
end
end
def self.request_plugin_status?
CHECK_PLUGIN_STATUS_ON_BRANCH.include?(Discourse.git_branch) || Rails.env.test? || Rails.env.development?
end
def self.subscription_message_domain
return LOCALHOST_DOMAIN if (Rails.env.test? || Rails.env.development?)
SUBSCRIPTION_MESSAGE_DOMAIN
end
def self.subscription_message_url
"https://#{subscription_message_domain}/subscription-server/messages.json"
end
def self.plugin_status_domain
return LOCALHOST_DOMAIN if (Rails.env.test? || Rails.env.development?)
PLUGIN_STATUS_DOMAINS[Discourse.git_branch]
end
def self.plugin_status_url
"https://#{plugin_status_domain}/plugin-manager/status/discourse-custom-wizard"
end
def self.request(archetype)
url = self.send("#{archetype.to_s}_url")
begin
response = Excon.get(url)
rescue Excon::Error::Socket, Excon::Error::Timeout => e
response = nil
end
connection_error = CustomWizard::Notice::ConnectionError.new(archetype)
if response && response.status == 200
connection_error.expire!
expire_all(types[:connection_error], archetypes[archetype.to_sym])
begin
data = JSON.parse(response.body).deep_symbolize_keys
rescue JSON::ParserError
return nil
end
data
else
connection_error.create!
notify_connection_errors(archetype) if connection_error.reached_limit?
nil
end
end
def self.namespace
"#{CustomWizard::PLUGIN_NAME}_notice"
end
def self.find(id)
raw = PluginStore.get(namespace, id)
new(raw.symbolize_keys) if raw.present?
end
def self.exists?(id)
PluginStoreRow.where(plugin_name: namespace, key: id).exists?
end
def self.store(id, raw_notice)
PluginStore.set(namespace, id, raw_notice)
end
def self.list_query(type: nil, archetype: nil, title: nil, include_all: false, page: nil, visible: false)
query = PluginStoreRow.where(plugin_name: namespace)
query = query.where("(value::json->>'hidden_at') IS NULL") if visible
query = query.where("(value::json->>'dismissed_at') IS NULL") unless include_all
query = query.where("(value::json->>'expired_at') IS NULL") unless include_all
query = query.where("(value::json->>'archetype')::integer = ?", archetype) if archetype
if type
type_query_str = type.is_a?(Array) ? "(value::json->>'type')::integer IN (?)" : "(value::json->>'type')::integer = ?"
query = query.where(type_query_str, type)
end
query = query.where("(value::json->>'title')::text = ?", title) if title
query = query.limit(PAGE_LIMIT).offset(page.to_i * PAGE_LIMIT) if !page.nil?
query.order("value::json->>'expired_at' DESC, value::json->>'updated_at' DESC,value::json->>'dismissed_at' DESC, value::json->>'created_at' DESC")
end
def self.list(type: nil, archetype: nil, title: nil, include_all: false, page: 0, visible: false)
list_query(type: type, archetype: archetype, title: title, include_all: include_all, page: page, visible: visible)
.map { |r| self.new(JSON.parse(r.value).symbolize_keys) }
end
def self.active_count
list_query.count
end
def self.dismiss_all
dismissed_count = PluginStoreRow.where("
plugin_name = '#{namespace}' AND
(value::json->>'type')::integer = #{types[:info]} AND
(value::json->>'expired_at') IS NULL AND
(value::json->>'dismissed_at') IS NULL
").update_all("
value = jsonb_set(value::jsonb, '{dismissed_at}', (to_json(now())::text)::jsonb, true)
")
publish_notice_count if dismissed_count.to_i > 0
dismissed_count
end
def self.expire_all(type, archetype)
expired_count = PluginStoreRow.where("
plugin_name = '#{namespace}' AND
(value::json->>'type')::integer = #{type} AND
(value::json->>'archetype')::integer = #{archetype} AND
(value::json->>'expired_at') IS NULL
").update_all("
value = jsonb_set(value::jsonb, '{expired_at}', (to_json(now())::text)::jsonb, true)
")
publish_notice_count if expired_count.to_i > 0
expired_count
end
def self.generate_notice_id(title, created_at)
Digest::SHA1.hexdigest("#{title}-#{created_at}")
end
end

Datei anzeigen

@ -1,77 +0,0 @@
# frozen_string_literal: true
class CustomWizard::Notice::ConnectionError
attr_reader :archetype
def initialize(archetype)
@archetype = archetype
end
def create!
if attrs = current_error
key = "#{archetype.to_s}_error_#{attrs[:id]}"
attrs[:updated_at] = Time.now
attrs[:count] = attrs[:count].to_i + 1
else
domain = CustomWizard::Notice.send("#{archetype.to_s}_domain")
id = SecureRandom.hex(8)
attrs = {
id: id,
message: I18n.t("wizard.notice.connection_error", domain: domain),
archetype: CustomWizard::Notice.archetypes[archetype.to_sym],
created_at: Time.now,
count: 1
}
key = "#{archetype.to_s}_error_#{id}"
end
PluginStore.set(namespace, key, attrs)
@current_error = nil
end
def expire!
if query = current_error(query_only: true)
record = query.first
error = JSON.parse(record.value)
error['expired_at'] = Time.now
record.value = error.to_json
record.save
end
end
def plugin_status_limit
5
end
def subscription_message_limit
10
end
def limit
self.send("#{archetype.to_s}_limit")
end
def reached_limit?
return false unless current_error.present?
current_error[:count].to_i >= limit
end
def namespace
"#{CustomWizard::PLUGIN_NAME}_notice_connection"
end
def current_error(query_only: false)
@current_error ||= begin
query = PluginStoreRow.where(plugin_name: namespace)
query = query.where("(value::json->>'archetype')::integer = ?", CustomWizard::Notice.archetypes[archetype.to_sym])
query = query.where("(value::json->>'expired_at') IS NULL")
return nil if !query.exists?
return query if query_only
JSON.parse(query.first.value).deep_symbolize_keys
end
end
end

Datei anzeigen

@ -1,240 +1,170 @@
# frozen_string_literal: true
class CustomWizard::Subscription
include ActiveModel::Serialization
STANDARD_PRODUCT_ID = 'prod_LNAGVAaIqDsHmB'
BUSINESS_PRODUCT_ID = 'prod_LNABQ50maBQ1pY'
attr_accessor :authentication,
:subscription
SUBSCRIPTION_LEVELS = {
standard: {
actions: ["send_message", "add_to_group", "watch_categories"],
custom_fields: {
klass: [],
type: ["json"],
def self.attributes
{
wizard: {
required: {
none: [],
standard: ['*'],
business: ['*']
},
permitted: {
none: [],
standard: ['*'],
business: ['*']
}
},
},
business: {
actions: ["create_category", "create_group", "send_to_api"],
custom_fields: {
klass: ["group", "category"],
type: [],
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: ['text', 'textarea', 'text_only', 'date', 'time', 'date_time', 'number', 'checkbox', 'dropdown', 'upload'],
standard: ['*'],
business: ['*']
},
realtime_validations: {
none: [],
standard: ['*'],
business: ['*']
}
},
action: {
type: {
none: ['create_topic', 'update_profile', 'open_composer', 'route_to'],
standard: ['create_topic', 'update_profile', 'open_composer', 'route_to', '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
@authentication = CustomWizard::Subscription::Authentication.new(get_authentication)
@subscription = CustomWizard::Subscription::Subscription.new(get_subscription)
@subscription = find_subscription
end
def authorized?
@authentication.active?
end
def includes?(feature, attribute, value)
attributes = self.class.attributes[feature]
def subscribed?
@subscription.active?
end
## Attribute is not part of a subscription
return true unless attributes.present? && attributes.key?(attribute)
def server
"test.thepavilion.io"
end
values = attributes[attribute][type]
def subscription_type
"stripe"
## Subscription type does not support the attribute.
return false if values.blank?
## Subscription type supports all values of the attribute.
return true if values.first === "*"
## Subscription type supports some values of the attributes.
values.include?(value)
end
def type
@subscription.type
return :none unless subscribed?
return :standard if standard?
return :business if business?
end
def client_name
"custom-wizard"
def subscribed?
standard? || business?
end
def scope
"discourse-subscription-server:user_subscription"
def standard?
@subscription.product_id === STANDARD_PRODUCT_ID
end
def requires_additional_subscription(kategory, sub_kategory)
case kategory
when "actions"
case self.type
when "business"
[]
when "standard"
SUBSCRIPTION_LEVELS[:business][kategory.to_sym]
else
SUBSCRIPTION_LEVELS[:standard][kategory.to_sym] + SUBSCRIPTION_LEVELS[:business][kategory.to_sym]
end
when "custom_fields"
case self.type
when "business"
[]
when "standard"
SUBSCRIPTION_LEVELS[:business][kategory.to_sym][sub_kategory.to_sym]
else
SUBSCRIPTION_LEVELS[:standard][kategory.to_sym][sub_kategory.to_sym] + SUBSCRIPTION_LEVELS[:business][kategory.to_sym][sub_kategory.to_sym]
end
else
[]
end
def business?
@subscription.product_id === BUSINESS_PRODUCT_ID
end
def update
if @authentication.active?
response = Excon.get(
"https://#{server}/subscription-server/user-subscriptions/#{subscription_type}/#{client_name}",
headers: {
"User-Api-Key" => @authentication.api_key
}
)
def client_installed?
defined?(SubscriptionClient) == 'constant' && SubscriptionClient.class == Module
end
if response.status == 200
begin
data = JSON.parse(response.body).deep_symbolize_keys
rescue JSON::ParserError
return false
end
def find_subscription
subscription = nil
return false unless data && data.is_a?(Hash)
subscriptions = data[:subscriptions]
if subscriptions.present? && type = subscriptions.first[:price_nickname]
@subscription = set_subscription(type)
return true
end
end
if client_installed?
subscription = SubscriptionClientSubscription.active
.where(product_id: [STANDARD_PRODUCT_ID, BUSINESS_PRODUCT_ID])
.order("product_id = '#{BUSINESS_PRODUCT_ID}' DESC")
.first
end
destroy_subscription
false
end
def destroy_subscription
if remove_subscription
@subscription = CustomWizard::Subscription::Subscription.new(get_subscription)
!@subscription.active?
else
false
unless subscription
subscription = OpenStruct.new(product_id: nil)
end
end
def authentication_url(user_id, request_id)
keys = @authentication.generate_keys(user_id, request_id)
params = {
public_key: keys.public_key,
nonce: keys.nonce,
client_id: @authentication.client_id,
auth_redirect: "#{Discourse.base_url}/admin/wizards/subscription/authorize/callback",
application_name: SiteSetting.title,
scopes: scope
}
uri = URI.parse("https://#{server}/user-api-key/new")
uri.query = URI.encode_www_form(params)
uri.to_s
end
def authentication_response(request_id, payload)
data = @authentication.decrypt_payload(request_id, payload)
return false unless data.is_a?(Hash) && data[:key] && data[:user_id]
api_key = data[:key]
user_id = data[:user_id]
user = User.find(user_id)
if user&.admin
@authentication = set_authentication(api_key, user.id)
true
else
false
end
end
def destroy_authentication
if remove_authentication
@authentication = CustomWizard::Subscription::Authentication.new(get_authentication)
!@authentication.active?
else
false
end
subscription
end
def self.subscribed?
self.new.subscribed?
new.subscribed?
end
def self.business?
new.business?
end
def self.standard?
new.standard?
end
def self.type
self.new.type
new.type
end
def self.requires_additional_subscription(kategory, sub_kategory)
self.new.requires_additional_subscription(kategory, sub_kategory)
def self.client_installed?
new.client_installed?
end
def self.authorized?
self.new.authorized?
end
def self.update
self.new.update
end
def self.namespace
"custom_wizard_subscription"
end
private
def subscription_db_key
"subscription"
end
def authentication_db_key
"authentication"
end
def get_subscription
raw = PluginStore.get(self.class.namespace, subscription_db_key)
if raw.present?
OpenStruct.new(
type: raw['type'],
updated_at: raw['updated_at']
)
end
end
def remove_subscription
PluginStore.remove(self.class.namespace, subscription_db_key)
end
def set_subscription(type)
PluginStore.set(CustomWizard::Subscription.namespace, subscription_db_key, type: type, updated_at: Time.now)
CustomWizard::Subscription::Subscription.new(get_subscription)
end
def get_authentication
raw = PluginStore.get(self.class.namespace, authentication_db_key)
OpenStruct.new(
key: raw && raw['key'],
auth_by: raw && raw['auth_by'],
auth_at: raw && raw['auth_at']
)
end
def set_authentication(key, user_id)
PluginStore.set(self.class.namespace, authentication_db_key,
key: key,
auth_by: user_id,
auth_at: Time.now
)
CustomWizard::Subscription::Authentication.new(get_authentication)
end
def remove_authentication
PluginStore.remove(self.class.namespace, authentication_db_key)
get_authentication
def self.includes?(feature, attribute, value)
new.includes?(feature, attribute, value)
end
end

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -54,34 +54,6 @@ class CustomWizard::TemplateValidator
}
end
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
]
}
}
end
private
def check_required(object, type)
@ -93,16 +65,10 @@ 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))
)
object.keys.each do |property|
value = object[property]
if is_subscription && !@subscription.subscribed?
if !@subscription.includes?(type, property.to_sym, value)
errors.add :base, I18n.t("wizard.validation.subscription", type: type.to_s, property: property)
end
end
@ -148,10 +114,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
@ -70,15 +84,11 @@ after_initialize do
../app/controllers/custom_wizard/admin/logs.rb
../app/controllers/custom_wizard/admin/manager.rb
../app/controllers/custom_wizard/admin/custom_fields.rb
../app/controllers/custom_wizard/admin/subscription.rb
../app/controllers/custom_wizard/admin/notice.rb
../app/controllers/custom_wizard/wizard.rb
../app/controllers/custom_wizard/steps.rb
../app/controllers/custom_wizard/realtime_validations.rb
../app/jobs/regular/refresh_api_access_token.rb
../app/jobs/regular/set_after_time_wizard.rb
../app/jobs/scheduled/custom_wizard/update_subscription.rb
../app/jobs/scheduled/custom_wizard/update_notices.rb
../lib/custom_wizard/validators/template.rb
../lib/custom_wizard/validators/update.rb
../lib/custom_wizard/action_result.rb
@ -95,13 +105,9 @@ 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/notice.rb
../lib/custom_wizard/notice/connection_error.rb
../lib/custom_wizard/subscription.rb
../lib/custom_wizard/subscription/subscription.rb
../lib/custom_wizard/subscription/authentication.rb
../lib/custom_wizard/api/api.rb
../lib/custom_wizard/api/authorization.rb
../lib/custom_wizard/api/endpoint.rb
@ -122,10 +128,6 @@ after_initialize do
../app/serializers/custom_wizard/log_serializer.rb
../app/serializers/custom_wizard/submission_serializer.rb
../app/serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb
../app/serializers/custom_wizard/subscription/authentication_serializer.rb
../app/serializers/custom_wizard/subscription/subscription_serializer.rb
../app/serializers/custom_wizard/subscription_serializer.rb
../app/serializers/custom_wizard/notice_serializer.rb
../lib/custom_wizard/extensions/extra_locales_controller.rb
../lib/custom_wizard/extensions/invites_controller.rb
../lib/custom_wizard/extensions/users_controller.rb
@ -271,14 +273,6 @@ after_initialize do
"#{serializer_klass}_serializer".classify.constantize.prepend CustomWizardCustomFieldSerializer
end
AdminDashboardData.add_problem_check do
warning_notices = CustomWizard::Notice.list(
type: CustomWizard::Notice.types[:warning],
archetype: CustomWizard::Notice.archetypes[:plugin_status]
)
warning_notices.any? ? ActionView::Base.full_sanitizer.sanitize(warning_notices.first.message, tags: %w(a)) : nil
end
reloadable_patch do |plugin|
::TagsController.prepend CustomWizardTagsController
::DiscourseTagging.singleton_class.prepend CustomWizardDiscourseTagging

Datei anzeigen

@ -8,6 +8,7 @@ describe CustomWizard::Action do
let(:wizard_template) { get_wizard_fixture("wizard") }
let(:open_composer) { get_wizard_fixture("actions/open_composer") }
let(:create_category) { get_wizard_fixture("actions/create_category") }
let(:watch_categories) { get_wizard_fixture("actions/watch_categories") }
let(:create_group) { get_wizard_fixture("actions/create_group") }
let(:add_to_group) { get_wizard_fixture("actions/add_to_group") }
let(:send_message) { get_wizard_fixture("actions/send_message") }
@ -158,17 +159,6 @@ describe CustomWizard::Action do
end
end
it 'watches categories' do
wizard = CustomWizard::Builder.new(@template[:id], user).build
wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update
wizard.create_updater(wizard.steps[1].id, {}).update
expect(CategoryUser.where(
category_id: category.id,
user_id: user.id
).first.notification_level).to eq(0)
end
it 're-routes a user' do
wizard = CustomWizard::Builder.new(@template[:id], user).build
updater = wizard.create_updater(wizard.steps.last.id, {})
@ -176,11 +166,25 @@ describe CustomWizard::Action do
expect(updater.result[:redirect_on_next]).to eq("https://google.com")
end
context "subscription actions" do
context "standard subscription actions" do
before do
enable_subscription("standard")
end
it 'watches categories' do
watch_categories[:categories][0][:output] = category.id
wizard_template[:actions] << watch_categories
update_template(wizard_template)
wizard = CustomWizard::Builder.new(@template[:id], user).build
wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update
expect(CategoryUser.where(
category_id: category.id,
user_id: user.id
).first.notification_level).to eq(2)
end
it '#send_message' do
wizard_template['actions'] << send_message
update_template(wizard_template)
@ -233,9 +237,16 @@ describe CustomWizard::Action do
expect(topic.first.allowed_groups.map(&:name)).to include('cool_group', 'cool_group_1')
expect(post.exists?).to eq(true)
end
end
context "business subscription actions" do
before do
enable_subscription("business")
end
it '#create_category' do
wizard_template['actions'] << create_category
wizard_template['actions'] << create_group
update_template(wizard_template)
wizard = CustomWizard::Builder.new(@template[:id], user).build

Datei anzeigen

@ -100,6 +100,7 @@ describe CustomWizard::Builder do
context "with restricted permissions" do
before do
enable_subscription("standard")
@template[:permitted] = permitted_json["permitted"]
CustomWizard::Template.save(@template.as_json)
end
@ -304,7 +305,7 @@ describe CustomWizard::Builder do
before do
enable_subscription("standard")
@template[:steps][0][:fields][0][:condition] = user_condition_json['condition']
@template[:steps][2][:fields][5][:condition] = boolean_field_condition_json['condition']
@template[:steps][2][:fields][0][:condition] = boolean_field_condition_json['condition']
CustomWizard::Template.save(@template.as_json)
end
@ -325,7 +326,7 @@ describe CustomWizard::Builder do
builder = CustomWizard::Builder.new(@template[:id], user)
wizard = builder.build
expect(wizard.steps.last.fields.last.id).to eq(@template[:steps][2][:fields][5]['id'])
expect(wizard.steps.last.fields.last.id).to eq(@template[:steps][2][:fields][0]['id'])
end
end
end

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 +1,104 @@
# 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)
def undefine_client_classes
Object.send(:remove_const, :SubscriptionClient) if Object.constants.include?(:SubscriptionClient)
Object.send(:remove_const, :SubscriptionClientSubscription) if Object.constants.include?(:SubscriptionClientSubscription)
end
it "returns authorized and subscribed states" do
subscription = described_class.new
expect(subscription.authorized?).to eq(false)
expect(subscription.subscribed?).to eq(false)
def define_client_classes
load File.expand_path("#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/subscription_client.rb", __FILE__)
end
context "subscription" do
def stub_client_methods
[:active, :where, :order, :first].each do |method|
SubscriptionClientSubscription.stubs(method)
.returns(SubscriptionClientSubscription)
end
SubscriptionClientSubscription.stubs(:product_id).returns(SecureRandom.hex(8))
end
after do
undefine_client_classes
end
it "detects the subscription client" do
expect(described_class.client_installed?).to eq(false)
end
context "without a subscription client" do
it "is not subscribed" do
expect(described_class.subscribed?).to eq(false)
end
it "has none type" do
subscription = described_class.new
expect(subscription.type).to eq(:none)
end
it "non subscriber features are included" do
expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true)
end
it "ubscriber features are not included" do
expect(described_class.includes?(:wizard, :permitted, {})).to eq(false)
end
end
context "with subscription client" do
before do
@subscription = described_class.new
define_client_classes
stub_client_methods
end
it "updates valid subscriptions" do
stub_subscription_request(200, valid_subscription)
expect(@subscription.update).to eq(true)
expect(@subscription.subscribed?).to eq(true)
it "detects the subscription client" do
expect(described_class.client_installed?).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)
context "without a subscription" do
it "has none type" do
expect(described_class.type).to eq(:none)
end
it "non subscriber features are included" do
expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true)
end
it "subscriber features are not included" do
expect(described_class.includes?(:wizard, :permitted, {})).to eq(false)
end
end
it "handles subscription http errors" do
stub_subscription_request(404, {})
expect(@subscription.update).to eq(false)
expect(@subscription.subscribed?).to eq(false)
context "with standard subscription" do
before do
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID)
end
it "detects standard type" do
expect(described_class.type).to eq(:standard)
end
it "standard features are included" do
expect(described_class.includes?(:wizard, :type, 'send_message')).to eq(true)
end
it "business features are not included" do
expect(described_class.includes?(:action, :type, 'create_category')).to eq(false)
end
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
context "with business subscription" do
before do
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::BUSINESS_PRODUCT_ID)
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
it "detects business type" do
expect(described_class.type).to eq(:business)
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)
it "business are included" do
expect(described_class.includes?(:action, :type, 'create_category')).to eq(true)
end
end
end
end

Datei anzeigen

@ -47,6 +47,8 @@ describe CustomWizard::Template do
context "wizard template list" do
before do
enable_subscription('standard')
template_json_2 = template_json.dup
template_json_2["id"] = 'super_mega_fun_wizard_2'
template_json_2["permitted"] = permitted_json['permitted']

Datei anzeigen

@ -5,6 +5,8 @@ describe CustomWizard::TemplateValidator do
let(:template) { get_wizard_fixture("wizard") }
let(:create_category) { get_wizard_fixture("actions/create_category") }
let(:user_condition) { get_wizard_fixture("condition/user_condition") }
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
let(:composer_preview) { get_wizard_fixture("field/composer_preview") }
let(:valid_liquid_template) {
<<-LIQUID.strip
@ -104,7 +106,7 @@ describe CustomWizard::TemplateValidator do
context "without subscription" do
it "invalidates subscription wizard attributes" do
template[:save_submissions] = false
template[:permitted] = permitted_json['permitted']
expect(
CustomWizard::TemplateValidator.new(template).perform
).to eq(false)
@ -134,11 +136,11 @@ describe CustomWizard::TemplateValidator do
context "with subscription" do
before do
enable_subscription("standard")
enable_subscription("business")
end
it "validates wizard attributes" do
template[:save_submissions] = false
template[:permitted] = permitted_json['permitted']
expect(
CustomWizard::TemplateValidator.new(template).perform
).to eq(true)
@ -227,7 +229,9 @@ describe CustomWizard::TemplateValidator do
end
it "validates preview templates" do
template[:steps][0][:fields][4][:preview_template] = invalid_liquid_template
enable_subscription("standard")
template[:steps][0][:fields] << composer_preview
template[:steps][0][:fields][3][:preview_template] = invalid_liquid_template
expect_validation_failure("step_1_field_5.preview_template", liquid_syntax_error)
end
end

Datei anzeigen

@ -3,6 +3,7 @@
describe CustomWizard::UpdateValidator do
fab!(:user) { Fabricate(:user) }
let(:template) { get_wizard_fixture("wizard") }
let(:url_field) { get_wizard_fixture("field/url") }
before do
CustomWizard::Template.save(template, skip_jobs: true)
@ -21,7 +22,6 @@ describe CustomWizard::UpdateValidator do
@template[:steps][0][:fields][0][:min_length] = min_length
@template[:steps][0][:fields][1][:min_length] = min_length
@template[:steps][0][:fields][2][:min_length] = min_length
CustomWizard::Template.save(@template)
@ -34,11 +34,6 @@ describe CustomWizard::UpdateValidator do
expect(
updater.errors.messages[:step_1_field_2].first
).to eq(I18n.t('wizard.field.too_short', label: 'Textarea', min: min_length))
updater = perform_validation('step_1', step_1_field_3: 'Te')
expect(
updater.errors.messages[:step_1_field_3].first
).to eq(I18n.t('wizard.field.too_short', label: 'Composer', min: min_length))
end
it 'prevents submission if the length is over the max length' do
@ -46,7 +41,6 @@ describe CustomWizard::UpdateValidator do
@template[:steps][0][:fields][0][:max_length] = max_length
@template[:steps][0][:fields][1][:max_length] = max_length
@template[:steps][0][:fields][2][:max_length] = max_length
CustomWizard::Template.save(@template)
long_string = "Our Competitive Capability solution offers platforms a suite of wholesale offerings. In the future, will you be able to effectively revolutionize synergies in your business? In the emerging market space, industry is ethically investing its mission critical executive searches. Key players will take ownership of their capabilities by iteratively right-sizing world-class visibilities. "
@ -59,11 +53,6 @@ describe CustomWizard::UpdateValidator do
expect(
updater.errors.messages[:step_1_field_2].first
).to eq(I18n.t('wizard.field.too_long', label: 'Textarea', max: max_length))
updater = perform_validation('step_1', step_1_field_3: long_string)
expect(
updater.errors.messages[:step_1_field_3].first
).to eq(I18n.t('wizard.field.too_long', label: 'Composer', max: max_length))
end
it "allows submission if the length is under or equal to the max length" do
@ -71,7 +60,6 @@ describe CustomWizard::UpdateValidator do
@template[:steps][0][:fields][0][:max_length] = max_length
@template[:steps][0][:fields][1][:max_length] = max_length
@template[:steps][0][:fields][2][:max_length] = max_length
CustomWizard::Template.save(@template)
hundred_chars_string = "This is a line, exactly hundred characters long and not more even a single character more than that."
@ -84,11 +72,6 @@ describe CustomWizard::UpdateValidator do
expect(
updater.errors.messages[:step_1_field_2].first
).to eq(nil)
updater = perform_validation('step_1', step_1_field_3: hundred_chars_string)
expect(
updater.errors.messages[:step_1_field_3].first
).to eq(nil)
end
it "applies min length only if the input is non-empty" do
@ -131,25 +114,33 @@ describe CustomWizard::UpdateValidator do
).to eq(I18n.t('wizard.field.required', label: 'Textarea'))
end
it 'validates url fields' do
updater = perform_validation('step_2', step_2_field_6: 'https://discourse.com')
expect(
updater.errors.messages[:step_2_field_6].first
).to eq(nil)
end
context "subscription fields" do
before do
enable_subscription("standard")
end
it 'does not validate url fields with non-url inputs' do
updater = perform_validation('step_2', step_2_field_6: 'discourse')
expect(
updater.errors.messages[:step_2_field_6].first
).to eq(I18n.t('wizard.field.not_url', label: 'Url'))
end
it 'validates url fields' do
updater = perform_validation('step_2', step_2_field_6: 'https://discourse.com')
expect(
updater.errors.messages[:step_2_field_6].first
).to eq(nil)
end
it 'validates empty url fields' do
updater = perform_validation('step_2', step_2_field_6: '')
expect(
updater.errors.messages[:step_2_field_6].first
).to eq(nil)
it 'does not validate url fields with non-url inputs' do
template[:steps][0][:fields] << url_field
CustomWizard::Template.save(template)
updater = perform_validation('step_1', step_2_field_6: 'discourse')
expect(
updater.errors.messages[:step_2_field_6].first
).to eq(I18n.t('wizard.field.not_url', label: 'Url'))
end
it 'validates empty url fields' do
updater = perform_validation('step_2', step_2_field_6: '')
expect(
updater.errors.messages[:step_2_field_6].first
).to eq(nil)
end
end
it 'validates date fields' do

Datei anzeigen

@ -113,69 +113,91 @@ describe CustomWizard::Wizard do
expect(wizard.completed?).to eq(false)
end
it "permits admins" do
expect(
CustomWizard::Wizard.new(@permitted_template, admin_user).permitted?
).to eq(true)
end
context "with subscription" do
before do
enable_subscription("standard")
end
it "permits permitted users" do
expect(
CustomWizard::Wizard.new(@permitted_template, trusted_user).permitted?
).to eq(true)
end
it "permits admins" do
expect(
CustomWizard::Wizard.new(@permitted_template, admin_user).permitted?
).to eq(true)
end
it "permits everyone if everyone is permitted" do
@permitted_template['permitted'][0]['output'] = Group::AUTO_GROUPS[:everyone]
expect(
CustomWizard::Wizard.new(@permitted_template, user).permitted?
).to eq(true)
end
it "permits permitted users" do
expect(
CustomWizard::Wizard.new(@permitted_template, trusted_user).permitted?
).to eq(true)
end
it "does not permit unpermitted users" do
expect(
CustomWizard::Wizard.new(@permitted_template, user).permitted?
).to eq(false)
end
it "permits everyone if everyone is permitted" do
@permitted_template['permitted'][0]['output'] = Group::AUTO_GROUPS[:everyone]
expect(
CustomWizard::Wizard.new(@permitted_template, user).permitted?
).to eq(true)
end
it "does not let an unpermitted user access a wizard" do
expect(
CustomWizard::Wizard.new(@permitted_template, user).can_access?
).to eq(false)
end
it "does not permit unpermitted users" do
expect(
CustomWizard::Wizard.new(@permitted_template, user).permitted?
).to eq(false)
end
it "lets a permitted user access an incomplete wizard" do
expect(
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
).to eq(true)
end
it "does not let an unpermitted user access a wizard" do
expect(
CustomWizard::Wizard.new(@permitted_template, user).can_access?
).to eq(false)
end
it "lets a permitted user access a complete wizard with multiple submissions" do
append_steps
it "lets a permitted user access an incomplete wizard" do
expect(
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
).to eq(true)
end
progress_step("step_1", acting_user: trusted_user)
progress_step("step_2", acting_user: trusted_user)
progress_step("step_3", acting_user: trusted_user)
it "lets a permitted user access a complete wizard with multiple submissions" do
append_steps
@permitted_template["multiple_submissions"] = true
progress_step("step_1", acting_user: trusted_user)
progress_step("step_2", acting_user: trusted_user)
progress_step("step_3", acting_user: trusted_user)
expect(
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
).to eq(true)
end
@permitted_template["multiple_submissions"] = true
it "does not let an unpermitted user access a complete wizard without multiple submissions" do
append_steps
expect(
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
).to eq(true)
end
progress_step("step_1", acting_user: trusted_user)
progress_step("step_2", acting_user: trusted_user)
progress_step("step_3", acting_user: trusted_user)
it "does not let an unpermitted user access a complete wizard without multiple submissions" do
append_steps
@permitted_template['multiple_submissions'] = false
progress_step("step_1", acting_user: trusted_user)
progress_step("step_2", acting_user: trusted_user)
progress_step("step_3", acting_user: trusted_user)
expect(
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
).to eq(false)
@permitted_template['multiple_submissions'] = false
expect(
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
).to eq(false)
end
it "sets wizard redirects if user is permitted" do
CustomWizard::Template.save(@permitted_template, skip_jobs: true)
CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', trusted_user)
expect(
trusted_user.custom_fields['redirect_to_wizard']
).to eq("super_mega_fun_wizard")
end
it "does not set a wizard redirect if user is not permitted" do
CustomWizard::Template.save(@permitted_template, skip_jobs: true)
CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', user)
expect(
trusted_user.custom_fields['redirect_to_wizard']
).to eq(nil)
end
end
it "lists the site groups" do
@ -203,6 +225,7 @@ describe CustomWizard::Wizard do
context "class methods" do
before do
enable_subscription("standard")
CustomWizard::Template.save(@permitted_template, skip_jobs: true)
template_json_2 = template_json.dup
@ -238,20 +261,4 @@ describe CustomWizard::Wizard do
expect(CustomWizard::Wizard.prompt_completion(user).length).to eq(1)
end
end
it "sets wizard redirects if user is permitted" do
CustomWizard::Template.save(@permitted_template, skip_jobs: true)
CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', trusted_user)
expect(
trusted_user.custom_fields['redirect_to_wizard']
).to eq("super_mega_fun_wizard")
end
it "does not set a wizard redirect if user is not permitted" do
CustomWizard::Template.save(@permitted_template, skip_jobs: true)
CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', user)
expect(
trusted_user.custom_fields['redirect_to_wizard']
).to eq(nil)
end
end

23
spec/fixtures/actions/watch_categories.json gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,23 @@
{
"id": "action_5",
"run_after": "step_1",
"type": "watch_categories",
"notification_level": "tracking",
"wizard_user": true,
"categories": [
{
"type": "assignment",
"output": "",
"output_type": "text",
"output_connector": "set"
}
],
"mute_remainder": [
{
"type": "assignment",
"output": "true",
"output_type": "text",
"output_connector": "set"
}
]
}

27
spec/fixtures/field/advanced_types.json gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,27 @@
{
"fields": [
{
"id": "step_3_field_2",
"label": "Tag",
"type": "tag"
},
{
"id": "step_3_field_3",
"label": "Category",
"type": "category",
"limit": 1,
"property": "id"
},
{
"id": "step_3_field_4",
"label": "Group",
"type": "group"
},
{
"id": "step_3_field_5",
"label": "User Selector",
"description": "",
"type": "user_selector"
}
]
}

7
spec/fixtures/field/composer_preview.json gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,7 @@
{
"id": "step_1_field_5",
"label": "I'm a preview",
"description": "",
"type": "composer_preview",
"preview_template": "w{step_1_field_1}"
}

5
spec/fixtures/field/url.json gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,5 @@
{
"id": "step_2_field_6",
"label": "Url",
"type": "url"
}

4
spec/fixtures/subscription_client.rb gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,4 @@
# frozen_string_literal: true
module SubscriptionClient; end
class SubscriptionClientSubscription; end

Datei anzeigen

@ -33,23 +33,11 @@
"type": "textarea",
"min_length": ""
},
{
"id": "step_1_field_3",
"label": "Composer",
"type": "composer"
},
{
"id": "step_1_field_4",
"label": "I'm only text",
"description": "",
"type": "text_only"
},
{
"id": "step_1_field_5",
"label": "I'm a preview",
"description": "",
"type": "composer_preview",
"preview_template": "w{step_1_field_1}"
}
],
"description": "Text inputs!"
@ -87,11 +75,6 @@
"label": "Checkbox",
"type": "checkbox"
},
{
"id": "step_2_field_6",
"label": "Url",
"type": "url"
},
{
"id": "step_2_field_7",
"label": "Upload",
@ -141,35 +124,6 @@
]
}
]
},
{
"id": "step_3_field_2",
"label": "Tag",
"type": "tag"
},
{
"id": "step_3_field_3",
"label": "Category",
"type": "category",
"limit": 1,
"property": "id"
},
{
"id": "step_3_field_4",
"label": "Group",
"type": "group"
},
{
"id": "step_3_field_5",
"label": "User Selector",
"description": "",
"type": "user_selector"
},
{
"id": "step_3_field_6",
"label": "Conditional User Selector",
"description": "Shown when checkbox in step_2_field_5 is true",
"type": "user_selector"
}
],
"description": "Unfortunately not the edible type :sushi: "
@ -264,29 +218,6 @@
}
]
},
{
"id": "action_5",
"run_after": "step_1",
"type": "watch_categories",
"notification_level": "tracking",
"wizard_user": true,
"categories": [
{
"type": "assignment",
"output": "action_8",
"output_type": "wizard_action",
"output_connector": "set"
}
],
"mute_remainder": [
{
"type": "assignment",
"output": "true",
"output_type": "text",
"output_connector": "set"
}
]
},
{
"id": "action_4",
"run_after": "step_2",

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

@ -8,37 +8,8 @@ def get_wizard_fixture(path)
).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)
CustomWizard::Subscription.stubs(:client_installed?).returns(true)
CustomWizard::Subscription.stubs("#{type}?".to_sym).returns(true)
CustomWizard::Subscription.any_instance.stubs("#{type}?".to_sym).returns(true)
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

@ -8,6 +8,7 @@ describe CustomWizard::AdminWizardController do
before do
CustomWizard::Template.save(template, skip_jobs: true)
enable_subscription("standard")
template_2 = template.dup
template_2["id"] = 'super_mega_fun_wizard_2'

Datei anzeigen

@ -34,6 +34,7 @@ describe CustomWizard::StepsController do
end
it "when the user cant access the wizard" do
enable_subscription("standard")
new_template = wizard_template.dup
new_template["permitted"] = permitted_json["permitted"]
CustomWizard::Template.save(new_template, skip_jobs: true)

Datei anzeigen

@ -40,14 +40,15 @@ describe CustomWizard::WizardController do
end
it 'lets user skip if user cant access wizard' do
enable_subscription("standard")
@template["permitted"] = permitted_json["permitted"]
CustomWizard::Template.save(@template, skip_jobs: true)
put '/w/super-mega-fun-wizard/skip.json'
expect(response.status).to eq(200)
end
it 'returns a no skip message if user is not allowed to skip' do
enable_subscription("standard")
@template['required'] = 'true'
CustomWizard::Template.save(@template)
put '/w/super-mega-fun-wizard/skip.json'

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

Datei anzeigen

@ -19,7 +19,7 @@ describe CustomWizard::FieldSerializer do
expect(json_array.size).to eq(@wizard.steps.first.fields.size)
expect(json_array[0][:label]).to eq("<p>Text</p>")
expect(json_array[0][:description]).to eq("Text field description.")
expect(json_array[3][:index]).to eq(3)
expect(json_array[2][:index]).to eq(2)
end
it "should return optional field attributes" do
@ -29,6 +29,6 @@ describe CustomWizard::FieldSerializer do
scope: Guardian.new(user)
).as_json
expect(json_array[0][:format]).to eq("YYYY-MM-DD")
expect(json_array[6][:file_types]).to eq(".jpg,.jpeg,.png")
expect(json_array[5][:file_types]).to eq(".jpg,.jpeg,.png")
end
end

Datei anzeigen

@ -5,6 +5,7 @@ describe CustomWizard::WizardSerializer do
fab!(:category) { Fabricate(:category) }
let(:template) { get_wizard_fixture("wizard") }
let(:similar_topics_validation) { get_wizard_fixture("field/validation/similar_topics") }
let(:advanced_fields) { get_wizard_fixture("field/advanced_types") }
before do
CustomWizard::Template.save(template, skip_jobs: true)
@ -52,43 +53,51 @@ describe CustomWizard::WizardSerializer do
expect(json[:wizard][:uncategorized_category_id].present?).to eq(false)
end
it "should return categories if there is a category selector field" do
json = CustomWizard::WizardSerializer.new(
CustomWizard::Builder.new(@template[:id], user).build,
scope: Guardian.new(user)
).as_json
expect(json[:wizard][:categories].present?).to eq(true)
expect(json[:wizard][:uncategorized_category_id].present?).to eq(true)
end
context "advanced fields" do
before do
enable_subscription("standard")
@template[:steps][0][:fields].push(*advanced_fields['fields'])
CustomWizard::Template.save(@template, skip_jobs: true)
end
it "should return categories if there is a similar topics validation scoped to category(s)" do
@template[:steps][0][:fields][0][:validations] = similar_topics_validation[:validations]
CustomWizard::Template.save(@template)
it "should return categories if there is a category selector field" do
json = CustomWizard::WizardSerializer.new(
CustomWizard::Builder.new(@template[:id], user).build,
scope: Guardian.new(user)
).as_json
expect(json[:wizard][:categories].present?).to eq(true)
expect(json[:wizard][:uncategorized_category_id].present?).to eq(true)
end
json = CustomWizard::WizardSerializer.new(
CustomWizard::Builder.new(@template[:id], user).build,
scope: Guardian.new(user)
).as_json
expect(json[:wizard][:categories].present?).to eq(true)
expect(json[:wizard][:uncategorized_category_id].present?).to eq(true)
end
it "should return categories if there is a similar topics validation scoped to category(s)" do
@template[:steps][0][:fields][0][:validations] = similar_topics_validation[:validations]
CustomWizard::Template.save(@template)
it 'should return groups if there is a group selector field' do
json = CustomWizard::WizardSerializer.new(
CustomWizard::Builder.new(@template[:id], user).build,
scope: Guardian.new(user)
).as_json
expect(json[:wizard][:groups].length).to eq(8)
end
json = CustomWizard::WizardSerializer.new(
CustomWizard::Builder.new(@template[:id], user).build,
scope: Guardian.new(user)
).as_json
expect(json[:wizard][:categories].present?).to eq(true)
expect(json[:wizard][:uncategorized_category_id].present?).to eq(true)
end
it 'should not return groups if there is not a group selector field' do
@template[:steps][2][:fields].delete_at(3)
CustomWizard::Template.save(@template)
it 'should return groups if there is a group selector field' do
json = CustomWizard::WizardSerializer.new(
CustomWizard::Builder.new(@template[:id], user).build,
scope: Guardian.new(user)
).as_json
expect(json[:wizard][:groups].length).to eq(8)
end
json = CustomWizard::WizardSerializer.new(
CustomWizard::Builder.new(@template[:id], user).build,
scope: Guardian.new(user)
).as_json
expect(json[:wizard][:groups].present?).to eq(false)
it 'should not return groups if there is not a group selector field' do
@template[:steps][0][:fields].reject! { |f| f["type"] === "group" }
CustomWizard::Template.save(@template)
json = CustomWizard::WizardSerializer.new(
CustomWizard::Builder.new(@template[:id], user).build,
scope: Guardian.new(user)
).as_json
expect(json[:wizard][:groups].present?).to eq(false)
end
end
end