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

Merge pull request #192 from paviliondev/remove_subs_and_notices

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

Datei anzeigen

@ -3,17 +3,12 @@ class CustomWizard::AdminController < ::Admin::AdminController
before_action :ensure_admin 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

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -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,17 +1,19 @@
// discourse-skip-module // discourse-skip-module
document.addEventListener("DOMContentLoaded", function () { if (window.location.pathname.indexOf("/w/") > -1 && Ember.testing) {
document.body.insertAdjacentHTML( document.addEventListener("DOMContentLoaded", function () {
"afterbegin", document.body.insertAdjacentHTML(
` "afterbegin",
<div id="ember-testing-container"><div id="ember-testing"></div></div> `
<style>#ember-testing-container { position: absolute; background: white; bottom: 0; right: 0; width: 640px; height: 384px; overflow: auto; z-index: 9999; border: 1px solid #ccc; } #ember-testing { zoom: 50%; }</style> <div id="ember-testing-container"><div id="ember-testing"></div></div>
` <style>#ember-testing-container { position: absolute; background: white; bottom: 0; right: 0; width: 640px; height: 384px; overflow: auto; z-index: 9999; border: 1px solid #ccc; } #ember-testing { zoom: 50%; }</style>
); `
}); );
});
Object.keys(requirejs.entries).forEach(function (entry) { Object.keys(requirejs.entries).forEach(function (entry) {
if (/\-test/.test(entry)) { if (/\-test/.test(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;
}
.admin-actions {
display: flex;
align-items: center;
}
.wizard-notices-link {
position: relative; position: relative;
margin-right: 10px; cursor: pointer;
padding: 0.5em 0.65em;
background-color: rgba($primary-medium, 0.05);
border: 1.5px solid rgba($primary-medium, 0.5);
color: $primary-medium;
div > a { &:hover {
@include btn; color: $primary-medium;
color: var(--secondary) !important; }
background-color: var(--primary-medium);
&.active { svg {
background-color: var(--tertiary) !important; width: 15px;
color: var(--secondary) !important; height: 15px;
margin-right: 0.45em;
margin-bottom: 0.15em;
}
&.standard {
background-color: rgba($subscription_standard, 0.05);
border: 1.5px solid rgba($subscription_standard, 0.5);
color: $subscription_standard;
}
&.business {
background-color: $subscription_business;
border: 1.5px solid $subscription_business;
color: $secondary;
}
.d-icon {
margin-right: 0.75em;
}
}
.wizard-subscription-selector.select-kit.single-select {
.select-kit-row {
.texts {
display: flex;
align-items: center;
}
&.disabled {
background: var(--primary-low);
cursor: unset;
} }
} }
}
.active-notice-count { .subscription-label {
background-color: $danger; margin-left: 0.75em;
color: $secondary; padding-top: 0.25em;
border-radius: 50%; color: var(--pavilion-primary);
width: 18px; font-size: 0.75em;
height: 18px;
line-height: 18px;
text-align: center;
position: absolute;
top: -8px;
right: -8px;
font-size: 0.7em;
}
a.show-notice-message {
padding: 0.25em 0.5em;
color: var(--primary);
}
.wizard-notice,
.wizard-notice-row:not(.expired):not(.dismissed) {
&.info {
background-color: rgba($info, 0.1);
border: 1px solid rgba($info, 0.5);
}
&.warning,
&.connection-error {
background-color: rgba($warning, 0.1);
border: 1px solid rgba($warning, 0.5);
}
}
.notice-message {
position: relative;
.cooked-notice-message {
background-color: var(--secondary);
padding: 1em;
z-index: 1;
box-shadow: shadow("dropdown");
border-top: 1px solid var(--primary-low);
p {
margin: 0;
}
} }
} }

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -1,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"], permitted: {
none: [],
standard: ['*'],
business: ['*']
}
}, },
}, step: {
business: { condition: {
actions: ["create_category", "create_group", "send_to_api"], none: [],
custom_fields: { standard: ['*'],
klass: ["group", "category"], business: ['*']
type: [], },
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( end
"https://#{server}/subscription-server/user-subscriptions/#{subscription_type}/#{client_name}",
headers: {
"User-Api-Key" => @authentication.api_key
}
)
if response.status == 200 def find_subscription
begin subscription = nil
data = JSON.parse(response.body).deep_symbolize_keys
rescue JSON::ParserError
return false
end
return false unless data && data.is_a?(Hash) if client_installed?
subscriptions = data[:subscriptions] subscription = SubscriptionClientSubscription.active
.where(product_id: [STANDARD_PRODUCT_ID, BUSINESS_PRODUCT_ID])
if subscriptions.present? && type = subscriptions.first[:price_nickname] .order("product_id = '#{BUSINESS_PRODUCT_ID}' DESC")
@subscription = set_subscription(type) .first
return true
end
end
end end
destroy_subscription unless subscription
false subscription = OpenStruct.new(product_id: nil)
end
def destroy_subscription
if remove_subscription
@subscription = CustomWizard::Subscription::Subscription.new(get_subscription)
!@subscription.active?
else
false
end end
end
def authentication_url(user_id, request_id) subscription
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)
it "initializes subscription authentication and subscription" do Object.send(:remove_const, :SubscriptionClientSubscription) if Object.constants.include?(:SubscriptionClientSubscription)
subscription = described_class.new
expect(subscription.authentication.class).to eq(CustomWizard::Subscription::Authentication)
expect(subscription.subscription.class).to eq(CustomWizard::Subscription::Subscription)
end end
it "returns authorized and subscribed states" do def define_client_classes
subscription = described_class.new load File.expand_path("#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/subscription_client.rb", __FILE__)
expect(subscription.authorized?).to eq(false)
expect(subscription.subscribed?).to eq(false)
end end
context "subscription" do def stub_client_methods
[:active, :where, :order, :first].each do |method|
SubscriptionClientSubscription.stubs(method)
.returns(SubscriptionClientSubscription)
end
SubscriptionClientSubscription.stubs(:product_id).returns(SecureRandom.hex(8))
end
after do
undefine_client_classes
end
it "detects the subscription client" do
expect(described_class.client_installed?).to eq(false)
end
context "without a subscription client" do
it "is not subscribed" do
expect(described_class.subscribed?).to eq(false)
end
it "has none type" do
subscription = described_class.new
expect(subscription.type).to eq(:none)
end
it "non subscriber features are included" do
expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true)
end
it "ubscriber features are not included" do
expect(described_class.includes?(:wizard, :permitted, {})).to eq(false)
end
end
context "with subscription client" do
before do 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
it "non subscriber features are included" do
expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true)
end
it "subscriber features are not included" do
expect(described_class.includes?(:wizard, :permitted, {})).to eq(false)
end
end end
it "handles subscription http errors" do context "with standard subscription" do
stub_subscription_request(404, {}) before do
expect(@subscription.update).to eq(false) SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID)
expect(@subscription.subscribed?).to eq(false) end
it "detects standard type" do
expect(described_class.type).to eq(:standard)
end
it "standard features are included" do
expect(described_class.includes?(:wizard, :type, 'send_message')).to eq(true)
end
it "business features are not included" do
expect(described_class.includes?(:action, :type, 'create_category')).to eq(false)
end
end end
it "destroys subscriptions" do context "with business subscription" do
stub_subscription_request(200, valid_subscription) before do
expect(@subscription.update).to eq(true) SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::BUSINESS_PRODUCT_ID)
expect(@subscription.destroy_subscription).to eq(true) end
expect(@subscription.subscribed?).to eq(false)
end
it "has class aliases" do it "detects business type" do
authenticate_subscription expect(described_class.type).to eq(:business)
stub_subscription_request(200, valid_subscription) end
expect(described_class.update).to eq(true)
expect(described_class.subscribed?).to eq(true)
end
end
context "authentication" do it "business are included" do
before do expect(described_class.includes?(:action, :type, 'create_category')).to eq(true)
@subscription = described_class.new end
user.update!(admin: true)
end
it "generates a valid authentication request url" do
request_id = SecureRandom.hex(32)
uri = URI(@subscription.authentication_url(user.id, request_id))
expect(uri.host).to eq(@subscription.server)
parsed_query = Rack::Utils.parse_query uri.query
expect(parsed_query['public_key'].present?).to eq(true)
expect(parsed_query['nonce'].present?).to eq(true)
expect(parsed_query['client_id'].present?).to eq(true)
expect(parsed_query['auth_redirect'].present?).to eq(true)
expect(parsed_query['application_name']).to eq(SiteSetting.title)
expect(parsed_query['scopes']).to eq(@subscription.scope)
end
def generate_payload(request_id, user_id)
uri = URI(@subscription.authentication_url(user_id, request_id))
keys = @subscription.authentication.get_keys(request_id)
raw_payload = {
key: "12345",
nonce: keys.nonce,
push: false,
api: UserApiKeysController::AUTH_API_VERSION
}.to_json
public_key = OpenSSL::PKey::RSA.new(keys.pem)
Base64.encode64(public_key.public_encrypt(raw_payload))
end
it "handles authentication response if request and response is valid" do
request_id = SecureRandom.hex(32)
payload = generate_payload(request_id, user.id)
expect(@subscription.authentication_response(request_id, payload)).to eq(true)
expect(@subscription.authorized?).to eq(true)
end
it "discards authentication response if user who made request as not an admin" do
user.update!(admin: false)
request_id = SecureRandom.hex(32)
payload = generate_payload(request_id, user.id)
expect(@subscription.authentication_response(request_id, payload)).to eq(false)
expect(@subscription.authorized?).to eq(false)
end
it "discards authentication response if request_id is invalid" do
payload = generate_payload(SecureRandom.hex(32), user.id)
expect(@subscription.authentication_response(SecureRandom.hex(32), payload)).to eq(false)
expect(@subscription.authorized?).to eq(false)
end
it "destroys authentication" do
request_id = SecureRandom.hex(32)
payload = generate_payload(request_id, user.id)
@subscription.authentication_response(request_id, payload)
expect(@subscription.destroy_authentication).to eq(true)
expect(@subscription.authorized?).to eq(false)
end end
end 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,25 +114,33 @@ describe CustomWizard::UpdateValidator do
).to eq(I18n.t('wizard.field.required', label: 'Textarea')) ).to eq(I18n.t('wizard.field.required', label: 'Textarea'))
end end
it 'validates url fields' do context "subscription fields" do
updater = perform_validation('step_2', step_2_field_6: 'https://discourse.com') before do
expect( enable_subscription("standard")
updater.errors.messages[:step_2_field_6].first end
).to eq(nil)
end
it 'does not validate url fields with non-url inputs' do it 'validates url fields' do
updater = perform_validation('step_2', step_2_field_6: 'discourse') updater = perform_validation('step_2', step_2_field_6: 'https://discourse.com')
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(nil)
end end
it 'validates empty url fields' do it 'does not validate url fields with non-url inputs' do
updater = perform_validation('step_2', step_2_field_6: '') template[:steps][0][:fields] << url_field
expect( CustomWizard::Template.save(template)
updater.errors.messages[:step_2_field_6].first updater = perform_validation('step_1', step_2_field_6: 'discourse')
).to eq(nil) expect(
updater.errors.messages[:step_2_field_6].first
).to eq(I18n.t('wizard.field.not_url', label: 'Url'))
end
it 'validates empty url fields' do
updater = perform_validation('step_2', step_2_field_6: '')
expect(
updater.errors.messages[:step_2_field_6].first
).to eq(nil)
end
end end
it 'validates date fields' do it 'validates date fields' do

