Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-22 17:30:29 +01:00
Merge pull request #192 from paviliondev/remove_subs_and_notices
Remove subs and notices
Dieser Commit ist enthalten in:
Commit
c56e5dcbf8
100 geänderte Dateien mit 828 neuen und 3100 gelöschten Zeilen
|
@ -3,17 +3,12 @@ class CustomWizard::AdminController < ::Admin::AdminController
|
|||
before_action :ensure_admin
|
||||
|
||||
def index
|
||||
subcription = CustomWizard::Subscription.new
|
||||
render_json_dump(
|
||||
#TODO replace with appropriate static?
|
||||
api_section: ["business"].include?(CustomWizard::Subscription.type),
|
||||
active_notice_count: CustomWizard::Notice.active_count,
|
||||
featured_notices: ActiveModel::ArraySerializer.new(
|
||||
CustomWizard::Notice.list(
|
||||
type: CustomWizard::Notice.types[:info],
|
||||
archetype: CustomWizard::Notice.archetypes[:subscription_message]
|
||||
),
|
||||
each_serializer: CustomWizard::NoticeSerializer
|
||||
)
|
||||
subscribed: subcription.subscribed?,
|
||||
subscription_type: subcription.type,
|
||||
subscription_attributes: CustomWizard::Subscription.attributes,
|
||||
subscription_client_installed: subcription.client_installed?
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController
|
||||
def index
|
||||
render_json_dump(
|
||||
custom_fields: custom_field_list,
|
||||
subscribed: CustomWizard::Subscription.subscribed?,
|
||||
subscription: CustomWizard::Subscription.type
|
||||
custom_fields: custom_field_list
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -10,9 +10,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
|
|||
),
|
||||
field_types: CustomWizard::Field.types,
|
||||
realtime_validations: CustomWizard::RealtimeValidation.types,
|
||||
custom_fields: custom_field_list,
|
||||
subscribed: CustomWizard::Subscription.subscribed?,
|
||||
subscription: CustomWizard::Subscription.type
|
||||
custom_fields: custom_field_list
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ class CustomWizard::WizardController < ::ActionController::Base
|
|||
|
||||
before_action :preload_wizard_json
|
||||
before_action :ensure_plugin_enabled
|
||||
before_action :update_subscription, only: [:index]
|
||||
before_action :ensure_logged_in, only: [:skip]
|
||||
|
||||
helper_method :wizard_page_title
|
||||
|
@ -45,7 +44,7 @@ class CustomWizard::WizardController < ::ActionController::Base
|
|||
return render json: { error: I18n.t('wizard.no_skip') }
|
||||
end
|
||||
|
||||
result = success_json
|
||||
result = { success: 'OK' }
|
||||
|
||||
if current_user && wizard.can_access?
|
||||
if redirect_to = wizard.current_submission&.redirect_to
|
||||
|
@ -123,8 +122,4 @@ class CustomWizard::WizardController < ::ActionController::Base
|
|||
redirect_to path("/")
|
||||
end
|
||||
end
|
||||
|
||||
def update_subscription
|
||||
CustomWizard::Subscription.update
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Jobs::CustomWizardUpdateNotices < ::Jobs::Scheduled
|
||||
every 5.minutes
|
||||
|
||||
def execute(args = {})
|
||||
CustomWizard::Notice.update
|
||||
end
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Jobs::CustomWizardUpdateSubscription < ::Jobs::Scheduled
|
||||
every 1.hour
|
||||
|
||||
def execute(args = {})
|
||||
CustomWizard::Subscription.update
|
||||
end
|
||||
end
|
|
@ -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
|
|
@ -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
|
|
@ -1,10 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
class CustomWizard::Subscription::SubscriptionSerializer < ApplicationSerializer
|
||||
attributes :type,
|
||||
:active,
|
||||
:updated_at
|
||||
|
||||
def active
|
||||
object.active?
|
||||
end
|
||||
end
|
|
@ -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
|
|
@ -3,29 +3,6 @@ import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
|||
import { alias, equal, or } from "@ember/object/computed";
|
||||
import I18n from "I18n";
|
||||
|
||||
import wizardSchema, {
|
||||
requiringAdditionalSubscription,
|
||||
subscriptionLevel,
|
||||
} from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
|
||||
|
||||
const generateContent = function (kategory, subscription) {
|
||||
let unsubscribedCustomFields = requiringAdditionalSubscription(
|
||||
subscription,
|
||||
"custom_fields",
|
||||
kategory
|
||||
);
|
||||
return wizardSchema.custom_field[kategory].reduce((result, item) => {
|
||||
let disabled = unsubscribedCustomFields.includes(item);
|
||||
result.push({
|
||||
id: item,
|
||||
name: I18n.t(`admin.wizard.custom_field.${kategory}.${item}`),
|
||||
subscription: subscriptionLevel(item, "custom_fields", kategory),
|
||||
disabled,
|
||||
});
|
||||
return result;
|
||||
}, []);
|
||||
};
|
||||
|
||||
export default Component.extend({
|
||||
tagName: "tr",
|
||||
topicSerializers: ["topic_view", "topic_list_item"],
|
||||
|
@ -58,16 +35,6 @@ export default Component.extend({
|
|||
}
|
||||
},
|
||||
|
||||
@discourseComputed("subscription")
|
||||
customFieldTypes(subscription) {
|
||||
return generateContent("type", subscription);
|
||||
},
|
||||
|
||||
@discourseComputed("subscription")
|
||||
customFieldKlasses(subscription) {
|
||||
return generateContent("klass", subscription);
|
||||
},
|
||||
|
||||
@observes("field.klass")
|
||||
clearSerializersWhenClassChanges() {
|
||||
this.set("field.serializers", null);
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import { default as discourseComputed } from "discourse-common/utils/decorators";
|
||||
import wizardSchema, {
|
||||
requiringAdditionalSubscription,
|
||||
subscriptionLevel,
|
||||
} from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
|
||||
import { subscriptionSelectKitContent } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-subscription";
|
||||
import { empty, equal, or } from "@ember/object/computed";
|
||||
import { notificationLevels, selectKitContent } from "../lib/wizard";
|
||||
import { computed } from "@ember/object";
|
||||
|
@ -97,22 +94,8 @@ export default Component.extend(UndoChanges, {
|
|||
return apis.find((a) => a.name === api).endpoints;
|
||||
},
|
||||
|
||||
@discourseComputed("subscription")
|
||||
actionTypes(subscription) {
|
||||
let unsubscribedActions = requiringAdditionalSubscription(
|
||||
subscription,
|
||||
"actions",
|
||||
""
|
||||
);
|
||||
return Object.keys(wizardSchema.action.types).reduce((result, type) => {
|
||||
let disabled = unsubscribedActions.includes(type);
|
||||
result.push({
|
||||
id: type,
|
||||
name: I18n.t(`admin.wizard.action.${type}.label`),
|
||||
subscription: subscriptionLevel(type, "actions", ""),
|
||||
disabled,
|
||||
});
|
||||
return result;
|
||||
}, []);
|
||||
@discourseComputed
|
||||
actionTypes() {
|
||||
return subscriptionSelectKitContent("action", "types");
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
},
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
|
@ -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);
|
||||
},
|
||||
});
|
|
@ -1,8 +1,9 @@
|
|||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import Subscription from "../mixins/subscription";
|
||||
|
||||
export default Component.extend({
|
||||
classNameBindings: [":subscription-container", "subscribed"],
|
||||
export default Component.extend(Subscription, {
|
||||
classNameBindings: [":wizard-subscription-container", "subscribed"],
|
||||
|
||||
@discourseComputed("subscribed")
|
||||
subscribedIcon(subscribed) {
|
|
@ -1,6 +1,22 @@
|
|||
import SingleSelectComponent from "select-kit/components/single-select";
|
||||
import Subscription from "../mixins/subscription";
|
||||
import wizardSchema from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
|
||||
import {
|
||||
subscriptionTypeSufficient,
|
||||
subscriptionTypes,
|
||||
} from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-subscription";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import I18n from "I18n";
|
||||
|
||||
export default SingleSelectComponent.extend({
|
||||
const nameKey = function (feature, attribute, value) {
|
||||
if (feature === "action") {
|
||||
return `admin.wizard.action.${value}.label`;
|
||||
} else {
|
||||
return `admin.wizard.${feature}.${attribute}.${value}`;
|
||||
}
|
||||
};
|
||||
|
||||
export default SingleSelectComponent.extend(Subscription, {
|
||||
classNames: ["combo-box", "wizard-subscription-selector"],
|
||||
|
||||
selectKitOptions: {
|
||||
|
@ -13,6 +29,47 @@ export default SingleSelectComponent.extend({
|
|||
caretDownIcon: "caret-down",
|
||||
},
|
||||
|
||||
requiredSubscriptionType(feature, attribute, value) {
|
||||
let attributes = this.subscriptionAttributes[feature];
|
||||
if (!attributes || !attributes[attribute]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let requiredType = null;
|
||||
Object.keys(attributes[attribute]).some((subscriptionType) => {
|
||||
let values = attributes[attribute][subscriptionType];
|
||||
if (values[0] === "*" || values.includes(value)) {
|
||||
if (subscriptionTypes.includes(subscriptionType)) {
|
||||
requiredType = subscriptionType;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return requiredType;
|
||||
},
|
||||
|
||||
@discourseComputed("feature", "attribute")
|
||||
content(feature, attribute) {
|
||||
return wizardSchema[feature][attribute].map((value) => {
|
||||
let requiredSubscriptionType = this.requiredSubscriptionType(
|
||||
feature,
|
||||
attribute,
|
||||
value
|
||||
);
|
||||
return {
|
||||
id: value,
|
||||
name: I18n.t(nameKey(feature, attribute, value)),
|
||||
subscriptionType: requiredSubscriptionType,
|
||||
disabled: !subscriptionTypeSufficient(
|
||||
this.subscriptionType,
|
||||
requiredSubscriptionType
|
||||
),
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
modifyComponentForRow() {
|
||||
return "wizard-subscription-selector/wizard-subscription-selector-row";
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
{{#if notices}}
|
||||
{{#each notices as |notice|}}
|
||||
{{wizard-notice notice=notice showPlugin=true}}
|
||||
{{/each}}
|
||||
{{/if}}
|
|
@ -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"));
|
||||
});
|
||||
},
|
||||
};
|
|
@ -6,11 +6,11 @@
|
|||
</label>
|
||||
<div class="controls">
|
||||
{{combo-box
|
||||
value=wizardListVal
|
||||
content=wizardList
|
||||
onChange=(action "changeWizard")
|
||||
options=(hash
|
||||
none="admin.wizard.select"
|
||||
)}}
|
||||
value=wizardListVal
|
||||
content=wizardList
|
||||
onChange=(action "changeWizard")
|
||||
options=(hash
|
||||
none="admin.wizard.select"
|
||||
)}}
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,26 +1,7 @@
|
|||
import Controller, { inject as controller } from "@ember/controller";
|
||||
import { isPresent } from "@ember/utils";
|
||||
import { A } from "@ember/array";
|
||||
import Controller from "@ember/controller";
|
||||
import { equal } from "@ember/object/computed";
|
||||
|
||||
export default Controller.extend({
|
||||
adminWizardsNotices: controller(),
|
||||
|
||||
unsubscribe() {
|
||||
this.messageBus.unsubscribe("/custom-wizard/notices");
|
||||
},
|
||||
|
||||
subscribe() {
|
||||
this.unsubscribe();
|
||||
this.messageBus.subscribe("/custom-wizard/notices", (data) => {
|
||||
if (isPresent(data.active_notice_count)) {
|
||||
this.set("activeNoticeCount", data.active_notice_count);
|
||||
this.adminWizardsNotices.setProperties({
|
||||
notices: A(),
|
||||
page: 0,
|
||||
canLoadMore: true,
|
||||
});
|
||||
this.adminWizardsNotices.loadMoreNotices();
|
||||
}
|
||||
});
|
||||
},
|
||||
businessSubscription: equal("subscriptionType", "business"),
|
||||
standardSubscription: equal("subscriptionType", "standard"),
|
||||
});
|
||||
|
|
|
@ -58,16 +58,6 @@ export default {
|
|||
path: "/manager",
|
||||
resetNamespace: true,
|
||||
});
|
||||
|
||||
this.route("adminWizardsSubscription", {
|
||||
path: "/subscription",
|
||||
resetNamespace: true,
|
||||
});
|
||||
|
||||
this.route("adminWizardsNotices", {
|
||||
path: "/notices",
|
||||
resetNamespace: true,
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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 += " ";
|
||||
}
|
||||
html += `<span>${I18n.t(attrs.label)}</span>`;
|
||||
}
|
||||
if (attrs.date) {
|
||||
if (attrs.icon || attrs.label) {
|
||||
html += " ";
|
||||
}
|
||||
let dateAttrs = {};
|
||||
if (attrs.leaveAgo) {
|
||||
dateAttrs = {
|
||||
format: "medium",
|
||||
leaveAgo: true,
|
||||
};
|
||||
}
|
||||
html += autoUpdatingRelativeAge(new Date(attrs.date), dateAttrs);
|
||||
}
|
||||
html += `</${tag}>`;
|
||||
return htmlSafe(html);
|
||||
});
|
|
@ -1,8 +1,5 @@
|
|||
import DiscourseURL from "discourse/lib/url";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import CustomWizardNotice from "../models/custom-wizard-notice";
|
||||
import { isPresent } from "@ember/utils";
|
||||
import { A } from "@ember/array";
|
||||
import getUrl from "discourse-common/lib/get-url";
|
||||
|
||||
export default {
|
||||
|
@ -23,50 +20,6 @@ export default {
|
|||
};
|
||||
|
||||
withPluginApi("0.8.36", (api) => {
|
||||
api.modifyClass("route:admin-dashboard", {
|
||||
pluginId: "custom-wizard",
|
||||
|
||||
setupController(controller) {
|
||||
this._super(...arguments);
|
||||
|
||||
controller.loadCriticalNotices();
|
||||
controller.subscribe();
|
||||
},
|
||||
});
|
||||
|
||||
api.modifyClass("controller:admin-dashboard", {
|
||||
pluginId: "custom-wizard",
|
||||
criticalNotices: A(),
|
||||
|
||||
unsubscribe() {
|
||||
this.messageBus.unsubscribe("/custom-wizard/notices");
|
||||
},
|
||||
|
||||
subscribe() {
|
||||
this.unsubscribe();
|
||||
this.messageBus.subscribe("/custom-wizard/notices", (data) => {
|
||||
if (isPresent(data.active_notice_count)) {
|
||||
this.loadCriticalNotices();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
loadCriticalNotices() {
|
||||
CustomWizardNotice.list({
|
||||
type: ["connection_error", "warning"],
|
||||
archetype: "plugin_status",
|
||||
visible: true,
|
||||
}).then((result) => {
|
||||
if (result.notices && result.notices.length) {
|
||||
const criticalNotices = A(
|
||||
result.notices.map((n) => CustomWizardNotice.create(n))
|
||||
);
|
||||
this.set("customWizardCriticalNotices", criticalNotices);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
api.modifyClass("component:d-navigation", {
|
||||
pluginId: "custom-wizard",
|
||||
actions: {
|
||||
|
|
|
@ -203,23 +203,8 @@ const custom_field = {
|
|||
type: ["string", "boolean", "integer", "json"],
|
||||
};
|
||||
|
||||
const subscription_levels = {
|
||||
standard: {
|
||||
actions: ["send_message", "add_to_group", "watch_categories"],
|
||||
custom_fields: {
|
||||
klass: [],
|
||||
type: ["json"],
|
||||
},
|
||||
},
|
||||
|
||||
business: {
|
||||
actions: ["create_category", "create_group", "send_to_api"],
|
||||
custom_fields: {
|
||||
klass: ["group", "category"],
|
||||
type: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
field.type = Object.keys(field.types);
|
||||
action.type = Object.keys(action.types);
|
||||
|
||||
const wizardSchema = {
|
||||
wizard,
|
||||
|
@ -227,77 +212,8 @@ const wizardSchema = {
|
|||
field,
|
||||
custom_field,
|
||||
action,
|
||||
subscription_levels,
|
||||
};
|
||||
|
||||
export function requiringAdditionalSubscription(
|
||||
currentSubscription,
|
||||
category,
|
||||
subCategory
|
||||
) {
|
||||
switch (category) {
|
||||
case "actions":
|
||||
switch (currentSubscription) {
|
||||
case "business":
|
||||
return [];
|
||||
case "standard":
|
||||
return subscription_levels["business"][category];
|
||||
default:
|
||||
return subscription_levels["standard"][category].concat(
|
||||
subscription_levels["business"][category]
|
||||
);
|
||||
}
|
||||
case "custom_fields":
|
||||
switch (currentSubscription) {
|
||||
case "business":
|
||||
return [];
|
||||
case "standard":
|
||||
return subscription_levels["business"][category][subCategory];
|
||||
default:
|
||||
return subscription_levels["standard"][category][subCategory].concat(
|
||||
subscription_levels["business"][category][subCategory]
|
||||
);
|
||||
}
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function subscriptionLevel(type, category, subCategory) {
|
||||
switch (category) {
|
||||
case "actions":
|
||||
if (subscription_levels["business"].actions.includes(type)) {
|
||||
return "business";
|
||||
} else {
|
||||
if (subscription_levels["standard"].actions.includes(type)) {
|
||||
return "standard";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
case "custom_fields":
|
||||
if (
|
||||
subscription_levels["business"].custom_fields[subCategory].includes(
|
||||
type
|
||||
)
|
||||
) {
|
||||
return "business";
|
||||
} else {
|
||||
if (
|
||||
subscription_levels["standard"].custom_fields[subCategory].includes(
|
||||
type
|
||||
)
|
||||
) {
|
||||
return "standard";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export function buildFieldTypes(types) {
|
||||
wizardSchema.field.types = types;
|
||||
}
|
||||
|
|
22
assets/javascripts/discourse/lib/wizard-subscription.js.es6
Normale Datei
22
assets/javascripts/discourse/lib/wizard-subscription.js.es6
Normale Datei
|
@ -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 };
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
30
assets/javascripts/discourse/mixins/subscription.js.es6
Normale Datei
30
assets/javascripts/discourse/mixins/subscription.js.es6
Normale Datei
|
@ -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;
|
||||
},
|
||||
});
|
|
@ -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;
|
|
@ -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;
|
|
@ -9,13 +9,9 @@ export default DiscourseRoute.extend({
|
|||
|
||||
setupController(controller, model) {
|
||||
const customFields = A(model.custom_fields || []);
|
||||
const subscribed = model.subscribed;
|
||||
const subscription = model.subscription;
|
||||
|
||||
controller.setProperties({
|
||||
customFields,
|
||||
subscribed,
|
||||
subscription,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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))
|
||||
),
|
||||
});
|
||||
},
|
||||
});
|
|
@ -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();
|
||||
},
|
||||
},
|
||||
});
|
|
@ -39,8 +39,6 @@ export default DiscourseRoute.extend({
|
|||
currentStep: wizard.steps[0],
|
||||
currentAction: wizard.actions[0],
|
||||
creating: model.create,
|
||||
subscribed: parentModel.subscribed,
|
||||
subscription: parentModel.subscription,
|
||||
};
|
||||
|
||||
controller.setProperties(props);
|
||||
|
|
|
@ -7,16 +7,12 @@ export default DiscourseRoute.extend({
|
|||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
controller.set("api_section", model.api_section);
|
||||
|
||||
if (model.active_notice_count) {
|
||||
controller.set("activeNoticeCount", model.active_notice_count);
|
||||
}
|
||||
if (model.featured_notices) {
|
||||
controller.set("featuredNotices", model.featured_notices);
|
||||
}
|
||||
|
||||
controller.subscribe();
|
||||
controller.setProperties({
|
||||
subscribed: model.subscribed,
|
||||
subscriptionType: model.subscription_type,
|
||||
subscriptionAttributes: model.subscription_attributes,
|
||||
subscriptionClientInstalled: model.subscription_client_installed,
|
||||
});
|
||||
},
|
||||
|
||||
afterModel(model, transition) {
|
||||
|
|
|
@ -32,9 +32,7 @@
|
|||
{{custom-field-input
|
||||
field=field
|
||||
removeField=(action "removeField")
|
||||
saveField=(action "saveField")
|
||||
subscribed=subscribed
|
||||
subscription=subscription}}
|
||||
saveField=(action "saveField")}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -126,7 +126,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{#subscription-container subscribed=subscribed}}
|
||||
{{#wizard-subscription-container}}
|
||||
<div class="setting">
|
||||
<div class="setting-label">
|
||||
<label>{{i18n "admin.wizard.save_submissions"}}</label>
|
||||
|
@ -146,7 +146,7 @@
|
|||
<span>{{i18n "admin.wizard.restart_on_revisit_label"}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{/subscription-container}}
|
||||
{{/wizard-subscription-container}}
|
||||
</div>
|
||||
|
||||
{{wizard-links
|
||||
|
@ -177,9 +177,7 @@
|
|||
wizard=wizard
|
||||
apis=apis
|
||||
removeAction="removeAction"
|
||||
wizardFields=wizardFields
|
||||
subscribed=subscribed
|
||||
subscription=subscription}}
|
||||
wizardFields=wizardFields}}
|
||||
{{/each}}
|
||||
|
||||
<div class="admin-wizard-buttons">
|
||||
|
|
|
@ -2,20 +2,14 @@
|
|||
{{nav-item route="adminWizardsWizard" label="admin.wizard.nav_label"}}
|
||||
{{nav-item route="adminWizardsCustomFields" label="admin.wizard.custom_field.nav_label"}}
|
||||
{{nav-item route="adminWizardsSubmissions" label="admin.wizard.submissions.nav_label"}}
|
||||
{{#if api_section}}
|
||||
{{#if businessSubscription}}
|
||||
{{nav-item route="adminWizardsApi" label="admin.wizard.api.nav_label"}}
|
||||
{{/if}}
|
||||
{{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}}
|
||||
{{nav-item route="adminWizardsManager" label="admin.wizard.manager.nav_label"}}
|
||||
{{nav-item route="adminWizardsSubscription" label="admin.wizard.subscription.nav_label"}}
|
||||
|
||||
<div class="admin-actions">
|
||||
<div class="wizard-notices-link">
|
||||
{{nav-item tagName="div" route="adminWizardsNotices" label="admin.wizard.notices.nav_label"}}
|
||||
{{#if activeNoticeCount}}
|
||||
<span class="active-notice-count">{{activeNoticeCount}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{wizard-subscription-badge}}
|
||||
<a target="_blank" class="btn btn-pavilion-support" rel="noreferrer noopener" href="https://thepavilion.io/w/support" title={{i18n "admin.wizard.support_button.title"}}>
|
||||
{{d-icon "far-life-ring"}}{{i18n "admin.wizard.support_button.label"}}
|
||||
</a>
|
||||
|
|
|
@ -2,16 +2,22 @@
|
|||
<td>
|
||||
{{wizard-subscription-selector
|
||||
value=field.klass
|
||||
content=customFieldKlasses
|
||||
none="admin.wizard.custom_field.klass.select"
|
||||
onChange=(action (mut field.klass))}}
|
||||
feature="custom_field"
|
||||
attribute="klass"
|
||||
onChange=(action (mut field.klass))
|
||||
options=(hash
|
||||
none="admin.wizard.custom_field.klass.select"
|
||||
)}}
|
||||
</td>
|
||||
<td>
|
||||
{{wizard-subscription-selector
|
||||
value=field.type
|
||||
content=customFieldTypes
|
||||
none="admin.wizard.custom_field.type.select"
|
||||
onChange=(action (mut field.type))}}
|
||||
feature="custom_field"
|
||||
attribute="type"
|
||||
onChange=(action (mut field.type))
|
||||
options=(hash
|
||||
none="admin.wizard.custom_field.type.select"
|
||||
)}}
|
||||
</td>
|
||||
<td class="input">
|
||||
{{input
|
||||
|
@ -22,8 +28,10 @@
|
|||
{{multi-select
|
||||
value=field.serializers
|
||||
content=serializerContent
|
||||
none="admin.wizard.custom_field.serializers.select"
|
||||
onChange=(action (mut field.serializers))}}
|
||||
onChange=(action (mut field.serializers))
|
||||
options=(hash
|
||||
none="admin.wizard.custom_field.serializers.select"
|
||||
)}}
|
||||
</td>
|
||||
<td class="actions">
|
||||
{{#if loading}}
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
<div class="setting-value">
|
||||
{{wizard-subscription-selector
|
||||
value=action.type
|
||||
content=actionTypes
|
||||
feature="action"
|
||||
attribute="type"
|
||||
onChange=(action "changeType")
|
||||
options=(hash
|
||||
none="admin.wizard.select_type"
|
||||
|
|
|
@ -222,7 +222,7 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#subscription-container subscribed=subscribed}}
|
||||
{{#wizard-subscription-container}}
|
||||
<div class="setting full field-mapper-setting">
|
||||
<div class="setting-label">
|
||||
<label>{{i18n "admin.wizard.condition"}}</label>
|
||||
|
@ -237,7 +237,7 @@
|
|||
|
||||
<div class="setting full field-mapper-setting">
|
||||
<div class="setting-label">
|
||||
<label>{{i18n "admin.wizard.index"}}</label>>
|
||||
<label>{{i18n "admin.wizard.index"}}</label>
|
||||
</div>
|
||||
|
||||
<div class="setting-value">
|
||||
|
@ -248,10 +248,9 @@
|
|||
</div>
|
||||
|
||||
{{#if isCategory}}
|
||||
<div class="setting pro">
|
||||
<div class="setting">
|
||||
<div class="setting-label">
|
||||
<label>{{i18n "admin.wizard.field.property"}}</label>
|
||||
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
|
||||
</div>
|
||||
|
||||
<div class="setting-value">
|
||||
|
@ -269,4 +268,4 @@
|
|||
{{#if validations}}
|
||||
{{wizard-realtime-validations field=field validations=validations}}
|
||||
{{/if}}
|
||||
{{/subscription-container}}
|
||||
{{/wizard-subscription-container}}
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{#subscription-container subscribed=subscribed}}
|
||||
{{#wizard-subscription-container}}
|
||||
<div class="setting full field-mapper-setting">
|
||||
<div class="setting-label">
|
||||
<label>{{i18n "admin.wizard.condition"}}</label>
|
||||
|
@ -56,11 +56,11 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting full field-mapper-setting pro">
|
||||
<div class="setting full field-mapper-setting">
|
||||
<div class="setting-label">
|
||||
<label>{{i18n "admin.wizard.step.required_data.label"}}</label>
|
||||
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
|
||||
</div>
|
||||
|
||||
<div class="setting-value">
|
||||
{{wizard-mapper
|
||||
inputs=step.required_data
|
||||
|
@ -99,7 +99,7 @@
|
|||
)}}
|
||||
</div>
|
||||
</div>
|
||||
{{/subscription-container}}
|
||||
{{/wizard-subscription-container}}
|
||||
|
||||
{{wizard-links
|
||||
itemType="field"
|
||||
|
|
|
@ -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"}} {{format-date notice.dismissed_at leaveAgo="true"}}</span>
|
||||
{{else if notice.expired}}
|
||||
<span>{{i18n "admin.wizard.notice.expired_at"}} {{format-date notice.expired_at leaveAgo="true"}}</span>
|
||||
{{else}}
|
||||
<span>{{i18n "admin.wizard.notice.active"}}</span>
|
||||
{{/if}}
|
||||
</td>
|
|
@ -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>
|
|
@ -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 |
|
@ -1,7 +1,7 @@
|
|||
<div class="subscription-header">
|
||||
<h3>{{i18n "admin.wizard.subscription_container.title"}}</h3>
|
||||
<h4>{{i18n "admin.wizard.subscription_container.title"}}</h4>
|
||||
|
||||
<a href="/admin/wizards/subscription" title={{i18n subscribedTitle}}>
|
||||
<a href={{subscriptionLink}} title={{i18n subscribedTitle}}>
|
||||
{{d-icon subscribedIcon}}
|
||||
{{i18n subscribedLabel}}
|
||||
</a>
|
|
@ -7,8 +7,10 @@
|
|||
shouldDisplayClearableButton=shouldDisplayClearableButton
|
||||
}}
|
||||
|
||||
{{#if selectedContent.subscription}}
|
||||
<span class="subscription-label">{{i18n "admin.wizard.subscription.label"}}</span>
|
||||
{{#if selectedContent.subscriptionType}}
|
||||
<span class="subscription-label">
|
||||
{{selectedContent.subscriptionType}}
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
{{d-icon caretIcon class="caret-icon"}}
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
|
||||
<div class="texts">
|
||||
<span class="name">{{html-safe label}}</span>
|
||||
{{#if item.subscription}}
|
||||
{{#if item.subscriptionType}}
|
||||
<span class="subscription-label">
|
||||
{{item.subscription}}
|
||||
{{item.subscriptionType}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -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}}
|
|
@ -1,17 +1,19 @@
|
|||
// discourse-skip-module
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
document.body.insertAdjacentHTML(
|
||||
"afterbegin",
|
||||
`
|
||||
<div id="ember-testing-container"><div id="ember-testing"></div></div>
|
||||
<style>#ember-testing-container { position: absolute; background: white; bottom: 0; right: 0; width: 640px; height: 384px; overflow: auto; z-index: 9999; border: 1px solid #ccc; } #ember-testing { zoom: 50%; }</style>
|
||||
`
|
||||
);
|
||||
});
|
||||
if (window.location.pathname.indexOf("/w/") > -1 && Ember.testing) {
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
document.body.insertAdjacentHTML(
|
||||
"afterbegin",
|
||||
`
|
||||
<div id="ember-testing-container"><div id="ember-testing"></div></div>
|
||||
<style>#ember-testing-container { position: absolute; background: white; bottom: 0; right: 0; width: 640px; height: 384px; overflow: auto; z-index: 9999; border: 1px solid #ccc; } #ember-testing { zoom: 50%; }</style>
|
||||
`
|
||||
);
|
||||
});
|
||||
|
||||
Object.keys(requirejs.entries).forEach(function (entry) {
|
||||
if (/\-test/.test(entry)) {
|
||||
requirejs(entry);
|
||||
}
|
||||
});
|
||||
Object.keys(requirejs.entries).forEach(function (entry) {
|
||||
if (/\-test/.test(entry)) {
|
||||
requirejs(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -25,6 +25,14 @@ $error: #ef1700;
|
|||
}
|
||||
}
|
||||
|
||||
.admin-wizards .admin-actions {
|
||||
display: flex;
|
||||
|
||||
.btn-pavilion-support {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-message {
|
||||
background-color: var(--primary-low);
|
||||
width: 100%;
|
||||
|
@ -156,6 +164,16 @@ $error: #ef1700;
|
|||
@extend .wizard-settings-group;
|
||||
}
|
||||
|
||||
.admin-wizard-container.settings {
|
||||
.wizard-settings {
|
||||
.wizard-subscription-container {
|
||||
[class~="setting"] {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-custom-field {
|
||||
background: transparent;
|
||||
background-color: var(--primary-very-low);
|
||||
|
@ -802,84 +820,6 @@ $error: #ef1700;
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.admin-wizards-subscription {
|
||||
.admin-wizard-controls {
|
||||
h3,
|
||||
label {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
padding: 0.4em 0.5em;
|
||||
margin-left: 0.75em;
|
||||
background-color: var(--success);
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.loading-container {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-wizard-subscription {
|
||||
.title-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.buttons > span {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1em;
|
||||
background-color: var(--primary-very-low);
|
||||
|
||||
.subscription-state {
|
||||
padding: 0.25em 0.5em;
|
||||
margin-right: 0.75em;
|
||||
|
||||
&.active {
|
||||
background-color: var(--success);
|
||||
color: var(--secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-subscription-selector.select-kit.single-select {
|
||||
.select-kit-row {
|
||||
.texts {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
&.disabled {
|
||||
background: var(--primary-low);
|
||||
}
|
||||
}
|
||||
|
||||
.subscription-label {
|
||||
margin-left: 0.75em;
|
||||
padding-top: 0.25em;
|
||||
color: var(--tertiary);
|
||||
font-size: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.btn.btn-pavilion-support {
|
||||
background: var(--pavilion-primary);
|
||||
color: var(--pavilion-secondary);
|
||||
|
@ -899,7 +839,7 @@ $error: #ef1700;
|
|||
}
|
||||
}
|
||||
|
||||
.subscription-container {
|
||||
.wizard-subscription-container {
|
||||
width: 100%;
|
||||
padding: 1em;
|
||||
background-color: rgba($pavilion_primary, 0.1);
|
||||
|
@ -907,158 +847,79 @@ $error: #ef1700;
|
|||
.subscription-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1em;
|
||||
margin-bottom: 0.25em;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--pavilion-primary);
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.subscribed) .subscription-settings {
|
||||
filter: blur(1px);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.admin-wizards-notices {
|
||||
.wizard-table {
|
||||
overflow: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-notice {
|
||||
padding: 0.75em;
|
||||
margin-bottom: 1em;
|
||||
border: 1px solid var(--primary-low);
|
||||
|
||||
&.dismissed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.expired .notice-badge:not(.notice-expired-at),
|
||||
&.expired a,
|
||||
&.expired p {
|
||||
color: var(--primary-medium) !important;
|
||||
}
|
||||
|
||||
.notice-badge {
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
.notice-header {
|
||||
display: flex;
|
||||
|
||||
.notice-title {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.notice-header-right {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.notice-badge {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dismiss-notice-container,
|
||||
.hide-notice-container {
|
||||
width: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dismiss-notice,
|
||||
.hide-notice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.d-icon {
|
||||
margin-right: 0;
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notice-badge {
|
||||
.wizard-subscription-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 25px;
|
||||
max-height: 34px;
|
||||
box-sizing: border-box;
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.admin-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.wizard-notices-link {
|
||||
position: relative;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
padding: 0.5em 0.65em;
|
||||
background-color: rgba($primary-medium, 0.05);
|
||||
border: 1.5px solid rgba($primary-medium, 0.5);
|
||||
color: $primary-medium;
|
||||
|
||||
div > a {
|
||||
@include btn;
|
||||
color: var(--secondary) !important;
|
||||
background-color: var(--primary-medium);
|
||||
&:hover {
|
||||
color: $primary-medium;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--tertiary) !important;
|
||||
color: var(--secondary) !important;
|
||||
svg {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
margin-right: 0.45em;
|
||||
margin-bottom: 0.15em;
|
||||
}
|
||||
|
||||
&.standard {
|
||||
background-color: rgba($subscription_standard, 0.05);
|
||||
border: 1.5px solid rgba($subscription_standard, 0.5);
|
||||
color: $subscription_standard;
|
||||
}
|
||||
|
||||
&.business {
|
||||
background-color: $subscription_business;
|
||||
border: 1.5px solid $subscription_business;
|
||||
color: $secondary;
|
||||
}
|
||||
|
||||
.d-icon {
|
||||
margin-right: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-subscription-selector.select-kit.single-select {
|
||||
.select-kit-row {
|
||||
.texts {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
&.disabled {
|
||||
background: var(--primary-low);
|
||||
cursor: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.active-notice-count {
|
||||
background-color: $danger;
|
||||
color: $secondary;
|
||||
border-radius: 50%;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
a.show-notice-message {
|
||||
padding: 0.25em 0.5em;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.wizard-notice,
|
||||
.wizard-notice-row:not(.expired):not(.dismissed) {
|
||||
&.info {
|
||||
background-color: rgba($info, 0.1);
|
||||
border: 1px solid rgba($info, 0.5);
|
||||
}
|
||||
&.warning,
|
||||
&.connection-error {
|
||||
background-color: rgba($warning, 0.1);
|
||||
border: 1px solid rgba($warning, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.notice-message {
|
||||
position: relative;
|
||||
|
||||
.cooked-notice-message {
|
||||
background-color: var(--secondary);
|
||||
padding: 1em;
|
||||
z-index: 1;
|
||||
box-shadow: shadow("dropdown");
|
||||
border-top: 1px solid var(--primary-low);
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
.subscription-label {
|
||||
margin-left: 0.75em;
|
||||
padding-top: 0.25em;
|
||||
color: var(--pavilion-primary);
|
||||
font-size: 0.75em;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,13 @@ $pavilion_primary: #3c1c8c;
|
|||
$pavilion_secondary: #ffffff;
|
||||
$pavilion_warning: rgb(243, 163, 61);
|
||||
|
||||
$subscription_standard: $pavilion_primary;
|
||||
$subscription_business: #333;
|
||||
|
||||
:root {
|
||||
--pavilion-primary: #{$pavilion_primary};
|
||||
--pavilion-secondary: #{$pavilion_secondary};
|
||||
--pavilion-warning: #{$pavilion_warning};
|
||||
--subscription-standard: #{$subscription_standard};
|
||||
--subscription-business: #{$subscription_business};
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ en:
|
|||
value: "Value"
|
||||
profile: "profile"
|
||||
type: "Type"
|
||||
none: "Make a selection"
|
||||
none: "Make a selection"
|
||||
submission_key: 'submission key'
|
||||
param_key: 'param'
|
||||
group: "Group"
|
||||
|
@ -126,9 +126,9 @@ en:
|
|||
hide: "Hide"
|
||||
preview: "{{action}} Preview"
|
||||
popover: "{{action}} Fields"
|
||||
|
||||
|
||||
input:
|
||||
conditional:
|
||||
conditional:
|
||||
name: 'if'
|
||||
output: 'then'
|
||||
assignment:
|
||||
|
@ -137,7 +137,7 @@ en:
|
|||
name: 'map'
|
||||
validation:
|
||||
name: 'ensure'
|
||||
|
||||
|
||||
selector:
|
||||
label:
|
||||
text: "text"
|
||||
|
@ -175,7 +175,7 @@ en:
|
|||
dependent: "{{property}} is dependent on {{dependent}}"
|
||||
conflict: "{{type}} with {{property}} '{{value}}' already exists"
|
||||
after_time: "After time invalid"
|
||||
|
||||
|
||||
step:
|
||||
header: "Steps"
|
||||
title: "Title"
|
||||
|
@ -189,7 +189,7 @@ en:
|
|||
force_final:
|
||||
label: "Conditional Final Step"
|
||||
description: "Display this step as the final step if conditions on later steps have not passed when the user reaches this step."
|
||||
|
||||
|
||||
field:
|
||||
header: "Fields"
|
||||
label: "Label"
|
||||
|
@ -212,7 +212,7 @@ en:
|
|||
prefill: "Prefill"
|
||||
content: "Content"
|
||||
tag_groups: "Tag Groups"
|
||||
date_time_format:
|
||||
date_time_format:
|
||||
label: "Format"
|
||||
instructions: "<a href='https://momentjs.com/docs/#/displaying/format/' target='_blank'>Moment.js format</a>"
|
||||
validations:
|
||||
|
@ -229,7 +229,7 @@ en:
|
|||
weeks: "Weeks"
|
||||
months: "Months"
|
||||
years: "Years"
|
||||
|
||||
|
||||
type:
|
||||
text: "Text"
|
||||
textarea: Textarea
|
||||
|
@ -248,7 +248,7 @@ en:
|
|||
date: Date
|
||||
time: Time
|
||||
date_time: Date & Time
|
||||
|
||||
|
||||
connector:
|
||||
and: "and"
|
||||
or: "or"
|
||||
|
@ -262,7 +262,7 @@ en:
|
|||
regex: '=~'
|
||||
association: '→'
|
||||
is: 'is'
|
||||
|
||||
|
||||
action:
|
||||
header: "Actions"
|
||||
include: "Include Fields"
|
||||
|
@ -270,8 +270,8 @@ en:
|
|||
post: "Post"
|
||||
topic_attr: "Topic Attribute"
|
||||
interpolate_fields: "Insert wizard fields using the field_id in w{}. Insert user fields using field key in u{}."
|
||||
|
||||
run_after:
|
||||
|
||||
run_after:
|
||||
label: "Run After"
|
||||
wizard_completion: "Wizard Completion"
|
||||
custom_fields:
|
||||
|
@ -353,11 +353,11 @@ en:
|
|||
messageable_level: Messageable Level
|
||||
visibility_level: Visibility Level
|
||||
members_visibility_level: Members Visibility Level
|
||||
|
||||
|
||||
custom_field:
|
||||
nav_label: "Custom Fields"
|
||||
add: "Add"
|
||||
external:
|
||||
external:
|
||||
label: "from another plugin"
|
||||
title: "This custom field has been added by another plugin. You can use it in your wizards but you can't edit the field here."
|
||||
name:
|
||||
|
@ -386,7 +386,7 @@ en:
|
|||
basic_category: "Category"
|
||||
basic_group: "Group"
|
||||
post: "Post"
|
||||
|
||||
|
||||
submissions:
|
||||
nav_label: "Submissions"
|
||||
title: "{{name}} Submissions"
|
||||
|
@ -468,59 +468,22 @@ en:
|
|||
|
||||
subscription_container:
|
||||
title: Subscriber Features
|
||||
subscribed:
|
||||
subscribed:
|
||||
label: Subscribed
|
||||
title: You're subscribed and can use these features
|
||||
not_subscribed:
|
||||
label: Not Subscribed
|
||||
title: Subscribe to use these features
|
||||
|
||||
subscription:
|
||||
nav_label: Subscription
|
||||
label: In subscription
|
||||
additional_label: "Add subscription"
|
||||
title: Custom Wizard Subscription
|
||||
authorize: Authorize
|
||||
authorized: Authorized
|
||||
unauthorize: cancel
|
||||
not_subscribed: You're not currently subscribed
|
||||
subscription:
|
||||
title:
|
||||
standard: Standard Subscription
|
||||
business: Business Subscription
|
||||
status:
|
||||
active: Active
|
||||
inactive: Inactive
|
||||
update: Update
|
||||
last_updated: Last updated
|
||||
|
||||
notice:
|
||||
plugin: Custom Wizard Plugin
|
||||
message: Message
|
||||
time: Time
|
||||
status: Status
|
||||
title: Title
|
||||
dismiss:
|
||||
label: Dismiss
|
||||
title: Dismiss notice
|
||||
dismiss_all:
|
||||
label: Dismiss All
|
||||
title: Dismiss all informational Custom Wizard notices
|
||||
confirm: Are you sure you want to dismiss all informational Custom Wizard notices?
|
||||
active: active
|
||||
created_at: issued
|
||||
updated_at: updated
|
||||
expired_at: expired
|
||||
dismissed_at: dismissed
|
||||
type:
|
||||
label: Type
|
||||
info: Information
|
||||
warning: Warning
|
||||
connection_error: Connection Error
|
||||
|
||||
notices:
|
||||
nav_label: Notices
|
||||
title: Plugin Notices
|
||||
none:
|
||||
label: Not Subscribed
|
||||
title: There is no Custom Wizard subscription active on this forum.
|
||||
business:
|
||||
label: Business
|
||||
title: There is a Custom Wizard Business subscription active on this forum.
|
||||
standard:
|
||||
label: Standard
|
||||
title: There is a Custom Wizard Standard subscription active on this forum.
|
||||
|
||||
wizard_js:
|
||||
group:
|
||||
|
@ -636,7 +599,7 @@ en:
|
|||
yourself_confirm:
|
||||
title: "Did you forget to add recipients?"
|
||||
body: "Right now this message is only being sent to yourself!"
|
||||
|
||||
|
||||
realtime_validations:
|
||||
similar_topics:
|
||||
insufficient_characters: "Type a minimum 5 characters to start looking for similar topics"
|
||||
|
|
|
@ -53,20 +53,7 @@ en:
|
|||
after_signup_after_time: "You can't use 'after time' and 'after signup' on the same wizard."
|
||||
after_time: "After time setting is invalid."
|
||||
liquid_syntax_error: "Liquid syntax error in %{attribute}: %{message}"
|
||||
|
||||
notice:
|
||||
connection_error: "Failed to connect to http://%{domain}"
|
||||
compatibility_issue:
|
||||
title: The Custom Wizard Plugin is incompatibile with the latest version of Discourse.
|
||||
message: Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse.
|
||||
plugin_status:
|
||||
connection_error:
|
||||
title: Unable to connect to the Custom Wizard Plugin status server
|
||||
message: Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse. If this issue persists contact <a href="mailto:support@thepavilion.io">support@thepavilion.io</a> for further assistance.
|
||||
subscription_message:
|
||||
connection_error:
|
||||
title: Unable to connect to the Custom Wizard Plugin subscription server
|
||||
message: If this issue persists contact <a href="mailto:support@thepavilion.io">support@thepavilion.io</a> for further assistance.
|
||||
subscription: "%{type} %{property} is subscription only"
|
||||
|
||||
site_settings:
|
||||
custom_wizard_enabled: "Enable custom wizards."
|
||||
|
|
|
@ -45,17 +45,5 @@ Discourse::Application.routes.append do
|
|||
get 'admin/wizards/manager/export' => 'admin_manager#export'
|
||||
post 'admin/wizards/manager/import' => 'admin_manager#import'
|
||||
delete 'admin/wizards/manager/destroy' => 'admin_manager#destroy'
|
||||
|
||||
get 'admin/wizards/subscription' => 'admin_subscription#index'
|
||||
post 'admin/wizards/subscription' => 'admin_subscription#update_subscription'
|
||||
get 'admin/wizards/subscription/authorize' => 'admin_subscription#authorize'
|
||||
get 'admin/wizards/subscription/authorize/callback' => 'admin_subscription#authorize_callback'
|
||||
delete 'admin/wizards/subscription/authorize' => 'admin_subscription#destroy_authentication'
|
||||
|
||||
get 'admin/wizards/notice' => 'admin_notice#index'
|
||||
put 'admin/wizards/notice/:notice_id/dismiss' => 'admin_notice#dismiss'
|
||||
put 'admin/wizards/notice/:notice_id/hide' => 'admin_notice#hide'
|
||||
put 'admin/wizards/notice/dismiss' => 'admin_notice#dismiss_all'
|
||||
get 'admin/wizards/notices' => 'admin_notice#index'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
plugins:
|
||||
custom_wizard_enabled:
|
||||
custom_wizard_enabled:
|
||||
default: true
|
||||
client: true
|
||||
wizard_redirect_exclude_paths:
|
||||
|
|
|
@ -84,7 +84,7 @@ class ::CustomWizard::CustomField
|
|||
next
|
||||
end
|
||||
|
||||
if attr == 'klass' && @subscription.requires_additional_subscription("custom_fields", "klass").include?(value)
|
||||
if attr == 'klass' && !@subscription.includes?(:custom_field, :klass, value)
|
||||
add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value))
|
||||
end
|
||||
|
||||
|
@ -99,7 +99,7 @@ class ::CustomWizard::CustomField
|
|||
add_error(I18n.t("#{i18n_key}.unsupported_type", type: value))
|
||||
end
|
||||
|
||||
if attr == 'type' && @subscription.requires_additional_subscription("custom_fields", "type").include?(value)
|
||||
if attr == 'type' && !@subscription.includes?(:custom_field, :type, value)
|
||||
add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value))
|
||||
end
|
||||
|
||||
|
|
|
@ -47,7 +47,6 @@ class CustomWizard::Mapper
|
|||
@data = params[:data] || {}
|
||||
@user = params[:user]
|
||||
@opts = params[:opts] || {}
|
||||
@subscription = CustomWizard::Subscription.new
|
||||
end
|
||||
|
||||
def perform
|
||||
|
@ -252,7 +251,7 @@ class CustomWizard::Mapper
|
|||
end
|
||||
end
|
||||
|
||||
if opts[:template] && @subscription.subscribed?
|
||||
if opts[:template] && CustomWizard::Subscription.subscribed?
|
||||
template = Liquid::Template.parse(string)
|
||||
string = template.render(data)
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -1,240 +1,170 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CustomWizard::Subscription
|
||||
include ActiveModel::Serialization
|
||||
STANDARD_PRODUCT_ID = 'prod_LNAGVAaIqDsHmB'
|
||||
BUSINESS_PRODUCT_ID = 'prod_LNABQ50maBQ1pY'
|
||||
|
||||
attr_accessor :authentication,
|
||||
:subscription
|
||||
|
||||
SUBSCRIPTION_LEVELS = {
|
||||
standard: {
|
||||
actions: ["send_message", "add_to_group", "watch_categories"],
|
||||
custom_fields: {
|
||||
klass: [],
|
||||
type: ["json"],
|
||||
def self.attributes
|
||||
{
|
||||
wizard: {
|
||||
required: {
|
||||
none: [],
|
||||
standard: ['*'],
|
||||
business: ['*']
|
||||
},
|
||||
permitted: {
|
||||
none: [],
|
||||
standard: ['*'],
|
||||
business: ['*']
|
||||
}
|
||||
},
|
||||
},
|
||||
business: {
|
||||
actions: ["create_category", "create_group", "send_to_api"],
|
||||
custom_fields: {
|
||||
klass: ["group", "category"],
|
||||
type: [],
|
||||
step: {
|
||||
condition: {
|
||||
none: [],
|
||||
standard: ['*'],
|
||||
business: ['*']
|
||||
},
|
||||
index: {
|
||||
none: [],
|
||||
standard: ['*'],
|
||||
business: ['*']
|
||||
},
|
||||
required_data: {
|
||||
none: [],
|
||||
standard: ['*'],
|
||||
business: ['*']
|
||||
},
|
||||
permitted_params: {
|
||||
none: [],
|
||||
standard: ['*'],
|
||||
business: ['*']
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
field: {
|
||||
condition: {
|
||||
none: [],
|
||||
standard: ['*'],
|
||||
business: ['*']
|
||||
},
|
||||
index: {
|
||||
none: [],
|
||||
standard: ['*'],
|
||||
business: ['*']
|
||||
},
|
||||
type: {
|
||||
none: ['text', 'textarea', 'text_only', 'date', 'time', 'date_time', 'number', 'checkbox', 'dropdown', 'upload'],
|
||||
standard: ['*'],
|
||||
business: ['*']
|
||||
},
|
||||
realtime_validations: {
|
||||
none: [],
|
||||
standard: ['*'],
|
||||
business: ['*']
|
||||
}
|
||||
},
|
||||
action: {
|
||||
type: {
|
||||
none: ['create_topic', 'update_profile', 'open_composer', 'route_to'],
|
||||
standard: ['create_topic', 'update_profile', 'open_composer', 'route_to', 'send_message', 'watch_categories', 'add_to_group'],
|
||||
business: ['*']
|
||||
}
|
||||
},
|
||||
custom_field: {
|
||||
klass: {
|
||||
none: ['topic', 'post'],
|
||||
standard: ['topic', 'post'],
|
||||
business: ['*']
|
||||
},
|
||||
type: {
|
||||
none: ['string', 'boolean', 'integer'],
|
||||
standard: ['string', 'boolean', 'integer'],
|
||||
business: ['*']
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def initialize
|
||||
@authentication = CustomWizard::Subscription::Authentication.new(get_authentication)
|
||||
@subscription = CustomWizard::Subscription::Subscription.new(get_subscription)
|
||||
@subscription = find_subscription
|
||||
end
|
||||
|
||||
def authorized?
|
||||
@authentication.active?
|
||||
end
|
||||
def includes?(feature, attribute, value)
|
||||
attributes = self.class.attributes[feature]
|
||||
|
||||
def subscribed?
|
||||
@subscription.active?
|
||||
end
|
||||
## Attribute is not part of a subscription
|
||||
return true unless attributes.present? && attributes.key?(attribute)
|
||||
|
||||
def server
|
||||
"test.thepavilion.io"
|
||||
end
|
||||
values = attributes[attribute][type]
|
||||
|
||||
def subscription_type
|
||||
"stripe"
|
||||
## Subscription type does not support the attribute.
|
||||
return false if values.blank?
|
||||
|
||||
## Subscription type supports all values of the attribute.
|
||||
return true if values.first === "*"
|
||||
|
||||
## Subscription type supports some values of the attributes.
|
||||
values.include?(value)
|
||||
end
|
||||
|
||||
def type
|
||||
@subscription.type
|
||||
return :none unless subscribed?
|
||||
return :standard if standard?
|
||||
return :business if business?
|
||||
end
|
||||
|
||||
def client_name
|
||||
"custom-wizard"
|
||||
def subscribed?
|
||||
standard? || business?
|
||||
end
|
||||
|
||||
def scope
|
||||
"discourse-subscription-server:user_subscription"
|
||||
def standard?
|
||||
@subscription.product_id === STANDARD_PRODUCT_ID
|
||||
end
|
||||
|
||||
def requires_additional_subscription(kategory, sub_kategory)
|
||||
case kategory
|
||||
when "actions"
|
||||
case self.type
|
||||
when "business"
|
||||
[]
|
||||
when "standard"
|
||||
SUBSCRIPTION_LEVELS[:business][kategory.to_sym]
|
||||
else
|
||||
SUBSCRIPTION_LEVELS[:standard][kategory.to_sym] + SUBSCRIPTION_LEVELS[:business][kategory.to_sym]
|
||||
end
|
||||
when "custom_fields"
|
||||
case self.type
|
||||
when "business"
|
||||
[]
|
||||
when "standard"
|
||||
SUBSCRIPTION_LEVELS[:business][kategory.to_sym][sub_kategory.to_sym]
|
||||
else
|
||||
SUBSCRIPTION_LEVELS[:standard][kategory.to_sym][sub_kategory.to_sym] + SUBSCRIPTION_LEVELS[:business][kategory.to_sym][sub_kategory.to_sym]
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
def business?
|
||||
@subscription.product_id === BUSINESS_PRODUCT_ID
|
||||
end
|
||||
|
||||
def update
|
||||
if @authentication.active?
|
||||
response = Excon.get(
|
||||
"https://#{server}/subscription-server/user-subscriptions/#{subscription_type}/#{client_name}",
|
||||
headers: {
|
||||
"User-Api-Key" => @authentication.api_key
|
||||
}
|
||||
)
|
||||
def client_installed?
|
||||
defined?(SubscriptionClient) == 'constant' && SubscriptionClient.class == Module
|
||||
end
|
||||
|
||||
if response.status == 200
|
||||
begin
|
||||
data = JSON.parse(response.body).deep_symbolize_keys
|
||||
rescue JSON::ParserError
|
||||
return false
|
||||
end
|
||||
def find_subscription
|
||||
subscription = nil
|
||||
|
||||
return false unless data && data.is_a?(Hash)
|
||||
subscriptions = data[:subscriptions]
|
||||
|
||||
if subscriptions.present? && type = subscriptions.first[:price_nickname]
|
||||
@subscription = set_subscription(type)
|
||||
return true
|
||||
end
|
||||
end
|
||||
if client_installed?
|
||||
subscription = SubscriptionClientSubscription.active
|
||||
.where(product_id: [STANDARD_PRODUCT_ID, BUSINESS_PRODUCT_ID])
|
||||
.order("product_id = '#{BUSINESS_PRODUCT_ID}' DESC")
|
||||
.first
|
||||
end
|
||||
|
||||
destroy_subscription
|
||||
false
|
||||
end
|
||||
|
||||
def destroy_subscription
|
||||
if remove_subscription
|
||||
@subscription = CustomWizard::Subscription::Subscription.new(get_subscription)
|
||||
!@subscription.active?
|
||||
else
|
||||
false
|
||||
unless subscription
|
||||
subscription = OpenStruct.new(product_id: nil)
|
||||
end
|
||||
end
|
||||
|
||||
def authentication_url(user_id, request_id)
|
||||
keys = @authentication.generate_keys(user_id, request_id)
|
||||
params = {
|
||||
public_key: keys.public_key,
|
||||
nonce: keys.nonce,
|
||||
client_id: @authentication.client_id,
|
||||
auth_redirect: "#{Discourse.base_url}/admin/wizards/subscription/authorize/callback",
|
||||
application_name: SiteSetting.title,
|
||||
scopes: scope
|
||||
}
|
||||
|
||||
uri = URI.parse("https://#{server}/user-api-key/new")
|
||||
uri.query = URI.encode_www_form(params)
|
||||
uri.to_s
|
||||
end
|
||||
|
||||
def authentication_response(request_id, payload)
|
||||
data = @authentication.decrypt_payload(request_id, payload)
|
||||
return false unless data.is_a?(Hash) && data[:key] && data[:user_id]
|
||||
|
||||
api_key = data[:key]
|
||||
user_id = data[:user_id]
|
||||
user = User.find(user_id)
|
||||
|
||||
if user&.admin
|
||||
@authentication = set_authentication(api_key, user.id)
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_authentication
|
||||
if remove_authentication
|
||||
@authentication = CustomWizard::Subscription::Authentication.new(get_authentication)
|
||||
!@authentication.active?
|
||||
else
|
||||
false
|
||||
end
|
||||
subscription
|
||||
end
|
||||
|
||||
def self.subscribed?
|
||||
self.new.subscribed?
|
||||
new.subscribed?
|
||||
end
|
||||
|
||||
def self.business?
|
||||
new.business?
|
||||
end
|
||||
|
||||
def self.standard?
|
||||
new.standard?
|
||||
end
|
||||
|
||||
def self.type
|
||||
self.new.type
|
||||
new.type
|
||||
end
|
||||
|
||||
def self.requires_additional_subscription(kategory, sub_kategory)
|
||||
self.new.requires_additional_subscription(kategory, sub_kategory)
|
||||
def self.client_installed?
|
||||
new.client_installed?
|
||||
end
|
||||
|
||||
def self.authorized?
|
||||
self.new.authorized?
|
||||
end
|
||||
|
||||
def self.update
|
||||
self.new.update
|
||||
end
|
||||
|
||||
def self.namespace
|
||||
"custom_wizard_subscription"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def subscription_db_key
|
||||
"subscription"
|
||||
end
|
||||
|
||||
def authentication_db_key
|
||||
"authentication"
|
||||
end
|
||||
|
||||
def get_subscription
|
||||
raw = PluginStore.get(self.class.namespace, subscription_db_key)
|
||||
|
||||
if raw.present?
|
||||
OpenStruct.new(
|
||||
type: raw['type'],
|
||||
updated_at: raw['updated_at']
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def remove_subscription
|
||||
PluginStore.remove(self.class.namespace, subscription_db_key)
|
||||
end
|
||||
|
||||
def set_subscription(type)
|
||||
PluginStore.set(CustomWizard::Subscription.namespace, subscription_db_key, type: type, updated_at: Time.now)
|
||||
CustomWizard::Subscription::Subscription.new(get_subscription)
|
||||
end
|
||||
|
||||
def get_authentication
|
||||
raw = PluginStore.get(self.class.namespace, authentication_db_key)
|
||||
OpenStruct.new(
|
||||
key: raw && raw['key'],
|
||||
auth_by: raw && raw['auth_by'],
|
||||
auth_at: raw && raw['auth_at']
|
||||
)
|
||||
end
|
||||
|
||||
def set_authentication(key, user_id)
|
||||
PluginStore.set(self.class.namespace, authentication_db_key,
|
||||
key: key,
|
||||
auth_by: user_id,
|
||||
auth_at: Time.now
|
||||
)
|
||||
CustomWizard::Subscription::Authentication.new(get_authentication)
|
||||
end
|
||||
|
||||
def remove_authentication
|
||||
PluginStore.remove(self.class.namespace, authentication_db_key)
|
||||
get_authentication
|
||||
def self.includes?(feature, attribute, value)
|
||||
new.includes?(feature, attribute, value)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -54,34 +54,6 @@ class CustomWizard::TemplateValidator
|
|||
}
|
||||
end
|
||||
|
||||
def self.subscription
|
||||
{
|
||||
wizard: {
|
||||
save_submissions: 'false',
|
||||
restart_on_revisit: 'true',
|
||||
},
|
||||
step: {
|
||||
condition: 'present',
|
||||
index: 'conditional',
|
||||
required_data: 'present',
|
||||
permitted_params: 'present'
|
||||
},
|
||||
field: {
|
||||
condition: 'present',
|
||||
index: 'conditional'
|
||||
},
|
||||
action: {
|
||||
type: %w[
|
||||
send_message
|
||||
add_to_group
|
||||
create_category
|
||||
create_group
|
||||
send_to_api
|
||||
]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_required(object, type)
|
||||
|
@ -93,16 +65,10 @@ class CustomWizard::TemplateValidator
|
|||
end
|
||||
|
||||
def validate_subscription(object, type)
|
||||
self.class.subscription[type].each do |property, subscription_type|
|
||||
val = object[property.to_s]
|
||||
is_subscription = (val != nil) && (
|
||||
subscription_type === 'present' && val.present? ||
|
||||
(['true', 'false'].include?(subscription_type) && cast_bool(val) == cast_bool(subscription_type)) ||
|
||||
(subscription_type === 'conditional' && val.is_a?(Hash)) ||
|
||||
(subscription_type.is_a?(Array) && subscription_type.include?(val))
|
||||
)
|
||||
object.keys.each do |property|
|
||||
value = object[property]
|
||||
|
||||
if is_subscription && !@subscription.subscribed?
|
||||
if !@subscription.includes?(type, property.to_sym, value)
|
||||
errors.add :base, I18n.t("wizard.validation.subscription", type: type.to_s, property: property)
|
||||
end
|
||||
end
|
||||
|
@ -148,10 +114,6 @@ class CustomWizard::TemplateValidator
|
|||
end
|
||||
end
|
||||
|
||||
def cast_bool(val)
|
||||
ActiveRecord::Type::Boolean.new.cast(val)
|
||||
end
|
||||
|
||||
def validate_liquid_template(object, type)
|
||||
%w[
|
||||
description
|
||||
|
|
46
plugin.rb
46
plugin.rb
|
@ -5,17 +5,13 @@
|
|||
# authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George
|
||||
# contact_emails: support@thepavilion.io
|
||||
# url: https://github.com/paviliondev/discourse-custom-wizard
|
||||
# subscription_url: https://coop.pavilion.tech
|
||||
|
||||
gem 'liquid', '5.0.1', require: true
|
||||
register_asset 'stylesheets/admin/admin.scss', :desktop
|
||||
|
||||
enabled_site_setting :custom_wizard_enabled
|
||||
|
||||
config = Rails.application.config
|
||||
plugin_asset_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets"
|
||||
config.assets.paths << "#{plugin_asset_path}/javascripts"
|
||||
config.assets.paths << "#{plugin_asset_path}/stylesheets/wizard"
|
||||
|
||||
if Rails.env.production?
|
||||
config.assets.precompile += %w{
|
||||
wizard-custom-guest.js
|
||||
|
@ -59,6 +55,24 @@ class ::Sprockets::DirectiveProcessor
|
|||
end
|
||||
end
|
||||
|
||||
## Override necessary due to 'assets/javascripts/wizard', particularly its tests.
|
||||
def each_globbed_asset
|
||||
if @path
|
||||
root_path = "#{File.dirname(@path)}/assets/javascripts/discourse"
|
||||
|
||||
Dir.glob(["#{root_path}/**/*"]).sort.each do |f|
|
||||
f_str = f.to_s
|
||||
if File.directory?(f)
|
||||
yield [f, true]
|
||||
elsif f_str.end_with?(".js.es6") || f_str.end_with?(".hbs") || f_str.end_with?(".hbr")
|
||||
yield [f, false]
|
||||
elsif transpile_js && f_str.end_with?(".js")
|
||||
yield [f, false]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
after_initialize do
|
||||
%w[
|
||||
../lib/custom_wizard/engine.rb
|
||||
|
@ -70,15 +84,11 @@ after_initialize do
|
|||
../app/controllers/custom_wizard/admin/logs.rb
|
||||
../app/controllers/custom_wizard/admin/manager.rb
|
||||
../app/controllers/custom_wizard/admin/custom_fields.rb
|
||||
../app/controllers/custom_wizard/admin/subscription.rb
|
||||
../app/controllers/custom_wizard/admin/notice.rb
|
||||
../app/controllers/custom_wizard/wizard.rb
|
||||
../app/controllers/custom_wizard/steps.rb
|
||||
../app/controllers/custom_wizard/realtime_validations.rb
|
||||
../app/jobs/regular/refresh_api_access_token.rb
|
||||
../app/jobs/regular/set_after_time_wizard.rb
|
||||
../app/jobs/scheduled/custom_wizard/update_subscription.rb
|
||||
../app/jobs/scheduled/custom_wizard/update_notices.rb
|
||||
../lib/custom_wizard/validators/template.rb
|
||||
../lib/custom_wizard/validators/update.rb
|
||||
../lib/custom_wizard/action_result.rb
|
||||
|
@ -95,13 +105,9 @@ after_initialize do
|
|||
../lib/custom_wizard/step_updater.rb
|
||||
../lib/custom_wizard/step.rb
|
||||
../lib/custom_wizard/submission.rb
|
||||
../lib/custom_wizard/subscription.rb
|
||||
../lib/custom_wizard/template.rb
|
||||
../lib/custom_wizard/wizard.rb
|
||||
../lib/custom_wizard/notice.rb
|
||||
../lib/custom_wizard/notice/connection_error.rb
|
||||
../lib/custom_wizard/subscription.rb
|
||||
../lib/custom_wizard/subscription/subscription.rb
|
||||
../lib/custom_wizard/subscription/authentication.rb
|
||||
../lib/custom_wizard/api/api.rb
|
||||
../lib/custom_wizard/api/authorization.rb
|
||||
../lib/custom_wizard/api/endpoint.rb
|
||||
|
@ -122,10 +128,6 @@ after_initialize do
|
|||
../app/serializers/custom_wizard/log_serializer.rb
|
||||
../app/serializers/custom_wizard/submission_serializer.rb
|
||||
../app/serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb
|
||||
../app/serializers/custom_wizard/subscription/authentication_serializer.rb
|
||||
../app/serializers/custom_wizard/subscription/subscription_serializer.rb
|
||||
../app/serializers/custom_wizard/subscription_serializer.rb
|
||||
../app/serializers/custom_wizard/notice_serializer.rb
|
||||
../lib/custom_wizard/extensions/extra_locales_controller.rb
|
||||
../lib/custom_wizard/extensions/invites_controller.rb
|
||||
../lib/custom_wizard/extensions/users_controller.rb
|
||||
|
@ -271,14 +273,6 @@ after_initialize do
|
|||
"#{serializer_klass}_serializer".classify.constantize.prepend CustomWizardCustomFieldSerializer
|
||||
end
|
||||
|
||||
AdminDashboardData.add_problem_check do
|
||||
warning_notices = CustomWizard::Notice.list(
|
||||
type: CustomWizard::Notice.types[:warning],
|
||||
archetype: CustomWizard::Notice.archetypes[:plugin_status]
|
||||
)
|
||||
warning_notices.any? ? ActionView::Base.full_sanitizer.sanitize(warning_notices.first.message, tags: %w(a)) : nil
|
||||
end
|
||||
|
||||
reloadable_patch do |plugin|
|
||||
::TagsController.prepend CustomWizardTagsController
|
||||
::DiscourseTagging.singleton_class.prepend CustomWizardDiscourseTagging
|
||||
|
|
|
@ -8,6 +8,7 @@ describe CustomWizard::Action do
|
|||
let(:wizard_template) { get_wizard_fixture("wizard") }
|
||||
let(:open_composer) { get_wizard_fixture("actions/open_composer") }
|
||||
let(:create_category) { get_wizard_fixture("actions/create_category") }
|
||||
let(:watch_categories) { get_wizard_fixture("actions/watch_categories") }
|
||||
let(:create_group) { get_wizard_fixture("actions/create_group") }
|
||||
let(:add_to_group) { get_wizard_fixture("actions/add_to_group") }
|
||||
let(:send_message) { get_wizard_fixture("actions/send_message") }
|
||||
|
@ -158,17 +159,6 @@ describe CustomWizard::Action do
|
|||
end
|
||||
end
|
||||
|
||||
it 'watches categories' do
|
||||
wizard = CustomWizard::Builder.new(@template[:id], user).build
|
||||
wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update
|
||||
wizard.create_updater(wizard.steps[1].id, {}).update
|
||||
|
||||
expect(CategoryUser.where(
|
||||
category_id: category.id,
|
||||
user_id: user.id
|
||||
).first.notification_level).to eq(0)
|
||||
end
|
||||
|
||||
it 're-routes a user' do
|
||||
wizard = CustomWizard::Builder.new(@template[:id], user).build
|
||||
updater = wizard.create_updater(wizard.steps.last.id, {})
|
||||
|
@ -176,11 +166,25 @@ describe CustomWizard::Action do
|
|||
expect(updater.result[:redirect_on_next]).to eq("https://google.com")
|
||||
end
|
||||
|
||||
context "subscription actions" do
|
||||
context "standard subscription actions" do
|
||||
before do
|
||||
enable_subscription("standard")
|
||||
end
|
||||
|
||||
it 'watches categories' do
|
||||
watch_categories[:categories][0][:output] = category.id
|
||||
wizard_template[:actions] << watch_categories
|
||||
update_template(wizard_template)
|
||||
|
||||
wizard = CustomWizard::Builder.new(@template[:id], user).build
|
||||
wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update
|
||||
|
||||
expect(CategoryUser.where(
|
||||
category_id: category.id,
|
||||
user_id: user.id
|
||||
).first.notification_level).to eq(2)
|
||||
end
|
||||
|
||||
it '#send_message' do
|
||||
wizard_template['actions'] << send_message
|
||||
update_template(wizard_template)
|
||||
|
@ -233,9 +237,16 @@ describe CustomWizard::Action do
|
|||
expect(topic.first.allowed_groups.map(&:name)).to include('cool_group', 'cool_group_1')
|
||||
expect(post.exists?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "business subscription actions" do
|
||||
before do
|
||||
enable_subscription("business")
|
||||
end
|
||||
|
||||
it '#create_category' do
|
||||
wizard_template['actions'] << create_category
|
||||
wizard_template['actions'] << create_group
|
||||
update_template(wizard_template)
|
||||
|
||||
wizard = CustomWizard::Builder.new(@template[:id], user).build
|
||||
|
|
|
@ -100,6 +100,7 @@ describe CustomWizard::Builder do
|
|||
|
||||
context "with restricted permissions" do
|
||||
before do
|
||||
enable_subscription("standard")
|
||||
@template[:permitted] = permitted_json["permitted"]
|
||||
CustomWizard::Template.save(@template.as_json)
|
||||
end
|
||||
|
@ -304,7 +305,7 @@ describe CustomWizard::Builder do
|
|||
before do
|
||||
enable_subscription("standard")
|
||||
@template[:steps][0][:fields][0][:condition] = user_condition_json['condition']
|
||||
@template[:steps][2][:fields][5][:condition] = boolean_field_condition_json['condition']
|
||||
@template[:steps][2][:fields][0][:condition] = boolean_field_condition_json['condition']
|
||||
CustomWizard::Template.save(@template.as_json)
|
||||
end
|
||||
|
||||
|
@ -325,7 +326,7 @@ describe CustomWizard::Builder do
|
|||
|
||||
builder = CustomWizard::Builder.new(@template[:id], user)
|
||||
wizard = builder.build
|
||||
expect(wizard.steps.last.fields.last.id).to eq(@template[:steps][2][:fields][5]['id'])
|
||||
expect(wizard.steps.last.fields.last.id).to eq(@template[:steps][2][:fields][0]['id'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -1,125 +1,104 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../plugin_helper'
|
||||
|
||||
describe CustomWizard::Subscription do
|
||||
fab!(:user) { Fabricate(:user) }
|
||||
|
||||
it "initializes subscription authentication and subscription" do
|
||||
subscription = described_class.new
|
||||
expect(subscription.authentication.class).to eq(CustomWizard::Subscription::Authentication)
|
||||
expect(subscription.subscription.class).to eq(CustomWizard::Subscription::Subscription)
|
||||
def undefine_client_classes
|
||||
Object.send(:remove_const, :SubscriptionClient) if Object.constants.include?(:SubscriptionClient)
|
||||
Object.send(:remove_const, :SubscriptionClientSubscription) if Object.constants.include?(:SubscriptionClientSubscription)
|
||||
end
|
||||
|
||||
it "returns authorized and subscribed states" do
|
||||
subscription = described_class.new
|
||||
expect(subscription.authorized?).to eq(false)
|
||||
expect(subscription.subscribed?).to eq(false)
|
||||
def define_client_classes
|
||||
load File.expand_path("#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/subscription_client.rb", __FILE__)
|
||||
end
|
||||
|
||||
context "subscription" do
|
||||
def stub_client_methods
|
||||
[:active, :where, :order, :first].each do |method|
|
||||
SubscriptionClientSubscription.stubs(method)
|
||||
.returns(SubscriptionClientSubscription)
|
||||
end
|
||||
SubscriptionClientSubscription.stubs(:product_id).returns(SecureRandom.hex(8))
|
||||
end
|
||||
|
||||
after do
|
||||
undefine_client_classes
|
||||
end
|
||||
|
||||
it "detects the subscription client" do
|
||||
expect(described_class.client_installed?).to eq(false)
|
||||
end
|
||||
|
||||
context "without a subscription client" do
|
||||
it "is not subscribed" do
|
||||
expect(described_class.subscribed?).to eq(false)
|
||||
end
|
||||
|
||||
it "has none type" do
|
||||
subscription = described_class.new
|
||||
expect(subscription.type).to eq(:none)
|
||||
end
|
||||
|
||||
it "non subscriber features are included" do
|
||||
expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true)
|
||||
end
|
||||
|
||||
it "ubscriber features are not included" do
|
||||
expect(described_class.includes?(:wizard, :permitted, {})).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context "with subscription client" do
|
||||
before do
|
||||
@subscription = described_class.new
|
||||
define_client_classes
|
||||
stub_client_methods
|
||||
end
|
||||
|
||||
it "updates valid subscriptions" do
|
||||
stub_subscription_request(200, valid_subscription)
|
||||
expect(@subscription.update).to eq(true)
|
||||
expect(@subscription.subscribed?).to eq(true)
|
||||
it "detects the subscription client" do
|
||||
expect(described_class.client_installed?).to eq(true)
|
||||
end
|
||||
|
||||
it "handles invalid subscriptions" do
|
||||
stub_subscription_request(200, invalid_subscription)
|
||||
expect(@subscription.update).to eq(false)
|
||||
expect(@subscription.subscribed?).to eq(false)
|
||||
context "without a subscription" do
|
||||
it "has none type" do
|
||||
expect(described_class.type).to eq(:none)
|
||||
end
|
||||
|
||||
it "non subscriber features are included" do
|
||||
expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true)
|
||||
end
|
||||
|
||||
it "subscriber features are not included" do
|
||||
expect(described_class.includes?(:wizard, :permitted, {})).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
it "handles subscription http errors" do
|
||||
stub_subscription_request(404, {})
|
||||
expect(@subscription.update).to eq(false)
|
||||
expect(@subscription.subscribed?).to eq(false)
|
||||
context "with standard subscription" do
|
||||
before do
|
||||
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID)
|
||||
end
|
||||
|
||||
it "detects standard type" do
|
||||
expect(described_class.type).to eq(:standard)
|
||||
end
|
||||
|
||||
it "standard features are included" do
|
||||
expect(described_class.includes?(:wizard, :type, 'send_message')).to eq(true)
|
||||
end
|
||||
|
||||
it "business features are not included" do
|
||||
expect(described_class.includes?(:action, :type, 'create_category')).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
it "destroys subscriptions" do
|
||||
stub_subscription_request(200, valid_subscription)
|
||||
expect(@subscription.update).to eq(true)
|
||||
expect(@subscription.destroy_subscription).to eq(true)
|
||||
expect(@subscription.subscribed?).to eq(false)
|
||||
end
|
||||
context "with business subscription" do
|
||||
before do
|
||||
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::BUSINESS_PRODUCT_ID)
|
||||
end
|
||||
|
||||
it "has class aliases" do
|
||||
authenticate_subscription
|
||||
stub_subscription_request(200, valid_subscription)
|
||||
expect(described_class.update).to eq(true)
|
||||
expect(described_class.subscribed?).to eq(true)
|
||||
end
|
||||
end
|
||||
it "detects business type" do
|
||||
expect(described_class.type).to eq(:business)
|
||||
end
|
||||
|
||||
context "authentication" do
|
||||
before do
|
||||
@subscription = described_class.new
|
||||
user.update!(admin: true)
|
||||
end
|
||||
|
||||
it "generates a valid authentication request url" do
|
||||
request_id = SecureRandom.hex(32)
|
||||
uri = URI(@subscription.authentication_url(user.id, request_id))
|
||||
expect(uri.host).to eq(@subscription.server)
|
||||
|
||||
parsed_query = Rack::Utils.parse_query uri.query
|
||||
expect(parsed_query['public_key'].present?).to eq(true)
|
||||
expect(parsed_query['nonce'].present?).to eq(true)
|
||||
expect(parsed_query['client_id'].present?).to eq(true)
|
||||
expect(parsed_query['auth_redirect'].present?).to eq(true)
|
||||
expect(parsed_query['application_name']).to eq(SiteSetting.title)
|
||||
expect(parsed_query['scopes']).to eq(@subscription.scope)
|
||||
end
|
||||
|
||||
def generate_payload(request_id, user_id)
|
||||
uri = URI(@subscription.authentication_url(user_id, request_id))
|
||||
keys = @subscription.authentication.get_keys(request_id)
|
||||
raw_payload = {
|
||||
key: "12345",
|
||||
nonce: keys.nonce,
|
||||
push: false,
|
||||
api: UserApiKeysController::AUTH_API_VERSION
|
||||
}.to_json
|
||||
public_key = OpenSSL::PKey::RSA.new(keys.pem)
|
||||
Base64.encode64(public_key.public_encrypt(raw_payload))
|
||||
end
|
||||
|
||||
it "handles authentication response if request and response is valid" do
|
||||
request_id = SecureRandom.hex(32)
|
||||
payload = generate_payload(request_id, user.id)
|
||||
|
||||
expect(@subscription.authentication_response(request_id, payload)).to eq(true)
|
||||
expect(@subscription.authorized?).to eq(true)
|
||||
end
|
||||
|
||||
it "discards authentication response if user who made request as not an admin" do
|
||||
user.update!(admin: false)
|
||||
|
||||
request_id = SecureRandom.hex(32)
|
||||
payload = generate_payload(request_id, user.id)
|
||||
|
||||
expect(@subscription.authentication_response(request_id, payload)).to eq(false)
|
||||
expect(@subscription.authorized?).to eq(false)
|
||||
end
|
||||
|
||||
it "discards authentication response if request_id is invalid" do
|
||||
payload = generate_payload(SecureRandom.hex(32), user.id)
|
||||
|
||||
expect(@subscription.authentication_response(SecureRandom.hex(32), payload)).to eq(false)
|
||||
expect(@subscription.authorized?).to eq(false)
|
||||
end
|
||||
|
||||
it "destroys authentication" do
|
||||
request_id = SecureRandom.hex(32)
|
||||
payload = generate_payload(request_id, user.id)
|
||||
@subscription.authentication_response(request_id, payload)
|
||||
|
||||
expect(@subscription.destroy_authentication).to eq(true)
|
||||
expect(@subscription.authorized?).to eq(false)
|
||||
it "business are included" do
|
||||
expect(described_class.includes?(:action, :type, 'create_category')).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -47,6 +47,8 @@ describe CustomWizard::Template do
|
|||
|
||||
context "wizard template list" do
|
||||
before do
|
||||
enable_subscription('standard')
|
||||
|
||||
template_json_2 = template_json.dup
|
||||
template_json_2["id"] = 'super_mega_fun_wizard_2'
|
||||
template_json_2["permitted"] = permitted_json['permitted']
|
||||
|
|
|
@ -5,6 +5,8 @@ describe CustomWizard::TemplateValidator do
|
|||
let(:template) { get_wizard_fixture("wizard") }
|
||||
let(:create_category) { get_wizard_fixture("actions/create_category") }
|
||||
let(:user_condition) { get_wizard_fixture("condition/user_condition") }
|
||||
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
|
||||
let(:composer_preview) { get_wizard_fixture("field/composer_preview") }
|
||||
|
||||
let(:valid_liquid_template) {
|
||||
<<-LIQUID.strip
|
||||
|
@ -104,7 +106,7 @@ describe CustomWizard::TemplateValidator do
|
|||
|
||||
context "without subscription" do
|
||||
it "invalidates subscription wizard attributes" do
|
||||
template[:save_submissions] = false
|
||||
template[:permitted] = permitted_json['permitted']
|
||||
expect(
|
||||
CustomWizard::TemplateValidator.new(template).perform
|
||||
).to eq(false)
|
||||
|
@ -134,11 +136,11 @@ describe CustomWizard::TemplateValidator do
|
|||
|
||||
context "with subscription" do
|
||||
before do
|
||||
enable_subscription("standard")
|
||||
enable_subscription("business")
|
||||
end
|
||||
|
||||
it "validates wizard attributes" do
|
||||
template[:save_submissions] = false
|
||||
template[:permitted] = permitted_json['permitted']
|
||||
expect(
|
||||
CustomWizard::TemplateValidator.new(template).perform
|
||||
).to eq(true)
|
||||
|
@ -227,7 +229,9 @@ describe CustomWizard::TemplateValidator do
|
|||
end
|
||||
|
||||
it "validates preview templates" do
|
||||
template[:steps][0][:fields][4][:preview_template] = invalid_liquid_template
|
||||
enable_subscription("standard")
|
||||
template[:steps][0][:fields] << composer_preview
|
||||
template[:steps][0][:fields][3][:preview_template] = invalid_liquid_template
|
||||
expect_validation_failure("step_1_field_5.preview_template", liquid_syntax_error)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
describe CustomWizard::UpdateValidator do
|
||||
fab!(:user) { Fabricate(:user) }
|
||||
let(:template) { get_wizard_fixture("wizard") }
|
||||
let(:url_field) { get_wizard_fixture("field/url") }
|
||||
|
||||
before do
|
||||
CustomWizard::Template.save(template, skip_jobs: true)
|
||||
|
@ -21,7 +22,6 @@ describe CustomWizard::UpdateValidator do
|
|||
|
||||
@template[:steps][0][:fields][0][:min_length] = min_length
|
||||
@template[:steps][0][:fields][1][:min_length] = min_length
|
||||
@template[:steps][0][:fields][2][:min_length] = min_length
|
||||
|
||||
CustomWizard::Template.save(@template)
|
||||
|
||||
|
@ -34,11 +34,6 @@ describe CustomWizard::UpdateValidator do
|
|||
expect(
|
||||
updater.errors.messages[:step_1_field_2].first
|
||||
).to eq(I18n.t('wizard.field.too_short', label: 'Textarea', min: min_length))
|
||||
|
||||
updater = perform_validation('step_1', step_1_field_3: 'Te')
|
||||
expect(
|
||||
updater.errors.messages[:step_1_field_3].first
|
||||
).to eq(I18n.t('wizard.field.too_short', label: 'Composer', min: min_length))
|
||||
end
|
||||
|
||||
it 'prevents submission if the length is over the max length' do
|
||||
|
@ -46,7 +41,6 @@ describe CustomWizard::UpdateValidator do
|
|||
|
||||
@template[:steps][0][:fields][0][:max_length] = max_length
|
||||
@template[:steps][0][:fields][1][:max_length] = max_length
|
||||
@template[:steps][0][:fields][2][:max_length] = max_length
|
||||
|
||||
CustomWizard::Template.save(@template)
|
||||
long_string = "Our Competitive Capability solution offers platforms a suite of wholesale offerings. In the future, will you be able to effectively revolutionize synergies in your business? In the emerging market space, industry is ethically investing its mission critical executive searches. Key players will take ownership of their capabilities by iteratively right-sizing world-class visibilities. "
|
||||
|
@ -59,11 +53,6 @@ describe CustomWizard::UpdateValidator do
|
|||
expect(
|
||||
updater.errors.messages[:step_1_field_2].first
|
||||
).to eq(I18n.t('wizard.field.too_long', label: 'Textarea', max: max_length))
|
||||
|
||||
updater = perform_validation('step_1', step_1_field_3: long_string)
|
||||
expect(
|
||||
updater.errors.messages[:step_1_field_3].first
|
||||
).to eq(I18n.t('wizard.field.too_long', label: 'Composer', max: max_length))
|
||||
end
|
||||
|
||||
it "allows submission if the length is under or equal to the max length" do
|
||||
|
@ -71,7 +60,6 @@ describe CustomWizard::UpdateValidator do
|
|||
|
||||
@template[:steps][0][:fields][0][:max_length] = max_length
|
||||
@template[:steps][0][:fields][1][:max_length] = max_length
|
||||
@template[:steps][0][:fields][2][:max_length] = max_length
|
||||
|
||||
CustomWizard::Template.save(@template)
|
||||
hundred_chars_string = "This is a line, exactly hundred characters long and not more even a single character more than that."
|
||||
|
@ -84,11 +72,6 @@ describe CustomWizard::UpdateValidator do
|
|||
expect(
|
||||
updater.errors.messages[:step_1_field_2].first
|
||||
).to eq(nil)
|
||||
|
||||
updater = perform_validation('step_1', step_1_field_3: hundred_chars_string)
|
||||
expect(
|
||||
updater.errors.messages[:step_1_field_3].first
|
||||
).to eq(nil)
|
||||
end
|
||||
|
||||
it "applies min length only if the input is non-empty" do
|
||||
|
@ -131,25 +114,33 @@ describe CustomWizard::UpdateValidator do
|
|||
).to eq(I18n.t('wizard.field.required', label: 'Textarea'))
|
||||
end
|
||||
|
||||
it 'validates url fields' do
|
||||
updater = perform_validation('step_2', step_2_field_6: 'https://discourse.com')
|
||||
expect(
|
||||
updater.errors.messages[:step_2_field_6].first
|
||||
).to eq(nil)
|
||||
end
|
||||
context "subscription fields" do
|
||||
before do
|
||||
enable_subscription("standard")
|
||||
end
|
||||
|
||||
it 'does not validate url fields with non-url inputs' do
|
||||
updater = perform_validation('step_2', step_2_field_6: 'discourse')
|
||||
expect(
|
||||
updater.errors.messages[:step_2_field_6].first
|
||||
).to eq(I18n.t('wizard.field.not_url', label: 'Url'))
|
||||
end
|
||||
it 'validates url fields' do
|
||||
updater = perform_validation('step_2', step_2_field_6: 'https://discourse.com')
|
||||
expect(
|
||||
updater.errors.messages[:step_2_field_6].first
|
||||
).to eq(nil)
|
||||
end
|
||||
|
||||
it 'validates empty url fields' do
|
||||
updater = perform_validation('step_2', step_2_field_6: '')
|
||||
expect(
|
||||
updater.errors.messages[:step_2_field_6].first
|
||||
).to eq(nil)
|
||||
it 'does not validate url fields with non-url inputs' do
|
||||
template[:steps][0][:fields] << url_field
|
||||
CustomWizard::Template.save(template)
|
||||
updater = perform_validation('step_1', step_2_field_6: 'discourse')
|
||||
expect(
|
||||
updater.errors.messages[:step_2_field_6].first
|
||||
).to eq(I18n.t('wizard.field.not_url', label: 'Url'))
|
||||
end
|
||||
|
||||
it 'validates empty url fields' do
|
||||
updater = perform_validation('step_2', step_2_field_6: '')
|
||||
expect(
|
||||
updater.errors.messages[:step_2_field_6].first
|
||||
).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
it 'validates date fields' do
|
||||
|
|
|
@ -113,69 +113,91 @@ describe CustomWizard::Wizard do
|
|||
expect(wizard.completed?).to eq(false)
|
||||
end
|
||||
|
||||
it "permits admins" do
|
||||
expect(
|
||||
CustomWizard::Wizard.new(@permitted_template, admin_user).permitted?
|
||||
).to eq(true)
|
||||
end
|
||||
context "with subscription" do
|
||||
before do
|
||||
enable_subscription("standard")
|
||||
end
|
||||
|
||||
it "permits permitted users" do
|
||||
expect(
|
||||
CustomWizard::Wizard.new(@permitted_template, trusted_user).permitted?
|
||||
).to eq(true)
|
||||
end
|
||||
it "permits admins" do
|
||||
expect(
|
||||
CustomWizard::Wizard.new(@permitted_template, admin_user).permitted?
|
||||
).to eq(true)
|
||||
end
|
||||
|
||||
it "permits everyone if everyone is permitted" do
|
||||
@permitted_template['permitted'][0]['output'] = Group::AUTO_GROUPS[:everyone]
|
||||
expect(
|
||||
CustomWizard::Wizard.new(@permitted_template, user).permitted?
|
||||
).to eq(true)
|
||||
end
|
||||
it "permits permitted users" do
|
||||
expect(
|
||||
CustomWizard::Wizard.new(@permitted_template, trusted_user).permitted?
|
||||
).to eq(true)
|
||||
end
|
||||
|
||||
it "does not permit unpermitted users" do
|
||||
expect(
|
||||
CustomWizard::Wizard.new(@permitted_template, user).permitted?
|
||||
).to eq(false)
|
||||
end
|
||||
it "permits everyone if everyone is permitted" do
|
||||
@permitted_template['permitted'][0]['output'] = Group::AUTO_GROUPS[:everyone]
|
||||
expect(
|
||||
CustomWizard::Wizard.new(@permitted_template, user).permitted?
|
||||
).to eq(true)
|
||||
end
|
||||
|
||||
it "does not let an unpermitted user access a wizard" do
|
||||
expect(
|
||||
CustomWizard::Wizard.new(@permitted_template, user).can_access?
|
||||
).to eq(false)
|
||||
end
|
||||
it "does not permit unpermitted users" do
|
||||
expect(
|
||||
CustomWizard::Wizard.new(@permitted_template, user).permitted?
|
||||
).to eq(false)
|
||||
end
|
||||
|
||||
it "lets a permitted user access an incomplete wizard" do
|
||||
expect(
|
||||
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
|
||||
).to eq(true)
|
||||
end
|
||||
it "does not let an unpermitted user access a wizard" do
|
||||
expect(
|
||||
CustomWizard::Wizard.new(@permitted_template, user).can_access?
|
||||
).to eq(false)
|
||||
end
|
||||
|
||||
it "lets a permitted user access a complete wizard with multiple submissions" do
|
||||
append_steps
|
||||
it "lets a permitted user access an incomplete wizard" do
|
||||
expect(
|
||||
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
|
||||
).to eq(true)
|
||||
end
|
||||
|
||||
progress_step("step_1", acting_user: trusted_user)
|
||||
progress_step("step_2", acting_user: trusted_user)
|
||||
progress_step("step_3", acting_user: trusted_user)
|
||||
it "lets a permitted user access a complete wizard with multiple submissions" do
|
||||
append_steps
|
||||
|
||||
@permitted_template["multiple_submissions"] = true
|
||||
progress_step("step_1", acting_user: trusted_user)
|
||||
progress_step("step_2", acting_user: trusted_user)
|
||||
progress_step("step_3", acting_user: trusted_user)
|
||||
|
||||
expect(
|
||||
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
|
||||
).to eq(true)
|
||||
end
|
||||
@permitted_template["multiple_submissions"] = true
|
||||
|
||||
it "does not let an unpermitted user access a complete wizard without multiple submissions" do
|
||||
append_steps
|
||||
expect(
|
||||
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
|
||||
).to eq(true)
|
||||
end
|
||||
|
||||
progress_step("step_1", acting_user: trusted_user)
|
||||
progress_step("step_2", acting_user: trusted_user)
|
||||
progress_step("step_3", acting_user: trusted_user)
|
||||
it "does not let an unpermitted user access a complete wizard without multiple submissions" do
|
||||
append_steps
|
||||
|
||||
@permitted_template['multiple_submissions'] = false
|
||||
progress_step("step_1", acting_user: trusted_user)
|
||||
progress_step("step_2", acting_user: trusted_user)
|
||||
progress_step("step_3", acting_user: trusted_user)
|
||||
|
||||
expect(
|
||||
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
|
||||
).to eq(false)
|
||||
@permitted_template['multiple_submissions'] = false
|
||||
|
||||
expect(
|
||||
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
|
||||
).to eq(false)
|
||||
end
|
||||
|
||||
it "sets wizard redirects if user is permitted" do
|
||||
CustomWizard::Template.save(@permitted_template, skip_jobs: true)
|
||||
CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', trusted_user)
|
||||
expect(
|
||||
trusted_user.custom_fields['redirect_to_wizard']
|
||||
).to eq("super_mega_fun_wizard")
|
||||
end
|
||||
|
||||
it "does not set a wizard redirect if user is not permitted" do
|
||||
CustomWizard::Template.save(@permitted_template, skip_jobs: true)
|
||||
CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', user)
|
||||
expect(
|
||||
trusted_user.custom_fields['redirect_to_wizard']
|
||||
).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
it "lists the site groups" do
|
||||
|
@ -203,6 +225,7 @@ describe CustomWizard::Wizard do
|
|||
|
||||
context "class methods" do
|
||||
before do
|
||||
enable_subscription("standard")
|
||||
CustomWizard::Template.save(@permitted_template, skip_jobs: true)
|
||||
|
||||
template_json_2 = template_json.dup
|
||||
|
@ -238,20 +261,4 @@ describe CustomWizard::Wizard do
|
|||
expect(CustomWizard::Wizard.prompt_completion(user).length).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
it "sets wizard redirects if user is permitted" do
|
||||
CustomWizard::Template.save(@permitted_template, skip_jobs: true)
|
||||
CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', trusted_user)
|
||||
expect(
|
||||
trusted_user.custom_fields['redirect_to_wizard']
|
||||
).to eq("super_mega_fun_wizard")
|
||||
end
|
||||
|
||||
it "does not set a wizard redirect if user is not permitted" do
|
||||
CustomWizard::Template.save(@permitted_template, skip_jobs: true)
|
||||
CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', user)
|
||||
expect(
|
||||
trusted_user.custom_fields['redirect_to_wizard']
|
||||
).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
|
23
spec/fixtures/actions/watch_categories.json
gevendort
Normale Datei
23
spec/fixtures/actions/watch_categories.json
gevendort
Normale Datei
|
@ -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
27
spec/fixtures/field/advanced_types.json
gevendort
Normale Datei
|
@ -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
7
spec/fixtures/field/composer_preview.json
gevendort
Normale Datei
|
@ -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
5
spec/fixtures/field/url.json
gevendort
Normale Datei
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"id": "step_2_field_6",
|
||||
"label": "Url",
|
||||
"type": "url"
|
||||
}
|
4
spec/fixtures/subscription_client.rb
gevendort
Normale Datei
4
spec/fixtures/subscription_client.rb
gevendort
Normale Datei
|
@ -0,0 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module SubscriptionClient; end
|
||||
class SubscriptionClientSubscription; end
|
69
spec/fixtures/wizard.json
gevendort
69
spec/fixtures/wizard.json
gevendort
|
@ -33,23 +33,11 @@
|
|||
"type": "textarea",
|
||||
"min_length": ""
|
||||
},
|
||||
{
|
||||
"id": "step_1_field_3",
|
||||
"label": "Composer",
|
||||
"type": "composer"
|
||||
},
|
||||
{
|
||||
"id": "step_1_field_4",
|
||||
"label": "I'm only text",
|
||||
"description": "",
|
||||
"type": "text_only"
|
||||
},
|
||||
{
|
||||
"id": "step_1_field_5",
|
||||
"label": "I'm a preview",
|
||||
"description": "",
|
||||
"type": "composer_preview",
|
||||
"preview_template": "w{step_1_field_1}"
|
||||
}
|
||||
],
|
||||
"description": "Text inputs!"
|
||||
|
@ -87,11 +75,6 @@
|
|||
"label": "Checkbox",
|
||||
"type": "checkbox"
|
||||
},
|
||||
{
|
||||
"id": "step_2_field_6",
|
||||
"label": "Url",
|
||||
"type": "url"
|
||||
},
|
||||
{
|
||||
"id": "step_2_field_7",
|
||||
"label": "Upload",
|
||||
|
@ -141,35 +124,6 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "step_3_field_2",
|
||||
"label": "Tag",
|
||||
"type": "tag"
|
||||
},
|
||||
{
|
||||
"id": "step_3_field_3",
|
||||
"label": "Category",
|
||||
"type": "category",
|
||||
"limit": 1,
|
||||
"property": "id"
|
||||
},
|
||||
{
|
||||
"id": "step_3_field_4",
|
||||
"label": "Group",
|
||||
"type": "group"
|
||||
},
|
||||
{
|
||||
"id": "step_3_field_5",
|
||||
"label": "User Selector",
|
||||
"description": "",
|
||||
"type": "user_selector"
|
||||
},
|
||||
{
|
||||
"id": "step_3_field_6",
|
||||
"label": "Conditional User Selector",
|
||||
"description": "Shown when checkbox in step_2_field_5 is true",
|
||||
"type": "user_selector"
|
||||
}
|
||||
],
|
||||
"description": "Unfortunately not the edible type :sushi: "
|
||||
|
@ -264,29 +218,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "action_5",
|
||||
"run_after": "step_1",
|
||||
"type": "watch_categories",
|
||||
"notification_level": "tracking",
|
||||
"wizard_user": true,
|
||||
"categories": [
|
||||
{
|
||||
"type": "assignment",
|
||||
"output": "action_8",
|
||||
"output_type": "wizard_action",
|
||||
"output_connector": "set"
|
||||
}
|
||||
],
|
||||
"mute_remainder": [
|
||||
{
|
||||
"type": "assignment",
|
||||
"output": "true",
|
||||
"output_type": "text",
|
||||
"output_connector": "set"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "action_4",
|
||||
"run_after": "step_2",
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -8,37 +8,8 @@ def get_wizard_fixture(path)
|
|||
).with_indifferent_access
|
||||
end
|
||||
|
||||
def authenticate_subscription
|
||||
CustomWizard::Subscription::Authentication.any_instance.stubs(:active?).returns(true)
|
||||
end
|
||||
|
||||
def enable_subscription(type)
|
||||
# CustomWizard::Subscription.new
|
||||
CustomWizard::Subscription.any_instance.stubs(:subscribed?).returns(true)
|
||||
CustomWizard::Subscription.any_instance.stubs(:type).returns(type)
|
||||
end
|
||||
|
||||
def disable_subscription
|
||||
CustomWizard::Subscription.any_instance.stubs(:subscribed?).returns(false)
|
||||
end
|
||||
|
||||
def valid_subscription
|
||||
{
|
||||
product_id: "prod_CBTNpi3fqWWkq0",
|
||||
price_id: "price_id",
|
||||
price_nickname: "business"
|
||||
}
|
||||
end
|
||||
|
||||
def invalid_subscription
|
||||
{
|
||||
product_id: "prod_CBTNpi3fqWWkq0",
|
||||
price_id: "price_id"
|
||||
}
|
||||
end
|
||||
|
||||
def stub_subscription_request(status, subscription)
|
||||
authenticate_subscription
|
||||
sub = CustomWizard::Subscription.new
|
||||
stub_request(:get, "https://#{sub.server}/subscription-server/user-subscriptions/#{sub.subscription_type}/#{sub.client_name}").to_return(status: status, body: { subscriptions: [subscription] }.to_json)
|
||||
CustomWizard::Subscription.stubs(:client_installed?).returns(true)
|
||||
CustomWizard::Subscription.stubs("#{type}?".to_sym).returns(true)
|
||||
CustomWizard::Subscription.any_instance.stubs("#{type}?".to_sym).returns(true)
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -8,6 +8,7 @@ describe CustomWizard::AdminWizardController do
|
|||
|
||||
before do
|
||||
CustomWizard::Template.save(template, skip_jobs: true)
|
||||
enable_subscription("standard")
|
||||
|
||||
template_2 = template.dup
|
||||
template_2["id"] = 'super_mega_fun_wizard_2'
|
||||
|
|
|
@ -34,6 +34,7 @@ describe CustomWizard::StepsController do
|
|||
end
|
||||
|
||||
it "when the user cant access the wizard" do
|
||||
enable_subscription("standard")
|
||||
new_template = wizard_template.dup
|
||||
new_template["permitted"] = permitted_json["permitted"]
|
||||
CustomWizard::Template.save(new_template, skip_jobs: true)
|
||||
|
|
|
@ -40,14 +40,15 @@ describe CustomWizard::WizardController do
|
|||
end
|
||||
|
||||
it 'lets user skip if user cant access wizard' do
|
||||
enable_subscription("standard")
|
||||
@template["permitted"] = permitted_json["permitted"]
|
||||
CustomWizard::Template.save(@template, skip_jobs: true)
|
||||
|
||||
put '/w/super-mega-fun-wizard/skip.json'
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
|
||||
it 'returns a no skip message if user is not allowed to skip' do
|
||||
enable_subscription("standard")
|
||||
@template['required'] = 'true'
|
||||
CustomWizard::Template.save(@template)
|
||||
put '/w/super-mega-fun-wizard/skip.json'
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -19,7 +19,7 @@ describe CustomWizard::FieldSerializer do
|
|||
expect(json_array.size).to eq(@wizard.steps.first.fields.size)
|
||||
expect(json_array[0][:label]).to eq("<p>Text</p>")
|
||||
expect(json_array[0][:description]).to eq("Text field description.")
|
||||
expect(json_array[3][:index]).to eq(3)
|
||||
expect(json_array[2][:index]).to eq(2)
|
||||
end
|
||||
|
||||
it "should return optional field attributes" do
|
||||
|
@ -29,6 +29,6 @@ describe CustomWizard::FieldSerializer do
|
|||
scope: Guardian.new(user)
|
||||
).as_json
|
||||
expect(json_array[0][:format]).to eq("YYYY-MM-DD")
|
||||
expect(json_array[6][:file_types]).to eq(".jpg,.jpeg,.png")
|
||||
expect(json_array[5][:file_types]).to eq(".jpg,.jpeg,.png")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@ describe CustomWizard::WizardSerializer do
|
|||
fab!(:category) { Fabricate(:category) }
|
||||
let(:template) { get_wizard_fixture("wizard") }
|
||||
let(:similar_topics_validation) { get_wizard_fixture("field/validation/similar_topics") }
|
||||
let(:advanced_fields) { get_wizard_fixture("field/advanced_types") }
|
||||
|
||||
before do
|
||||
CustomWizard::Template.save(template, skip_jobs: true)
|
||||
|
@ -52,43 +53,51 @@ describe CustomWizard::WizardSerializer do
|
|||
expect(json[:wizard][:uncategorized_category_id].present?).to eq(false)
|
||||
end
|
||||
|
||||
it "should return categories if there is a category selector field" do
|
||||
json = CustomWizard::WizardSerializer.new(
|
||||
CustomWizard::Builder.new(@template[:id], user).build,
|
||||
scope: Guardian.new(user)
|
||||
).as_json
|
||||
expect(json[:wizard][:categories].present?).to eq(true)
|
||||
expect(json[:wizard][:uncategorized_category_id].present?).to eq(true)
|
||||
end
|
||||
context "advanced fields" do
|
||||
before do
|
||||
enable_subscription("standard")
|
||||
@template[:steps][0][:fields].push(*advanced_fields['fields'])
|
||||
CustomWizard::Template.save(@template, skip_jobs: true)
|
||||
end
|
||||
|
||||
it "should return categories if there is a similar topics validation scoped to category(s)" do
|
||||
@template[:steps][0][:fields][0][:validations] = similar_topics_validation[:validations]
|
||||
CustomWizard::Template.save(@template)
|
||||
it "should return categories if there is a category selector field" do
|
||||
json = CustomWizard::WizardSerializer.new(
|
||||
CustomWizard::Builder.new(@template[:id], user).build,
|
||||
scope: Guardian.new(user)
|
||||
).as_json
|
||||
expect(json[:wizard][:categories].present?).to eq(true)
|
||||
expect(json[:wizard][:uncategorized_category_id].present?).to eq(true)
|
||||
end
|
||||
|
||||
json = CustomWizard::WizardSerializer.new(
|
||||
CustomWizard::Builder.new(@template[:id], user).build,
|
||||
scope: Guardian.new(user)
|
||||
).as_json
|
||||
expect(json[:wizard][:categories].present?).to eq(true)
|
||||
expect(json[:wizard][:uncategorized_category_id].present?).to eq(true)
|
||||
end
|
||||
it "should return categories if there is a similar topics validation scoped to category(s)" do
|
||||
@template[:steps][0][:fields][0][:validations] = similar_topics_validation[:validations]
|
||||
CustomWizard::Template.save(@template)
|
||||
|
||||
it 'should return groups if there is a group selector field' do
|
||||
json = CustomWizard::WizardSerializer.new(
|
||||
CustomWizard::Builder.new(@template[:id], user).build,
|
||||
scope: Guardian.new(user)
|
||||
).as_json
|
||||
expect(json[:wizard][:groups].length).to eq(8)
|
||||
end
|
||||
json = CustomWizard::WizardSerializer.new(
|
||||
CustomWizard::Builder.new(@template[:id], user).build,
|
||||
scope: Guardian.new(user)
|
||||
).as_json
|
||||
expect(json[:wizard][:categories].present?).to eq(true)
|
||||
expect(json[:wizard][:uncategorized_category_id].present?).to eq(true)
|
||||
end
|
||||
|
||||
it 'should not return groups if there is not a group selector field' do
|
||||
@template[:steps][2][:fields].delete_at(3)
|
||||
CustomWizard::Template.save(@template)
|
||||
it 'should return groups if there is a group selector field' do
|
||||
json = CustomWizard::WizardSerializer.new(
|
||||
CustomWizard::Builder.new(@template[:id], user).build,
|
||||
scope: Guardian.new(user)
|
||||
).as_json
|
||||
expect(json[:wizard][:groups].length).to eq(8)
|
||||
end
|
||||
|
||||
json = CustomWizard::WizardSerializer.new(
|
||||
CustomWizard::Builder.new(@template[:id], user).build,
|
||||
scope: Guardian.new(user)
|
||||
).as_json
|
||||
expect(json[:wizard][:groups].present?).to eq(false)
|
||||
it 'should not return groups if there is not a group selector field' do
|
||||
@template[:steps][0][:fields].reject! { |f| f["type"] === "group" }
|
||||
CustomWizard::Template.save(@template)
|
||||
|
||||
json = CustomWizard::WizardSerializer.new(
|
||||
CustomWizard::Builder.new(@template[:id], user).build,
|
||||
scope: Guardian.new(user)
|
||||
).as_json
|
||||
expect(json[:wizard][:groups].present?).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Laden …
In neuem Issue referenzieren