1
0
Fork 0

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 before_action :ensure_admin
def index def index
subcription = CustomWizard::Subscription.new
render_json_dump( render_json_dump(
#TODO replace with appropriate static? subscribed: subcription.subscribed?,
api_section: ["business"].include?(CustomWizard::Subscription.type), subscription_type: subcription.type,
active_notice_count: CustomWizard::Notice.active_count, subscription_attributes: CustomWizard::Subscription.attributes,
featured_notices: ActiveModel::ArraySerializer.new( subscription_client_installed: subcription.client_installed?
CustomWizard::Notice.list(
type: CustomWizard::Notice.types[:info],
archetype: CustomWizard::Notice.archetypes[:subscription_message]
),
each_serializer: CustomWizard::NoticeSerializer
)
) )
end end

Datei anzeigen

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

Datei anzeigen

@ -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, field_types: CustomWizard::Field.types,
realtime_validations: CustomWizard::RealtimeValidation.types, realtime_validations: CustomWizard::RealtimeValidation.types,
custom_fields: custom_field_list, custom_fields: custom_field_list
subscribed: CustomWizard::Subscription.subscribed?,
subscription: CustomWizard::Subscription.type
) )
end end

Datei anzeigen

@ -11,7 +11,6 @@ class CustomWizard::WizardController < ::ActionController::Base
before_action :preload_wizard_json before_action :preload_wizard_json
before_action :ensure_plugin_enabled before_action :ensure_plugin_enabled
before_action :update_subscription, only: [:index]
before_action :ensure_logged_in, only: [:skip] before_action :ensure_logged_in, only: [:skip]
helper_method :wizard_page_title helper_method :wizard_page_title
@ -45,7 +44,7 @@ class CustomWizard::WizardController < ::ActionController::Base
return render json: { error: I18n.t('wizard.no_skip') } return render json: { error: I18n.t('wizard.no_skip') }
end end
result = success_json result = { success: 'OK' }
if current_user && wizard.can_access? if current_user && wizard.can_access?
if redirect_to = wizard.current_submission&.redirect_to if redirect_to = wizard.current_submission&.redirect_to
@ -123,8 +122,4 @@ class CustomWizard::WizardController < ::ActionController::Base
redirect_to path("/") redirect_to path("/")
end end
end end
def update_subscription
CustomWizard::Subscription.update
end
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 { alias, equal, or } from "@ember/object/computed";
import I18n from "I18n"; import I18n from "I18n";
import wizardSchema, {
requiringAdditionalSubscription,
subscriptionLevel,
} from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
const generateContent = function (kategory, subscription) {
let unsubscribedCustomFields = requiringAdditionalSubscription(
subscription,
"custom_fields",
kategory
);
return wizardSchema.custom_field[kategory].reduce((result, item) => {
let disabled = unsubscribedCustomFields.includes(item);
result.push({
id: item,
name: I18n.t(`admin.wizard.custom_field.${kategory}.${item}`),
subscription: subscriptionLevel(item, "custom_fields", kategory),
disabled,
});
return result;
}, []);
};
export default Component.extend({ export default Component.extend({
tagName: "tr", tagName: "tr",
topicSerializers: ["topic_view", "topic_list_item"], topicSerializers: ["topic_view", "topic_list_item"],
@ -58,16 +35,6 @@ export default Component.extend({
} }
}, },
@discourseComputed("subscription")
customFieldTypes(subscription) {
return generateContent("type", subscription);
},
@discourseComputed("subscription")
customFieldKlasses(subscription) {
return generateContent("klass", subscription);
},
@observes("field.klass") @observes("field.klass")
clearSerializersWhenClassChanges() { clearSerializersWhenClassChanges() {
this.set("field.serializers", null); this.set("field.serializers", null);

Datei anzeigen

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

Datei anzeigen

@ -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 Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import Subscription from "../mixins/subscription";
export default Component.extend({ export default Component.extend(Subscription, {
classNameBindings: [":subscription-container", "subscribed"], classNameBindings: [":wizard-subscription-container", "subscribed"],
@discourseComputed("subscribed") @discourseComputed("subscribed")
subscribedIcon(subscribed) { subscribedIcon(subscribed) {

Datei anzeigen

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

Datei anzeigen

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

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

Datei anzeigen

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

Datei anzeigen

@ -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 DiscourseURL from "discourse/lib/url";
import { withPluginApi } from "discourse/lib/plugin-api"; 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"; import getUrl from "discourse-common/lib/get-url";
export default { export default {
@ -23,50 +20,6 @@ export default {
}; };
withPluginApi("0.8.36", (api) => { 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", { api.modifyClass("component:d-navigation", {
pluginId: "custom-wizard", pluginId: "custom-wizard",
actions: { actions: {

Datei anzeigen

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

Datei anzeigen

@ -0,0 +1,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) { setupController(controller, model) {
const customFields = A(model.custom_fields || []); const customFields = A(model.custom_fields || []);
const subscribed = model.subscribed;
const subscription = model.subscription;
controller.setProperties({ controller.setProperties({
customFields, customFields,
subscribed,
subscription,
}); });
}, },
}); });

Datei anzeigen

@ -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], currentStep: wizard.steps[0],
currentAction: wizard.actions[0], currentAction: wizard.actions[0],
creating: model.create, creating: model.create,
subscribed: parentModel.subscribed,
subscription: parentModel.subscription,
}; };
controller.setProperties(props); controller.setProperties(props);

Datei anzeigen

@ -7,16 +7,12 @@ export default DiscourseRoute.extend({
}, },
setupController(controller, model) { setupController(controller, model) {
controller.set("api_section", model.api_section); controller.setProperties({
subscribed: model.subscribed,
if (model.active_notice_count) { subscriptionType: model.subscription_type,
controller.set("activeNoticeCount", model.active_notice_count); subscriptionAttributes: model.subscription_attributes,
} subscriptionClientInstalled: model.subscription_client_installed,
if (model.featured_notices) { });
controller.set("featuredNotices", model.featured_notices);
}
controller.subscribe();
}, },
afterModel(model, transition) { afterModel(model, transition) {

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -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,5 +1,6 @@
// discourse-skip-module // discourse-skip-module
if (window.location.pathname.indexOf("/w/") > -1 && Ember.testing) {
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
document.body.insertAdjacentHTML( document.body.insertAdjacentHTML(
"afterbegin", "afterbegin",
@ -15,3 +16,4 @@ Object.keys(requirejs.entries).forEach(function (entry) {
requirejs(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 { .wizard-message {
background-color: var(--primary-low); background-color: var(--primary-low);
width: 100%; width: 100%;
@ -156,6 +164,16 @@ $error: #ef1700;
@extend .wizard-settings-group; @extend .wizard-settings-group;
} }
.admin-wizard-container.settings {
.wizard-settings {
.wizard-subscription-container {
[class~="setting"] {
margin-bottom: 0;
}
}
}
}
.wizard-custom-field { .wizard-custom-field {
background: transparent; background: transparent;
background-color: var(--primary-very-low); background-color: var(--primary-very-low);
@ -802,84 +820,6 @@ $error: #ef1700;
vertical-align: middle; vertical-align: middle;
} }
.admin-wizards-subscription {
.admin-wizard-controls {
h3,
label {
margin: 0;
}
label {
padding: 0.4em 0.5em;
margin-left: 0.75em;
background-color: var(--success);
color: var(--secondary);
}
.buttons {
display: flex;
align-items: center;
.loading-container {
margin-right: 1em;
}
}
}
.custom-wizard-subscription {
.title-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5em;
h3 {
margin: 0;
}
.buttons > span {
margin-right: 0.5em;
}
}
.detail-container {
display: flex;
align-items: center;
padding: 1em;
background-color: var(--primary-very-low);
.subscription-state {
padding: 0.25em 0.5em;
margin-right: 0.75em;
&.active {
background-color: var(--success);
color: var(--secondary);
}
}
}
}
}
.wizard-subscription-selector.select-kit.single-select {
.select-kit-row {
.texts {
display: flex;
align-items: center;
}
&.disabled {
background: var(--primary-low);
}
}
.subscription-label {
margin-left: 0.75em;
padding-top: 0.25em;
color: var(--tertiary);
font-size: 0.75em;
}
}
.btn.btn-pavilion-support { .btn.btn-pavilion-support {
background: var(--pavilion-primary); background: var(--pavilion-primary);
color: var(--pavilion-secondary); color: var(--pavilion-secondary);
@ -899,7 +839,7 @@ $error: #ef1700;
} }
} }
.subscription-container { .wizard-subscription-container {
width: 100%; width: 100%;
padding: 1em; padding: 1em;
background-color: rgba($pavilion_primary, 0.1); background-color: rgba($pavilion_primary, 0.1);
@ -907,158 +847,79 @@ $error: #ef1700;
.subscription-header { .subscription-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-bottom: 1em; margin-bottom: 0.25em;
h3 { h3 {
margin: 0; margin: 0;
} }
a { a {
color: var(--pavilion-primary); color: var(--primary);
} }
} }
&:not(.subscribed) .subscription-settings { &:not(.subscribed) .subscription-settings {
filter: blur(1px); filter: blur(1px);
pointer-events: none;
} }
} }
.admin-wizards-notices { .wizard-subscription-badge {
.wizard-table {
overflow: unset;
}
}
.wizard-notice {
padding: 0.75em;
margin-bottom: 1em;
border: 1px solid var(--primary-low);
&.dismissed {
display: none;
}
&.expired .notice-badge:not(.notice-expired-at),
&.expired a,
&.expired p {
color: var(--primary-medium) !important;
}
.notice-badge {
padding: 0 0.5em;
}
.notice-header {
display: flex;
.notice-title {
padding: 0;
}
.notice-header-right {
margin-left: auto;
display: flex;
align-items: center;
.notice-badge {
margin-left: 0.5em;
}
}
}
.dismiss-notice-container,
.hide-notice-container {
width: 40px;
display: flex;
justify-content: center;
align-items: center;
}
.dismiss-notice,
.hide-notice {
display: flex;
align-items: center;
.d-icon {
margin-right: 0;
color: var(--primary);
}
}
}
.notice-badge {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
min-height: 25px; max-height: 34px;
box-sizing: border-box; box-sizing: border-box;
color: var(--primary) !important; position: relative;
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;
&:hover {
color: $primary-medium;
} }
.admin-actions { 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; display: flex;
align-items: center; align-items: center;
} }
&.disabled {
.wizard-notices-link { background: var(--primary-low);
position: relative; cursor: unset;
margin-right: 10px;
div > a {
@include btn;
color: var(--secondary) !important;
background-color: var(--primary-medium);
&.active {
background-color: var(--tertiary) !important;
color: var(--secondary) !important;
}
}
}
.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 { .subscription-label {
position: relative; margin-left: 0.75em;
padding-top: 0.25em;
.cooked-notice-message { color: var(--pavilion-primary);
background-color: var(--secondary); font-size: 0.75em;
padding: 1em;
z-index: 1;
box-shadow: shadow("dropdown");
border-top: 1px solid var(--primary-low);
p {
margin: 0;
}
} }
} }

Datei anzeigen

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

Datei anzeigen

@ -474,53 +474,16 @@ en:
not_subscribed: not_subscribed:
label: Not Subscribed label: Not Subscribed
title: Subscribe to use these features title: Subscribe to use these features
subscription:
nav_label: Subscription
label: In subscription
additional_label: "Add subscription"
title: Custom Wizard Subscription
authorize: Authorize
authorized: Authorized
unauthorize: cancel
not_subscribed: You're not currently subscribed
subscription:
title:
standard: Standard Subscription
business: Business Subscription
status:
active: Active
inactive: Inactive
update: Update
last_updated: Last updated
notice:
plugin: Custom Wizard Plugin
message: Message
time: Time
status: Status
title: Title
dismiss:
label: Dismiss
title: Dismiss notice
dismiss_all:
label: Dismiss All
title: Dismiss all informational Custom Wizard notices
confirm: Are you sure you want to dismiss all informational Custom Wizard notices?
active: active
created_at: issued
updated_at: updated
expired_at: expired
dismissed_at: dismissed
type: type:
label: Type none:
info: Information label: Not Subscribed
warning: Warning title: There is no Custom Wizard subscription active on this forum.
connection_error: Connection Error business:
label: Business
notices: title: There is a Custom Wizard Business subscription active on this forum.
nav_label: Notices standard:
title: Plugin Notices label: Standard
title: There is a Custom Wizard Standard subscription active on this forum.
wizard_js: wizard_js:
group: group:

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_signup_after_time: "You can't use 'after time' and 'after signup' on the same wizard."
after_time: "After time setting is invalid." after_time: "After time setting is invalid."
liquid_syntax_error: "Liquid syntax error in %{attribute}: %{message}" liquid_syntax_error: "Liquid syntax error in %{attribute}: %{message}"
subscription: "%{type} %{property} is subscription only"
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.
site_settings: site_settings:
custom_wizard_enabled: "Enable custom wizards." 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' get 'admin/wizards/manager/export' => 'admin_manager#export'
post 'admin/wizards/manager/import' => 'admin_manager#import' post 'admin/wizards/manager/import' => 'admin_manager#import'
delete 'admin/wizards/manager/destroy' => 'admin_manager#destroy' 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
end end

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -1,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 # frozen_string_literal: true
class CustomWizard::Subscription class CustomWizard::Subscription
include ActiveModel::Serialization STANDARD_PRODUCT_ID = 'prod_LNAGVAaIqDsHmB'
BUSINESS_PRODUCT_ID = 'prod_LNABQ50maBQ1pY'
attr_accessor :authentication, def self.attributes
:subscription {
wizard: {
SUBSCRIPTION_LEVELS = { required: {
standard: { none: [],
actions: ["send_message", "add_to_group", "watch_categories"], standard: ['*'],
custom_fields: { business: ['*']
klass: [],
type: ["json"],
},
},
business: {
actions: ["create_category", "create_group", "send_to_api"],
custom_fields: {
klass: ["group", "category"],
type: [],
},
}, },
permitted: {
none: [],
standard: ['*'],
business: ['*']
} }
},
step: {
condition: {
none: [],
standard: ['*'],
business: ['*']
},
index: {
none: [],
standard: ['*'],
business: ['*']
},
required_data: {
none: [],
standard: ['*'],
business: ['*']
},
permitted_params: {
none: [],
standard: ['*'],
business: ['*']
}
},
field: {
condition: {
none: [],
standard: ['*'],
business: ['*']
},
index: {
none: [],
standard: ['*'],
business: ['*']
},
type: {
none: ['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 def initialize
@authentication = CustomWizard::Subscription::Authentication.new(get_authentication) @subscription = find_subscription
@subscription = CustomWizard::Subscription::Subscription.new(get_subscription)
end end
def authorized? def includes?(feature, attribute, value)
@authentication.active? attributes = self.class.attributes[feature]
end
def subscribed? ## Attribute is not part of a subscription
@subscription.active? return true unless attributes.present? && attributes.key?(attribute)
end
def server values = attributes[attribute][type]
"test.thepavilion.io"
end
def subscription_type ## Subscription type does not support the attribute.
"stripe" 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 end
def type def type
@subscription.type return :none unless subscribed?
return :standard if standard?
return :business if business?
end end
def client_name def subscribed?
"custom-wizard" standard? || business?
end end
def scope def standard?
"discourse-subscription-server:user_subscription" @subscription.product_id === STANDARD_PRODUCT_ID
end end
def requires_additional_subscription(kategory, sub_kategory) def business?
case kategory @subscription.product_id === BUSINESS_PRODUCT_ID
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
end end
def update def client_installed?
if @authentication.active? defined?(SubscriptionClient) == 'constant' && SubscriptionClient.class == Module
response = Excon.get(
"https://#{server}/subscription-server/user-subscriptions/#{subscription_type}/#{client_name}",
headers: {
"User-Api-Key" => @authentication.api_key
}
)
if response.status == 200
begin
data = JSON.parse(response.body).deep_symbolize_keys
rescue JSON::ParserError
return false
end end
return false unless data && data.is_a?(Hash) def find_subscription
subscriptions = data[:subscriptions] subscription = nil
if subscriptions.present? && type = subscriptions.first[:price_nickname] if client_installed?
@subscription = set_subscription(type) subscription = SubscriptionClientSubscription.active
return true .where(product_id: [STANDARD_PRODUCT_ID, BUSINESS_PRODUCT_ID])
end .order("product_id = '#{BUSINESS_PRODUCT_ID}' DESC")
end .first
end end
destroy_subscription unless subscription
false subscription = OpenStruct.new(product_id: nil)
end end
def destroy_subscription subscription
if remove_subscription
@subscription = CustomWizard::Subscription::Subscription.new(get_subscription)
!@subscription.active?
else
false
end
end
def authentication_url(user_id, request_id)
keys = @authentication.generate_keys(user_id, request_id)
params = {
public_key: keys.public_key,
nonce: keys.nonce,
client_id: @authentication.client_id,
auth_redirect: "#{Discourse.base_url}/admin/wizards/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
end end
def self.subscribed? def self.subscribed?
self.new.subscribed? new.subscribed?
end
def self.business?
new.business?
end
def self.standard?
new.standard?
end end
def self.type def self.type
self.new.type new.type
end end
def self.requires_additional_subscription(kategory, sub_kategory) def self.client_installed?
self.new.requires_additional_subscription(kategory, sub_kategory) new.client_installed?
end end
def self.authorized? def self.includes?(feature, attribute, value)
self.new.authorized? new.includes?(feature, attribute, value)
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
end end
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 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 private
def check_required(object, type) def check_required(object, type)
@ -93,16 +65,10 @@ class CustomWizard::TemplateValidator
end end
def validate_subscription(object, type) def validate_subscription(object, type)
self.class.subscription[type].each do |property, subscription_type| object.keys.each do |property|
val = object[property.to_s] value = object[property]
is_subscription = (val != nil) && (
subscription_type === 'present' && val.present? ||
(['true', 'false'].include?(subscription_type) && cast_bool(val) == cast_bool(subscription_type)) ||
(subscription_type === 'conditional' && val.is_a?(Hash)) ||
(subscription_type.is_a?(Array) && subscription_type.include?(val))
)
if is_subscription && !@subscription.subscribed? if !@subscription.includes?(type, property.to_sym, value)
errors.add :base, I18n.t("wizard.validation.subscription", type: type.to_s, property: property) errors.add :base, I18n.t("wizard.validation.subscription", type: type.to_s, property: property)
end end
end end
@ -148,10 +114,6 @@ class CustomWizard::TemplateValidator
end end
end end
def cast_bool(val)
ActiveRecord::Type::Boolean.new.cast(val)
end
def validate_liquid_template(object, type) def validate_liquid_template(object, type)
%w[ %w[
description description

Datei anzeigen

@ -5,17 +5,13 @@
# authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George # authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George
# contact_emails: support@thepavilion.io # contact_emails: support@thepavilion.io
# url: https://github.com/paviliondev/discourse-custom-wizard # url: https://github.com/paviliondev/discourse-custom-wizard
# subscription_url: https://coop.pavilion.tech
gem 'liquid', '5.0.1', require: true gem 'liquid', '5.0.1', require: true
register_asset 'stylesheets/admin/admin.scss', :desktop register_asset 'stylesheets/admin/admin.scss', :desktop
enabled_site_setting :custom_wizard_enabled enabled_site_setting :custom_wizard_enabled
config = Rails.application.config
plugin_asset_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets"
config.assets.paths << "#{plugin_asset_path}/javascripts"
config.assets.paths << "#{plugin_asset_path}/stylesheets/wizard"
if Rails.env.production? if Rails.env.production?
config.assets.precompile += %w{ config.assets.precompile += %w{
wizard-custom-guest.js wizard-custom-guest.js
@ -59,6 +55,24 @@ class ::Sprockets::DirectiveProcessor
end end
end end
## Override necessary due to 'assets/javascripts/wizard', particularly its tests.
def each_globbed_asset
if @path
root_path = "#{File.dirname(@path)}/assets/javascripts/discourse"
Dir.glob(["#{root_path}/**/*"]).sort.each do |f|
f_str = f.to_s
if File.directory?(f)
yield [f, true]
elsif f_str.end_with?(".js.es6") || f_str.end_with?(".hbs") || f_str.end_with?(".hbr")
yield [f, false]
elsif transpile_js && f_str.end_with?(".js")
yield [f, false]
end
end
end
end
after_initialize do after_initialize do
%w[ %w[
../lib/custom_wizard/engine.rb ../lib/custom_wizard/engine.rb
@ -70,15 +84,11 @@ after_initialize do
../app/controllers/custom_wizard/admin/logs.rb ../app/controllers/custom_wizard/admin/logs.rb
../app/controllers/custom_wizard/admin/manager.rb ../app/controllers/custom_wizard/admin/manager.rb
../app/controllers/custom_wizard/admin/custom_fields.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/wizard.rb
../app/controllers/custom_wizard/steps.rb ../app/controllers/custom_wizard/steps.rb
../app/controllers/custom_wizard/realtime_validations.rb ../app/controllers/custom_wizard/realtime_validations.rb
../app/jobs/regular/refresh_api_access_token.rb ../app/jobs/regular/refresh_api_access_token.rb
../app/jobs/regular/set_after_time_wizard.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/template.rb
../lib/custom_wizard/validators/update.rb ../lib/custom_wizard/validators/update.rb
../lib/custom_wizard/action_result.rb ../lib/custom_wizard/action_result.rb
@ -95,13 +105,9 @@ after_initialize do
../lib/custom_wizard/step_updater.rb ../lib/custom_wizard/step_updater.rb
../lib/custom_wizard/step.rb ../lib/custom_wizard/step.rb
../lib/custom_wizard/submission.rb ../lib/custom_wizard/submission.rb
../lib/custom_wizard/subscription.rb
../lib/custom_wizard/template.rb ../lib/custom_wizard/template.rb
../lib/custom_wizard/wizard.rb ../lib/custom_wizard/wizard.rb
../lib/custom_wizard/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/api.rb
../lib/custom_wizard/api/authorization.rb ../lib/custom_wizard/api/authorization.rb
../lib/custom_wizard/api/endpoint.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/log_serializer.rb
../app/serializers/custom_wizard/submission_serializer.rb ../app/serializers/custom_wizard/submission_serializer.rb
../app/serializers/custom_wizard/realtime_validation/similar_topics_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/extra_locales_controller.rb
../lib/custom_wizard/extensions/invites_controller.rb ../lib/custom_wizard/extensions/invites_controller.rb
../lib/custom_wizard/extensions/users_controller.rb ../lib/custom_wizard/extensions/users_controller.rb
@ -271,14 +273,6 @@ after_initialize do
"#{serializer_klass}_serializer".classify.constantize.prepend CustomWizardCustomFieldSerializer "#{serializer_klass}_serializer".classify.constantize.prepend CustomWizardCustomFieldSerializer
end 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| reloadable_patch do |plugin|
::TagsController.prepend CustomWizardTagsController ::TagsController.prepend CustomWizardTagsController
::DiscourseTagging.singleton_class.prepend CustomWizardDiscourseTagging ::DiscourseTagging.singleton_class.prepend CustomWizardDiscourseTagging

Datei anzeigen

@ -8,6 +8,7 @@ describe CustomWizard::Action do
let(:wizard_template) { get_wizard_fixture("wizard") } let(:wizard_template) { get_wizard_fixture("wizard") }
let(:open_composer) { get_wizard_fixture("actions/open_composer") } let(:open_composer) { get_wizard_fixture("actions/open_composer") }
let(:create_category) { get_wizard_fixture("actions/create_category") } 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(:create_group) { get_wizard_fixture("actions/create_group") }
let(:add_to_group) { get_wizard_fixture("actions/add_to_group") } let(:add_to_group) { get_wizard_fixture("actions/add_to_group") }
let(:send_message) { get_wizard_fixture("actions/send_message") } let(:send_message) { get_wizard_fixture("actions/send_message") }
@ -158,17 +159,6 @@ describe CustomWizard::Action do
end end
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 it 're-routes a user' do
wizard = CustomWizard::Builder.new(@template[:id], user).build wizard = CustomWizard::Builder.new(@template[:id], user).build
updater = wizard.create_updater(wizard.steps.last.id, {}) 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") expect(updater.result[:redirect_on_next]).to eq("https://google.com")
end end
context "subscription actions" do context "standard subscription actions" do
before do before do
enable_subscription("standard") enable_subscription("standard")
end 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 it '#send_message' do
wizard_template['actions'] << send_message wizard_template['actions'] << send_message
update_template(wizard_template) 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(topic.first.allowed_groups.map(&:name)).to include('cool_group', 'cool_group_1')
expect(post.exists?).to eq(true) expect(post.exists?).to eq(true)
end end
end
context "business subscription actions" do
before do
enable_subscription("business")
end
it '#create_category' do it '#create_category' do
wizard_template['actions'] << create_category wizard_template['actions'] << create_category
wizard_template['actions'] << create_group
update_template(wizard_template) update_template(wizard_template)
wizard = CustomWizard::Builder.new(@template[:id], user).build wizard = CustomWizard::Builder.new(@template[:id], user).build

Datei anzeigen

@ -100,6 +100,7 @@ describe CustomWizard::Builder do
context "with restricted permissions" do context "with restricted permissions" do
before do before do
enable_subscription("standard")
@template[:permitted] = permitted_json["permitted"] @template[:permitted] = permitted_json["permitted"]
CustomWizard::Template.save(@template.as_json) CustomWizard::Template.save(@template.as_json)
end end
@ -304,7 +305,7 @@ describe CustomWizard::Builder do
before do before do
enable_subscription("standard") enable_subscription("standard")
@template[:steps][0][:fields][0][:condition] = user_condition_json['condition'] @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) CustomWizard::Template.save(@template.as_json)
end end
@ -325,7 +326,7 @@ describe CustomWizard::Builder do
builder = CustomWizard::Builder.new(@template[:id], user) builder = CustomWizard::Builder.new(@template[:id], user)
wizard = builder.build 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 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 # frozen_string_literal: true
require_relative '../../plugin_helper'
describe CustomWizard::Subscription do describe CustomWizard::Subscription do
fab!(:user) { Fabricate(:user) } 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 "initializes subscription authentication and subscription" do def define_client_classes
load File.expand_path("#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/subscription_client.rb", __FILE__)
end
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 subscription = described_class.new
expect(subscription.authentication.class).to eq(CustomWizard::Subscription::Authentication) expect(subscription.type).to eq(:none)
expect(subscription.subscription.class).to eq(CustomWizard::Subscription::Subscription)
end end
it "returns authorized and subscribed states" do it "non subscriber features are included" do
subscription = described_class.new expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true)
expect(subscription.authorized?).to eq(false)
expect(subscription.subscribed?).to eq(false)
end end
context "subscription" do 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 before do
@subscription = described_class.new define_client_classes
stub_client_methods
end end
it "updates valid subscriptions" do it "detects the subscription client" do
stub_subscription_request(200, valid_subscription) expect(described_class.client_installed?).to eq(true)
expect(@subscription.update).to eq(true)
expect(@subscription.subscribed?).to eq(true)
end end
it "handles invalid subscriptions" do context "without a subscription" do
stub_subscription_request(200, invalid_subscription) it "has none type" do
expect(@subscription.update).to eq(false) expect(described_class.type).to eq(:none)
expect(@subscription.subscribed?).to eq(false)
end end
it "handles subscription http errors" do it "non subscriber features are included" do
stub_subscription_request(404, {}) expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true)
expect(@subscription.update).to eq(false)
expect(@subscription.subscribed?).to eq(false)
end end
it "destroys subscriptions" do it "subscriber features are not included" do
stub_subscription_request(200, valid_subscription) expect(described_class.includes?(:wizard, :permitted, {})).to eq(false)
expect(@subscription.update).to eq(true)
expect(@subscription.destroy_subscription).to eq(true)
expect(@subscription.subscribed?).to eq(false)
end
it "has class aliases" do
authenticate_subscription
stub_subscription_request(200, valid_subscription)
expect(described_class.update).to eq(true)
expect(described_class.subscribed?).to eq(true)
end end
end end
context "authentication" do context "with standard subscription" do
before do before do
@subscription = described_class.new SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID)
user.update!(admin: true)
end end
it "generates a valid authentication request url" do it "detects standard type" do
request_id = SecureRandom.hex(32) expect(described_class.type).to eq(:standard)
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 end
def generate_payload(request_id, user_id) it "standard features are included" do
uri = URI(@subscription.authentication_url(user_id, request_id)) expect(described_class.includes?(:wizard, :type, 'send_message')).to eq(true)
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 end
it "handles authentication response if request and response is valid" do it "business features are not included" do
request_id = SecureRandom.hex(32) expect(described_class.includes?(:action, :type, 'create_category')).to eq(false)
payload = generate_payload(request_id, user.id) end
expect(@subscription.authentication_response(request_id, payload)).to eq(true)
expect(@subscription.authorized?).to eq(true)
end end
it "discards authentication response if user who made request as not an admin" do context "with business subscription" do
user.update!(admin: false) before do
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::BUSINESS_PRODUCT_ID)
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 end
it "discards authentication response if request_id is invalid" do it "detects business type" do
payload = generate_payload(SecureRandom.hex(32), user.id) expect(described_class.type).to eq(:business)
expect(@subscription.authentication_response(SecureRandom.hex(32), payload)).to eq(false)
expect(@subscription.authorized?).to eq(false)
end end
it "destroys authentication" do it "business are included" do
request_id = SecureRandom.hex(32) expect(described_class.includes?(:action, :type, 'create_category')).to eq(true)
payload = generate_payload(request_id, user.id) end
@subscription.authentication_response(request_id, payload)
expect(@subscription.destroy_authentication).to eq(true)
expect(@subscription.authorized?).to eq(false)
end end
end end
end end

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -3,6 +3,7 @@
describe CustomWizard::UpdateValidator do describe CustomWizard::UpdateValidator do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
let(:template) { get_wizard_fixture("wizard") } let(:template) { get_wizard_fixture("wizard") }
let(:url_field) { get_wizard_fixture("field/url") }
before do before do
CustomWizard::Template.save(template, skip_jobs: true) 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][0][:min_length] = min_length
@template[:steps][0][:fields][1][: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) CustomWizard::Template.save(@template)
@ -34,11 +34,6 @@ describe CustomWizard::UpdateValidator do
expect( expect(
updater.errors.messages[:step_1_field_2].first updater.errors.messages[:step_1_field_2].first
).to eq(I18n.t('wizard.field.too_short', label: 'Textarea', min: min_length)) ).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 end
it 'prevents submission if the length is over the max length' do 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][0][:max_length] = max_length
@template[:steps][0][:fields][1][: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) 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. " 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( expect(
updater.errors.messages[:step_1_field_2].first updater.errors.messages[:step_1_field_2].first
).to eq(I18n.t('wizard.field.too_long', label: 'Textarea', max: max_length)) ).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 end
it "allows submission if the length is under or equal to the max length" do 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][0][:max_length] = max_length
@template[:steps][0][:fields][1][: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) 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." 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( expect(
updater.errors.messages[:step_1_field_2].first updater.errors.messages[:step_1_field_2].first
).to eq(nil) ).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 end
it "applies min length only if the input is non-empty" do it "applies min length only if the input is non-empty" do
@ -131,6 +114,11 @@ describe CustomWizard::UpdateValidator do
).to eq(I18n.t('wizard.field.required', label: 'Textarea')) ).to eq(I18n.t('wizard.field.required', label: 'Textarea'))
end end
context "subscription fields" do
before do
enable_subscription("standard")
end
it 'validates url fields' do it 'validates url fields' do
updater = perform_validation('step_2', step_2_field_6: 'https://discourse.com') updater = perform_validation('step_2', step_2_field_6: 'https://discourse.com')
expect( expect(
@ -139,7 +127,9 @@ describe CustomWizard::UpdateValidator do
end end
it 'does not validate url fields with non-url inputs' do it 'does not validate url fields with non-url inputs' do
updater = perform_validation('step_2', step_2_field_6: 'discourse') template[:steps][0][:fields] << url_field
CustomWizard::Template.save(template)
updater = perform_validation('step_1', step_2_field_6: 'discourse')
expect( expect(
updater.errors.messages[:step_2_field_6].first updater.errors.messages[:step_2_field_6].first
).to eq(I18n.t('wizard.field.not_url', label: 'Url')) ).to eq(I18n.t('wizard.field.not_url', label: 'Url'))
@ -151,6 +141,7 @@ describe CustomWizard::UpdateValidator do
updater.errors.messages[:step_2_field_6].first updater.errors.messages[:step_2_field_6].first
).to eq(nil) ).to eq(nil)
end end
end
it 'validates date fields' do it 'validates date fields' do
@template[:steps][1][:fields][0][:format] = "DD-MM-YYYY" @template[:steps][1][:fields][0][:format] = "DD-MM-YYYY"

Datei anzeigen

@ -113,6 +113,11 @@ describe CustomWizard::Wizard do
expect(wizard.completed?).to eq(false) expect(wizard.completed?).to eq(false)
end end
context "with subscription" do
before do
enable_subscription("standard")
end
it "permits admins" do it "permits admins" do
expect( expect(
CustomWizard::Wizard.new(@permitted_template, admin_user).permitted? CustomWizard::Wizard.new(@permitted_template, admin_user).permitted?
@ -178,6 +183,23 @@ describe CustomWizard::Wizard do
).to eq(false) ).to eq(false)
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
it "lists the site groups" do it "lists the site groups" do
expect(@wizard.groups.length).to eq(8) expect(@wizard.groups.length).to eq(8)
end end
@ -203,6 +225,7 @@ describe CustomWizard::Wizard do
context "class methods" do context "class methods" do
before do before do
enable_subscription("standard")
CustomWizard::Template.save(@permitted_template, skip_jobs: true) CustomWizard::Template.save(@permitted_template, skip_jobs: true)
template_json_2 = template_json.dup template_json_2 = template_json.dup
@ -238,20 +261,4 @@ describe CustomWizard::Wizard do
expect(CustomWizard::Wizard.prompt_completion(user).length).to eq(1) expect(CustomWizard::Wizard.prompt_completion(user).length).to eq(1)
end end
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 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", "type": "textarea",
"min_length": "" "min_length": ""
}, },
{
"id": "step_1_field_3",
"label": "Composer",
"type": "composer"
},
{ {
"id": "step_1_field_4", "id": "step_1_field_4",
"label": "I'm only text", "label": "I'm only text",
"description": "", "description": "",
"type": "text_only" "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!" "description": "Text inputs!"
@ -87,11 +75,6 @@
"label": "Checkbox", "label": "Checkbox",
"type": "checkbox" "type": "checkbox"
}, },
{
"id": "step_2_field_6",
"label": "Url",
"type": "url"
},
{ {
"id": "step_2_field_7", "id": "step_2_field_7",
"label": "Upload", "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: " "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", "id": "action_4",
"run_after": "step_2", "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 ).with_indifferent_access
end end
def authenticate_subscription
CustomWizard::Subscription::Authentication.any_instance.stubs(:active?).returns(true)
end
def enable_subscription(type) def enable_subscription(type)
# CustomWizard::Subscription.new CustomWizard::Subscription.stubs(:client_installed?).returns(true)
CustomWizard::Subscription.any_instance.stubs(:subscribed?).returns(true) CustomWizard::Subscription.stubs("#{type}?".to_sym).returns(true)
CustomWizard::Subscription.any_instance.stubs(:type).returns(type) CustomWizard::Subscription.any_instance.stubs("#{type}?".to_sym).returns(true)
end
def disable_subscription
CustomWizard::Subscription.any_instance.stubs(:subscribed?).returns(false)
end
def valid_subscription
{
product_id: "prod_CBTNpi3fqWWkq0",
price_id: "price_id",
price_nickname: "business"
}
end
def invalid_subscription
{
product_id: "prod_CBTNpi3fqWWkq0",
price_id: "price_id"
}
end
def stub_subscription_request(status, subscription)
authenticate_subscription
sub = CustomWizard::Subscription.new
stub_request(:get, "https://#{sub.server}/subscription-server/user-subscriptions/#{sub.subscription_type}/#{sub.client_name}").to_return(status: status, body: { subscriptions: [subscription] }.to_json)
end 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 before do
CustomWizard::Template.save(template, skip_jobs: true) CustomWizard::Template.save(template, skip_jobs: true)
enable_subscription("standard")
template_2 = template.dup template_2 = template.dup
template_2["id"] = 'super_mega_fun_wizard_2' template_2["id"] = 'super_mega_fun_wizard_2'

Datei anzeigen

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

Datei anzeigen

@ -40,14 +40,15 @@ describe CustomWizard::WizardController do
end end
it 'lets user skip if user cant access wizard' do it 'lets user skip if user cant access wizard' do
enable_subscription("standard")
@template["permitted"] = permitted_json["permitted"] @template["permitted"] = permitted_json["permitted"]
CustomWizard::Template.save(@template, skip_jobs: true) CustomWizard::Template.save(@template, skip_jobs: true)
put '/w/super-mega-fun-wizard/skip.json' put '/w/super-mega-fun-wizard/skip.json'
expect(response.status).to eq(200) expect(response.status).to eq(200)
end end
it 'returns a no skip message if user is not allowed to skip' do it 'returns a no skip message if user is not allowed to skip' do
enable_subscription("standard")
@template['required'] = 'true' @template['required'] = 'true'
CustomWizard::Template.save(@template) CustomWizard::Template.save(@template)
put '/w/super-mega-fun-wizard/skip.json' 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.size).to eq(@wizard.steps.first.fields.size)
expect(json_array[0][:label]).to eq("<p>Text</p>") expect(json_array[0][:label]).to eq("<p>Text</p>")
expect(json_array[0][:description]).to eq("Text field description.") 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 end
it "should return optional field attributes" do it "should return optional field attributes" do
@ -29,6 +29,6 @@ describe CustomWizard::FieldSerializer do
scope: Guardian.new(user) scope: Guardian.new(user)
).as_json ).as_json
expect(json_array[0][:format]).to eq("YYYY-MM-DD") 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
end end

Datei anzeigen

@ -5,6 +5,7 @@ describe CustomWizard::WizardSerializer do
fab!(:category) { Fabricate(:category) } fab!(:category) { Fabricate(:category) }
let(:template) { get_wizard_fixture("wizard") } let(:template) { get_wizard_fixture("wizard") }
let(:similar_topics_validation) { get_wizard_fixture("field/validation/similar_topics") } let(:similar_topics_validation) { get_wizard_fixture("field/validation/similar_topics") }
let(:advanced_fields) { get_wizard_fixture("field/advanced_types") }
before do before do
CustomWizard::Template.save(template, skip_jobs: true) CustomWizard::Template.save(template, skip_jobs: true)
@ -52,6 +53,13 @@ describe CustomWizard::WizardSerializer do
expect(json[:wizard][:uncategorized_category_id].present?).to eq(false) expect(json[:wizard][:uncategorized_category_id].present?).to eq(false)
end 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 category selector field" do it "should return categories if there is a category selector field" do
json = CustomWizard::WizardSerializer.new( json = CustomWizard::WizardSerializer.new(
CustomWizard::Builder.new(@template[:id], user).build, CustomWizard::Builder.new(@template[:id], user).build,
@ -82,7 +90,7 @@ describe CustomWizard::WizardSerializer do
end end
it 'should not return groups if there is not a group selector field' do it 'should not return groups if there is not a group selector field' do
@template[:steps][2][:fields].delete_at(3) @template[:steps][0][:fields].reject! { |f| f["type"] === "group" }
CustomWizard::Template.save(@template) CustomWizard::Template.save(@template)
json = CustomWizard::WizardSerializer.new( json = CustomWizard::WizardSerializer.new(
@ -92,3 +100,4 @@ describe CustomWizard::WizardSerializer do
expect(json[:wizard][:groups].present?).to eq(false) expect(json[:wizard][:groups].present?).to eq(false)
end end
end end
end