Datei anzeigen

@ -113,69 +113,91 @@ describe CustomWizard::Wizard do
expect(wizard.completed?).to eq(false) expect(wizard.completed?).to eq(false)
end end
it "permits admins" do context "with subscription" do
expect( before do
CustomWizard::Wizard.new(@permitted_template, admin_user).permitted? enable_subscription("standard")
).to eq(true) end
end
it "permits permitted users" do it "permits admins" do
expect( expect(
CustomWizard::Wizard.new(@permitted_template, trusted_user).permitted? CustomWizard::Wizard.new(@permitted_template, admin_user).permitted?
).to eq(true) ).to eq(true)
end end
it "permits everyone if everyone is permitted" do it "permits permitted users" do
@permitted_template['permitted'][0]['output'] = Group::AUTO_GROUPS[:everyone] expect(
expect( CustomWizard::Wizard.new(@permitted_template, trusted_user).permitted?
CustomWizard::Wizard.new(@permitted_template, user).permitted? ).to eq(true)
).to eq(true) end
end
it "does not permit unpermitted users" do it "permits everyone if everyone is permitted" do
expect( @permitted_template['permitted'][0]['output'] = Group::AUTO_GROUPS[:everyone]
CustomWizard::Wizard.new(@permitted_template, user).permitted? expect(
).to eq(false) CustomWizard::Wizard.new(@permitted_template, user).permitted?
end ).to eq(true)
end
it "does not let an unpermitted user access a wizard" do it "does not permit unpermitted users" do
expect( expect(
CustomWizard::Wizard.new(@permitted_template, user).can_access? CustomWizard::Wizard.new(@permitted_template, user).permitted?
).to eq(false) ).to eq(false)
end end
it "lets a permitted user access an incomplete wizard" do it "does not let an unpermitted user access a wizard" do
expect( expect(
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access? CustomWizard::Wizard.new(@permitted_template, user).can_access?
).to eq(true) ).to eq(false)
end end
it "lets a permitted user access a complete wizard with multiple submissions" do it "lets a permitted user access an incomplete wizard" do
append_steps expect(
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
).to eq(true)
end
progress_step("step_1", acting_user: trusted_user) it "lets a permitted user access a complete wizard with multiple submissions" do
progress_step("step_2", acting_user: trusted_user) append_steps
progress_step("step_3", acting_user: trusted_user)
@permitted_template["multiple_submissions"] = true progress_step("step_1", acting_user: trusted_user)
progress_step("step_2", acting_user: trusted_user)
progress_step("step_3", acting_user: trusted_user)
expect( @permitted_template["multiple_submissions"] = true
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
).to eq(true)
end
it "does not let an unpermitted user access a complete wizard without multiple submissions" do expect(
append_steps CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
).to eq(true)
end
progress_step("step_1", acting_user: trusted_user) it "does not let an unpermitted user access a complete wizard without multiple submissions" do
progress_step("step_2", acting_user: trusted_user) append_steps
progress_step("step_3", acting_user: trusted_user)
@permitted_template['multiple_submissions'] = false progress_step("step_1", acting_user: trusted_user)
progress_step("step_2", acting_user: trusted_user)
progress_step("step_3", acting_user: trusted_user)
expect( @permitted_template['multiple_submissions'] = false
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
).to eq(false) expect(
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
).to eq(false)
end
it "sets wizard redirects if user is permitted" do
CustomWizard::Template.save(@permitted_template, skip_jobs: true)
CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', trusted_user)
expect(
trusted_user.custom_fields['redirect_to_wizard']
).to eq("super_mega_fun_wizard")
end
it "does not set a wizard redirect if user is not permitted" do
CustomWizard::Template.save(@permitted_template, skip_jobs: true)
CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', user)
expect(
trusted_user.custom_fields['redirect_to_wizard']
).to eq(nil)
end
end end
it "lists the site groups" do it "lists the site groups" do
@ -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,43 +53,51 @@ 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
it "should return categories if there is a category selector field" do context "advanced fields" do
json = CustomWizard::WizardSerializer.new( before do
CustomWizard::Builder.new(@template[:id], user).build, enable_subscription("standard")
scope: Guardian.new(user) @template[:steps][0][:fields].push(*advanced_fields['fields'])
).as_json CustomWizard::Template.save(@template, skip_jobs: true)
expect(json[:wizard][:categories].present?).to eq(true) end
expect(json[:wizard][:uncategorized_category_id].present?).to eq(true)
end
it "should return categories if there is a similar topics validation scoped to category(s)" do it "should return categories if there is a category selector field" do
@template[:steps][0][:fields][0][:validations] = similar_topics_validation[:validations] json = CustomWizard::WizardSerializer.new(
CustomWizard::Template.save(@template) CustomWizard::Builder.new(@template[:id], user).build,
scope: Guardian.new(user)
).as_json
expect(json[:wizard][:categories].present?).to eq(true)
expect(json[:wizard][:uncategorized_category_id].present?).to eq(true)
end
json = CustomWizard::WizardSerializer.new( it "should return categories if there is a similar topics validation scoped to category(s)" do
CustomWizard::Builder.new(@template[:id], user).build, @template[:steps][0][:fields][0][:validations] = similar_topics_validation[:validations]
scope: Guardian.new(user) CustomWizard::Template.save(@template)
).as_json
expect(json[:wizard][:categories].present?).to eq(true)
expect(json[:wizard][:uncategorized_category_id].present?).to eq(true)
end
it 'should return groups if there is a group 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, scope: Guardian.new(user)
scope: Guardian.new(user) ).as_json
).as_json expect(json[:wizard][:categories].present?).to eq(true)
expect(json[:wizard][:groups].length).to eq(8) expect(json[:wizard][:uncategorized_category_id].present?).to eq(true)
end end
it 'should not return groups if there is not a group selector field' do it 'should return groups if there is a group selector field' do
@template[:steps][2][:fields].delete_at(3) json = CustomWizard::WizardSerializer.new(
CustomWizard::Template.save(@template) CustomWizard::Builder.new(@template[:id], user).build,
scope: Guardian.new(user)
).as_json
expect(json[:wizard][:groups].length).to eq(8)
end
json = CustomWizard::WizardSerializer.new( it 'should not return groups if there is not a group selector field' do
CustomWizard::Builder.new(@template[:id], user).build, @template[:steps][0][:fields].reject! { |f| f["type"] === "group" }
scope: Guardian.new(user) CustomWizard::Template.save(@template)
).as_json
expect(json[:wizard][:groups].present?).to eq(false) json = CustomWizard::WizardSerializer.new(
CustomWizard::Builder.new(@template[:id], user).build,
scope: Guardian.new(user)
).as_json
expect(json[:wizard][:groups].present?).to eq(false)
end
end end
end end