Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-10 04:12:53 +01:00
Merge branch 'main' into tag_group_restriction_fix
Dieser Commit ist enthalten in:
Commit
75d429388e
47 geänderte Dateien mit 901 neuen und 419 gelöschten Zeilen
|
@ -1,4 +1,4 @@
|
||||||
All code in this repository is Copyright 2018 by Angus McLeod.
|
All code in this repository is Copyright 2023 by Angus McLeod.
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
This program is free software; you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class CustomWizard::StepsController < ::ApplicationController
|
class CustomWizard::StepsController < ::CustomWizard::WizardClientController
|
||||||
before_action :ensure_logged_in
|
|
||||||
before_action :ensure_can_update
|
before_action :ensure_can_update
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
@ -22,7 +21,7 @@ class CustomWizard::StepsController < ::ApplicationController
|
||||||
|
|
||||||
if updater.success?
|
if updater.success?
|
||||||
wizard_id = update_params[:wizard_id]
|
wizard_id = update_params[:wizard_id]
|
||||||
builder = CustomWizard::Builder.new(wizard_id, current_user)
|
builder = CustomWizard::Builder.new(wizard_id, current_user, guest_id)
|
||||||
@wizard = builder.build(force: true)
|
@wizard = builder.build(force: true)
|
||||||
|
|
||||||
current_step = @wizard.find_step(update[:step_id])
|
current_step = @wizard.find_step(update[:step_id])
|
||||||
|
@ -85,7 +84,6 @@ class CustomWizard::StepsController < ::ApplicationController
|
||||||
private
|
private
|
||||||
|
|
||||||
def ensure_can_update
|
def ensure_can_update
|
||||||
@builder = CustomWizard::Builder.new(update_params[:wizard_id], current_user)
|
|
||||||
raise Discourse::InvalidParameters.new(:wizard_id) if @builder.template.nil?
|
raise Discourse::InvalidParameters.new(:wizard_id) if @builder.template.nil?
|
||||||
raise Discourse::InvalidAccess.new if !@builder.wizard || !@builder.wizard.can_access?
|
raise Discourse::InvalidAccess.new if !@builder.wizard || !@builder.wizard.can_access?
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class CustomWizard::WizardController < ::ApplicationController
|
class CustomWizard::WizardController < ::CustomWizard::WizardClientController
|
||||||
before_action :ensure_plugin_enabled
|
|
||||||
before_action :ensure_logged_in, only: [:skip]
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
if wizard.present?
|
if wizard.present?
|
||||||
render json: CustomWizard::WizardSerializer.new(wizard, scope: guardian, root: false).as_json, status: 200
|
render json: CustomWizard::WizardSerializer.new(wizard, scope: guardian, root: false).as_json, status: 200
|
||||||
|
@ -35,19 +32,8 @@ class CustomWizard::WizardController < ::ApplicationController
|
||||||
|
|
||||||
def wizard
|
def wizard
|
||||||
@wizard ||= begin
|
@wizard ||= begin
|
||||||
builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user)
|
return nil unless @builder.present?
|
||||||
return nil unless builder.present?
|
@builder.build({ reset: params[:reset] }, params)
|
||||||
opts = {}
|
|
||||||
opts[:reset] = params[:reset]
|
|
||||||
builder.build(opts, params)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def ensure_plugin_enabled
|
|
||||||
unless SiteSetting.custom_wizard_enabled
|
|
||||||
redirect_to path("/")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
23
app/controllers/custom_wizard/wizard_client.rb
Normale Datei
23
app/controllers/custom_wizard/wizard_client.rb
Normale Datei
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class CustomWizard::WizardClientController < ::ApplicationController
|
||||||
|
before_action :ensure_plugin_enabled
|
||||||
|
before_action :set_builder
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def ensure_plugin_enabled
|
||||||
|
unless SiteSetting.custom_wizard_enabled
|
||||||
|
redirect_to path("/")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def guest_id
|
||||||
|
return nil if current_user.present?
|
||||||
|
cookies[:custom_wizard_guest_id] ||= CustomWizard::Wizard.generate_guest_id
|
||||||
|
cookies[:custom_wizard_guest_id]
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_builder
|
||||||
|
@builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user, guest_id)
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,12 +2,15 @@
|
||||||
class CustomWizard::SubmissionSerializer < ApplicationSerializer
|
class CustomWizard::SubmissionSerializer < ApplicationSerializer
|
||||||
attributes :id,
|
attributes :id,
|
||||||
:fields,
|
:fields,
|
||||||
:submitted_at
|
:submitted_at,
|
||||||
|
:user
|
||||||
has_one :user, serializer: ::BasicUserSerializer, embed: :objects
|
|
||||||
|
|
||||||
def include_user?
|
def include_user?
|
||||||
object.user.present?
|
object.wizard.user.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def user
|
||||||
|
::BasicUserSerializer.new(object.wizard.user).as_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def fields
|
def fields
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { bind, later } from "@ember/runloop";
|
import { bind, later } from "@ember/runloop";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
import Subscription from "../mixins/subscription";
|
||||||
|
|
||||||
const customFieldActionMap = {
|
const customFieldActionMap = {
|
||||||
topic: ["create_topic", "send_message"],
|
topic: ["create_topic", "send_message"],
|
||||||
|
@ -26,7 +27,7 @@ const customFieldActionMap = {
|
||||||
|
|
||||||
const values = ["present", "true", "false"];
|
const values = ["present", "true", "false"];
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend(Subscription, {
|
||||||
classNameBindings: [":mapper-selector", "activeType"],
|
classNameBindings: [":mapper-selector", "activeType"],
|
||||||
|
|
||||||
showText: computed("activeType", function () {
|
showText: computed("activeType", function () {
|
||||||
|
@ -116,6 +117,9 @@ export default Component.extend({
|
||||||
groupEnabled: computed("options.groupSelection", "inputType", function () {
|
groupEnabled: computed("options.groupSelection", "inputType", function () {
|
||||||
return this.optionEnabled("groupSelection");
|
return this.optionEnabled("groupSelection");
|
||||||
}),
|
}),
|
||||||
|
guestGroup: computed("options.guestGroup", "inputType", function () {
|
||||||
|
return this.optionEnabled("guestGroup");
|
||||||
|
}),
|
||||||
userEnabled: computed("options.userSelection", "inputType", function () {
|
userEnabled: computed("options.userSelection", "inputType", function () {
|
||||||
return this.optionEnabled("userSelection");
|
return this.optionEnabled("userSelection");
|
||||||
}),
|
}),
|
||||||
|
@ -126,7 +130,29 @@ export default Component.extend({
|
||||||
return this.connector === "is";
|
return this.connector === "is";
|
||||||
}),
|
}),
|
||||||
|
|
||||||
groups: alias("site.groups"),
|
@discourseComputed("site.groups", "guestGroup", "subscriptionType")
|
||||||
|
groups(groups, guestGroup, subscriptionType) {
|
||||||
|
let result = groups;
|
||||||
|
if (!guestGroup) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (["standard", "business"].includes(subscriptionType)) {
|
||||||
|
let guestIndex;
|
||||||
|
result.forEach((r, index) => {
|
||||||
|
if (r.id === 0) {
|
||||||
|
r.name = I18n.t("admin.wizard.selector.label.users");
|
||||||
|
guestIndex = index;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
result.splice(guestIndex, 0, {
|
||||||
|
id: -1,
|
||||||
|
name: I18n.t("admin.wizard.selector.label.guests"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
categories: alias("site.categories"),
|
categories: alias("site.categories"),
|
||||||
showComboBox: or(
|
showComboBox: or(
|
||||||
"showWizardField",
|
"showWizardField",
|
||||||
|
|
|
@ -32,6 +32,7 @@ export default Component.extend({
|
||||||
pairConnector: options.pairConnector || null,
|
pairConnector: options.pairConnector || null,
|
||||||
outputConnector: options.outputConnector || null,
|
outputConnector: options.outputConnector || null,
|
||||||
context: options.context || null,
|
context: options.context || null,
|
||||||
|
guestGroup: options.guestGroup || false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inputTypes = ["key", "value", "output"];
|
let inputTypes = ["key", "value", "output"];
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import SingleSelectComponent from "select-kit/components/single-select";
|
import SingleSelectComponent from "select-kit/components/single-select";
|
||||||
import Subscription from "../mixins/subscription";
|
import Subscription from "../mixins/subscription";
|
||||||
import wizardSchema from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
|
import { filterValues } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
@ -40,9 +40,9 @@ export default SingleSelectComponent.extend(Subscription, {
|
||||||
return allowedTypes;
|
return allowedTypes;
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed("feature", "attribute")
|
@discourseComputed("feature", "attribute", "wizard.allowGuests")
|
||||||
content(feature, attribute) {
|
content(feature, attribute) {
|
||||||
return wizardSchema[feature][attribute]
|
return filterValues(this.wizard, feature, attribute)
|
||||||
.map((value) => {
|
.map((value) => {
|
||||||
let allowedSubscriptionTypes = this.allowedSubscriptionTypes(
|
let allowedSubscriptionTypes = this.allowedSubscriptionTypes(
|
||||||
feature,
|
feature,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { later, scheduleOnce } from "@ember/runloop";
|
||||||
import Controller from "@ember/controller";
|
import Controller from "@ember/controller";
|
||||||
import copyText from "discourse/lib/copy-text";
|
import copyText from "discourse/lib/copy-text";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
import { filterValues } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
hasName: notEmpty("wizard.name"),
|
hasName: notEmpty("wizard.name"),
|
||||||
|
@ -59,6 +60,19 @@ export default Controller.extend({
|
||||||
}
|
}
|
||||||
return wizardFieldList(steps);
|
return wizardFieldList(steps);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@discourseComputed("fieldTypes", "wizard.allowGuests")
|
||||||
|
filteredFieldTypes(fieldTypes) {
|
||||||
|
const fieldTypeIds = fieldTypes.map((f) => f.id);
|
||||||
|
const allowedTypeIds = filterValues(
|
||||||
|
this.wizard,
|
||||||
|
"field",
|
||||||
|
"type",
|
||||||
|
fieldTypeIds
|
||||||
|
);
|
||||||
|
return fieldTypes.filter((f) => allowedTypeIds.includes(f.id));
|
||||||
|
},
|
||||||
|
|
||||||
getErrorMessage(result) {
|
getErrorMessage(result) {
|
||||||
if (result.backend_validation_error) {
|
if (result.backend_validation_error) {
|
||||||
return result.backend_validation_error;
|
return result.backend_validation_error;
|
||||||
|
|
|
@ -210,6 +210,32 @@ const action = {
|
||||||
objectArrays: {},
|
objectArrays: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filters = {
|
||||||
|
allow_guests: {
|
||||||
|
field: {
|
||||||
|
type: [
|
||||||
|
"text",
|
||||||
|
"textarea",
|
||||||
|
"text_only",
|
||||||
|
"date",
|
||||||
|
"time",
|
||||||
|
"date_time",
|
||||||
|
"number",
|
||||||
|
"checkbox",
|
||||||
|
"url",
|
||||||
|
"dropdown",
|
||||||
|
"tag",
|
||||||
|
"category",
|
||||||
|
"group",
|
||||||
|
"user_selector",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: ["route_to", "send_message"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const custom_field = {
|
const custom_field = {
|
||||||
klass: ["topic", "post", "group", "category"],
|
klass: ["topic", "post", "group", "category"],
|
||||||
type: ["string", "boolean", "integer", "json"],
|
type: ["string", "boolean", "integer", "json"],
|
||||||
|
@ -224,16 +250,29 @@ const wizardSchema = {
|
||||||
field,
|
field,
|
||||||
custom_field,
|
custom_field,
|
||||||
action,
|
action,
|
||||||
|
filters,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function buildFieldTypes(types) {
|
|
||||||
wizardSchema.field.types = types;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildFieldValidations(validations) {
|
export function buildFieldValidations(validations) {
|
||||||
wizardSchema.field.validations = validations;
|
wizardSchema.field.validations = validations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filterValues(currentWizard, feature, attribute, values = null) {
|
||||||
|
values = values || wizardSchema[feature][attribute];
|
||||||
|
|
||||||
|
if (currentWizard.allowGuests) {
|
||||||
|
const filteredFeature = wizardSchema.filters.allow_guests[feature];
|
||||||
|
if (filteredFeature) {
|
||||||
|
const filtered = filteredFeature[attribute];
|
||||||
|
if (filtered) {
|
||||||
|
values = values.filter((v) => filtered.includes(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
const siteSettings = getOwner(this).lookup("service:site-settings");
|
const siteSettings = getOwner(this).lookup("service:site-settings");
|
||||||
if (siteSettings.wizard_apis_enabled) {
|
if (siteSettings.wizard_apis_enabled) {
|
||||||
wizardSchema.action.types.send_to_api = {
|
wizardSchema.action.types.send_to_api = {
|
||||||
|
|
|
@ -5,8 +5,20 @@ import wizardSchema from "../lib/wizard-schema";
|
||||||
import { Promise } from "rsvp";
|
import { Promise } from "rsvp";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
const GUEST_GROUP_ID = -1;
|
||||||
|
|
||||||
const CustomWizardAdmin = EmberObject.extend({
|
const CustomWizardAdmin = EmberObject.extend({
|
||||||
|
@discourseComputed("permitted.@each.output")
|
||||||
|
allowGuests(permitted) {
|
||||||
|
return (
|
||||||
|
permitted &&
|
||||||
|
permitted.filter((p) => p.output && p.output.includes(GUEST_GROUP_ID))
|
||||||
|
.length
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
save(opts) {
|
save(opts) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let wizard = this.buildJson(this, "wizard");
|
let wizard = this.buildJson(this, "wizard");
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
import { buildFieldTypes, buildFieldValidations } from "../lib/wizard-schema";
|
import { buildFieldValidations } from "../lib/wizard-schema";
|
||||||
import EmberObject, { set } from "@ember/object";
|
import EmberObject, { set } from "@ember/object";
|
||||||
import { A } from "@ember/array";
|
import { A } from "@ember/array";
|
||||||
import { all } from "rsvp";
|
import { all } from "rsvp";
|
||||||
|
@ -11,7 +11,6 @@ export default DiscourseRoute.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
afterModel(model) {
|
afterModel(model) {
|
||||||
buildFieldTypes(model.field_types);
|
|
||||||
buildFieldValidations(model.realtime_validations);
|
buildFieldValidations(model.realtime_validations);
|
||||||
|
|
||||||
return all([
|
return all([
|
||||||
|
|
|
@ -4,13 +4,7 @@ import Route from "@ember/routing/route";
|
||||||
export default Route.extend({
|
export default Route.extend({
|
||||||
beforeModel() {
|
beforeModel() {
|
||||||
const wizard = getCachedWizard();
|
const wizard = getCachedWizard();
|
||||||
if (
|
if (wizard && wizard.permitted && !wizard.completed && wizard.start) {
|
||||||
wizard &&
|
|
||||||
wizard.user &&
|
|
||||||
wizard.permitted &&
|
|
||||||
!wizard.completed &&
|
|
||||||
wizard.start
|
|
||||||
) {
|
|
||||||
this.replaceWith("customWizardStep", wizard.start);
|
this.replaceWith("customWizardStep", wizard.start);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -26,7 +20,7 @@ export default Route.extend({
|
||||||
const wizardId = model.get("id");
|
const wizardId = model.get("id");
|
||||||
const user = model.get("user");
|
const user = model.get("user");
|
||||||
const name = model.get("name");
|
const name = model.get("name");
|
||||||
const requiresLogin = !user;
|
const requiresLogin = !user && !permitted;
|
||||||
const notPermitted = !permitted;
|
const notPermitted = !permitted;
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
|
|
|
@ -7,7 +7,7 @@ export default Route.extend({
|
||||||
const wizard = getCachedWizard();
|
const wizard = getCachedWizard();
|
||||||
this.set("wizard", wizard);
|
this.set("wizard", wizard);
|
||||||
|
|
||||||
if (!wizard || !wizard.user || !wizard.permitted || wizard.completed) {
|
if (!wizard || !wizard.permitted || wizard.completed) {
|
||||||
this.replaceWith("customWizard");
|
this.replaceWith("customWizard");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -140,6 +140,7 @@
|
||||||
context="wizard"
|
context="wizard"
|
||||||
inputTypes="assignment,validation"
|
inputTypes="assignment,validation"
|
||||||
groupSelection="output"
|
groupSelection="output"
|
||||||
|
guestGroup=true
|
||||||
userFieldSelection="key"
|
userFieldSelection="key"
|
||||||
textSelection="value"
|
textSelection="value"
|
||||||
inputConnector="and"
|
inputConnector="and"
|
||||||
|
@ -160,7 +161,7 @@
|
||||||
wizard=wizard
|
wizard=wizard
|
||||||
currentField=currentField
|
currentField=currentField
|
||||||
wizardFields=wizardFields
|
wizardFields=wizardFields
|
||||||
fieldTypes=fieldTypes
|
fieldTypes=filteredFieldTypes
|
||||||
subscribed=subscribed}}
|
subscribed=subscribed}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
@ -178,7 +179,7 @@
|
||||||
apis=apis
|
apis=apis
|
||||||
removeAction="removeAction"
|
removeAction="removeAction"
|
||||||
wizardFields=wizardFields
|
wizardFields=wizardFields
|
||||||
fieldTypes=fieldTypes}}
|
fieldTypes=filteredFieldTypes}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
<div class="admin-wizard-buttons">
|
<div class="admin-wizard-buttons">
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
feature="action"
|
feature="action"
|
||||||
attribute="type"
|
attribute="type"
|
||||||
onChange=(action "changeType")
|
onChange=(action "changeType")
|
||||||
|
wizard=wizard
|
||||||
options=(hash
|
options=(hash
|
||||||
none="admin.wizard.select_type"
|
none="admin.wizard.select_type"
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,7 +17,7 @@ body.custom-wizard .wizard-column {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img.emoji {
|
.emoji {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
@ -29,7 +29,7 @@ body.custom-wizard .wizard-column {
|
||||||
|
|
||||||
p {
|
p {
|
||||||
img {
|
img {
|
||||||
@extend img.emoji;
|
@extend .emoji;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -217,6 +217,8 @@ en:
|
||||||
list: "list"
|
list: "list"
|
||||||
custom_field: "custom field"
|
custom_field: "custom field"
|
||||||
value: "value"
|
value: "value"
|
||||||
|
users: "users"
|
||||||
|
guests: "users and guests"
|
||||||
|
|
||||||
placeholder:
|
placeholder:
|
||||||
text: "Enter text"
|
text: "Enter text"
|
||||||
|
|
|
@ -53,7 +53,8 @@ en:
|
||||||
after_signup_after_time: "You can't use 'after time' and 'after signup' on the same wizard."
|
after_signup_after_time: "You can't use 'after time' and 'after signup' on the same wizard."
|
||||||
after_time: "After time setting is invalid."
|
after_time: "After time setting is invalid."
|
||||||
liquid_syntax_error: "Liquid syntax error in %{attribute}: %{message}"
|
liquid_syntax_error: "Liquid syntax error in %{attribute}: %{message}"
|
||||||
subscription: "%{type} %{property} is subscription only"
|
subscription: "%{type} %{property} usage is not supported on your subscription"
|
||||||
|
not_permitted_for_guests: "%{object_id} is not permitted when guests can access the wizard"
|
||||||
|
|
||||||
site_settings:
|
site_settings:
|
||||||
custom_wizard_enabled: "Enable custom wizards."
|
custom_wizard_enabled: "Enable custom wizards."
|
||||||
|
|
|
@ -6,6 +6,14 @@ class CustomWizard::Action
|
||||||
:guardian,
|
:guardian,
|
||||||
:result
|
:result
|
||||||
|
|
||||||
|
REQUIRES_USER = %w[
|
||||||
|
create_topic
|
||||||
|
update_profile
|
||||||
|
open_composer
|
||||||
|
watch_categories
|
||||||
|
add_to_group
|
||||||
|
]
|
||||||
|
|
||||||
def initialize(opts)
|
def initialize(opts)
|
||||||
@wizard = opts[:wizard]
|
@wizard = opts[:wizard]
|
||||||
@action = opts[:action]
|
@action = opts[:action]
|
||||||
|
@ -17,6 +25,12 @@ class CustomWizard::Action
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
|
if REQUIRES_USER.include?(action['id']) && !@user
|
||||||
|
log_error("action requires user", "id: #{action['id']};")
|
||||||
|
@result.success = false
|
||||||
|
return @result
|
||||||
|
end
|
||||||
|
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
self.send(action['type'].to_sym)
|
self.send(action['type'].to_sym)
|
||||||
end
|
end
|
||||||
|
@ -76,7 +90,6 @@ class CustomWizard::Action
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_message
|
def send_message
|
||||||
|
|
||||||
if action['required'].present?
|
if action['required'].present?
|
||||||
required = CustomWizard::Mapper.new(
|
required = CustomWizard::Mapper.new(
|
||||||
inputs: action['required'],
|
inputs: action['required'],
|
||||||
|
@ -123,13 +136,14 @@ class CustomWizard::Action
|
||||||
|
|
||||||
params[:archetype] = Archetype.private_message
|
params[:archetype] = Archetype.private_message
|
||||||
|
|
||||||
creator = PostCreator.new(user, params)
|
poster = user || Discourse.system_user
|
||||||
|
creator = PostCreator.new(poster, params)
|
||||||
post = creator.create
|
post = creator.create
|
||||||
|
|
||||||
if creator.errors.present?
|
if creator.errors.present?
|
||||||
messages = creator.errors.full_messages.join(" ")
|
messages = creator.errors.full_messages.join(" ")
|
||||||
log_error("failed to create message", messages)
|
log_error("failed to create message", messages)
|
||||||
elsif action['skip_redirect'].blank?
|
elsif user && action['skip_redirect'].blank?
|
||||||
@submission.redirect_on_complete = post.topic.url
|
@submission.redirect_on_complete = post.topic.url
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -809,10 +823,12 @@ class CustomWizard::Action
|
||||||
end
|
end
|
||||||
|
|
||||||
def save_log
|
def save_log
|
||||||
|
username = user ? user.username : @wizard.actor_id
|
||||||
|
|
||||||
CustomWizard::Log.create(
|
CustomWizard::Log.create(
|
||||||
@wizard.id,
|
@wizard.id,
|
||||||
action['type'],
|
action['type'],
|
||||||
user.username,
|
username,
|
||||||
@log.join('; ')
|
@log.join('; ')
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
class CustomWizard::Builder
|
class CustomWizard::Builder
|
||||||
attr_accessor :wizard, :updater, :template
|
attr_accessor :wizard, :updater, :template
|
||||||
|
|
||||||
def initialize(wizard_id, user = nil)
|
def initialize(wizard_id, user = nil, guest_id = nil)
|
||||||
@template = CustomWizard::Template.create(wizard_id)
|
@template = CustomWizard::Template.create(wizard_id)
|
||||||
return nil if @template.nil?
|
return nil if @template.nil?
|
||||||
@wizard = CustomWizard::Wizard.new(template.data, user)
|
@wizard = CustomWizard::Wizard.new(template.data, user, guest_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.sorted_handlers
|
def self.sorted_handlers
|
||||||
|
@ -182,7 +182,7 @@ class CustomWizard::Builder
|
||||||
if field_template['description'].present?
|
if field_template['description'].present?
|
||||||
params[:description] = mapper.interpolate(
|
params[:description] = mapper.interpolate(
|
||||||
field_template['description'],
|
field_template['description'],
|
||||||
user: true,
|
user: @wizard.user,
|
||||||
value: true,
|
value: true,
|
||||||
wizard: true,
|
wizard: true,
|
||||||
template: true
|
template: true
|
||||||
|
@ -192,7 +192,7 @@ class CustomWizard::Builder
|
||||||
if field_template['preview_template'].present?
|
if field_template['preview_template'].present?
|
||||||
preview_template = mapper.interpolate(
|
preview_template = mapper.interpolate(
|
||||||
field_template['preview_template'],
|
field_template['preview_template'],
|
||||||
user: true,
|
user: @wizard.user,
|
||||||
value: true,
|
value: true,
|
||||||
wizard: true,
|
wizard: true,
|
||||||
template: true
|
template: true
|
||||||
|
@ -204,7 +204,7 @@ class CustomWizard::Builder
|
||||||
if field_template['placeholder'].present?
|
if field_template['placeholder'].present?
|
||||||
params[:placeholder] = mapper.interpolate(
|
params[:placeholder] = mapper.interpolate(
|
||||||
field_template['placeholder'],
|
field_template['placeholder'],
|
||||||
user: true,
|
user: @wizard.user,
|
||||||
value: true,
|
value: true,
|
||||||
wizard: true,
|
wizard: true,
|
||||||
template: true
|
template: true
|
||||||
|
@ -248,7 +248,7 @@ class CustomWizard::Builder
|
||||||
if step_template['description']
|
if step_template['description']
|
||||||
step.description = mapper.interpolate(
|
step.description = mapper.interpolate(
|
||||||
step_template['description'],
|
step_template['description'],
|
||||||
user: true,
|
user: @wizard.user,
|
||||||
value: true,
|
value: true,
|
||||||
wizard: true,
|
wizard: true,
|
||||||
template: true
|
template: true
|
||||||
|
|
|
@ -29,6 +29,11 @@ class CustomWizard::Field
|
||||||
attr_accessor :index,
|
attr_accessor :index,
|
||||||
:step
|
:step
|
||||||
|
|
||||||
|
REQUIRES_USER = %w[
|
||||||
|
composer
|
||||||
|
upload
|
||||||
|
]
|
||||||
|
|
||||||
def initialize(attrs)
|
def initialize(attrs)
|
||||||
@raw = attrs || {}
|
@raw = attrs || {}
|
||||||
@id = attrs[:id]
|
@id = attrs[:id]
|
||||||
|
|
|
@ -203,6 +203,8 @@ class CustomWizard::Mapper
|
||||||
end
|
end
|
||||||
|
|
||||||
def map_user_field(value)
|
def map_user_field(value)
|
||||||
|
return nil unless user
|
||||||
|
|
||||||
if value.include?(User::USER_FIELD_PREFIX)
|
if value.include?(User::USER_FIELD_PREFIX)
|
||||||
user.custom_fields[value]
|
user.custom_fields[value]
|
||||||
elsif PROFILE_FIELDS.include?(value)
|
elsif PROFILE_FIELDS.include?(value)
|
||||||
|
@ -229,7 +231,7 @@ class CustomWizard::Mapper
|
||||||
def interpolate(string, opts = { user: true, wizard: true, value: true, template: false })
|
def interpolate(string, opts = { user: true, wizard: true, value: true, template: false })
|
||||||
return string if string.blank? || string.frozen?
|
return string if string.blank? || string.frozen?
|
||||||
|
|
||||||
if opts[:user]
|
if opts[:user] && @user.present?
|
||||||
string.gsub!(/u\{(.*?)\}/) { |match| map_user_field($1) || '' }
|
string.gsub!(/u\{(.*?)\}/) { |match| map_user_field($1) || '' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -282,4 +284,8 @@ class CustomWizard::Mapper
|
||||||
user.avatar_template_url.gsub("{size}", parts.last)
|
user.avatar_template_url.gsub("{size}", parts.last)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.mapped_value?(value)
|
||||||
|
value.is_a?(Array) && value.all? { |v| v.is_a?(Hash) && v.key?("type") }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,8 +5,7 @@ class CustomWizard::StepUpdater
|
||||||
attr_accessor :refresh_required, :result
|
attr_accessor :refresh_required, :result
|
||||||
attr_reader :step, :submission
|
attr_reader :step, :submission
|
||||||
|
|
||||||
def initialize(current_user, wizard, step, submission)
|
def initialize(wizard, step, submission)
|
||||||
@current_user = current_user
|
|
||||||
@wizard = wizard
|
@wizard = wizard
|
||||||
@step = step
|
@step = step
|
||||||
@refresh_required = false
|
@refresh_required = false
|
||||||
|
@ -22,9 +21,9 @@ class CustomWizard::StepUpdater
|
||||||
|
|
||||||
@step.updater.call(self)
|
@step.updater.call(self)
|
||||||
|
|
||||||
UserHistory.create(
|
CustomWizard::UserHistory.create(
|
||||||
action: UserHistory.actions[:custom_wizard_step],
|
action: CustomWizard::UserHistory.actions[:step],
|
||||||
acting_user_id: @current_user.id,
|
actor_id: @wizard.actor_id,
|
||||||
context: @wizard.id,
|
context: @wizard.id,
|
||||||
subject: @step.id
|
subject: @step.id
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,8 +7,6 @@ class CustomWizard::Submission
|
||||||
META ||= %w(updated_at submitted_at route_to redirect_on_complete redirect_to)
|
META ||= %w(updated_at submitted_at route_to redirect_on_complete redirect_to)
|
||||||
|
|
||||||
attr_reader :id,
|
attr_reader :id,
|
||||||
:user,
|
|
||||||
:user_id,
|
|
||||||
:wizard
|
:wizard
|
||||||
|
|
||||||
attr_accessor :fields,
|
attr_accessor :fields,
|
||||||
|
@ -18,15 +16,8 @@ class CustomWizard::Submission
|
||||||
class_eval { attr_accessor attr }
|
class_eval { attr_accessor attr }
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(wizard, data = {}, user_id = nil)
|
def initialize(wizard, data = {})
|
||||||
@wizard = wizard
|
@wizard = wizard
|
||||||
@user_id = user_id
|
|
||||||
|
|
||||||
if user_id
|
|
||||||
@user = User.find_by(id: user_id)
|
|
||||||
else
|
|
||||||
@user = wizard.user
|
|
||||||
end
|
|
||||||
|
|
||||||
data = (data || {}).with_indifferent_access
|
data = (data || {}).with_indifferent_access
|
||||||
@id = data['id'] || SecureRandom.hex(12)
|
@id = data['id'] || SecureRandom.hex(12)
|
||||||
|
@ -44,13 +35,13 @@ class CustomWizard::Submission
|
||||||
return nil unless wizard.save_submissions
|
return nil unless wizard.save_submissions
|
||||||
validate
|
validate
|
||||||
|
|
||||||
submission_list = self.class.list(wizard, user_id: user.id)
|
submission_list = self.class.list(wizard)
|
||||||
submissions = submission_list.submissions.select { |submission| submission.id != self.id }
|
submissions = submission_list.submissions.select { |submission| submission.id != self.id }
|
||||||
self.updated_at = Time.now.iso8601
|
self.updated_at = Time.now.iso8601
|
||||||
submissions.push(self)
|
submissions.push(self)
|
||||||
|
|
||||||
submission_data = submissions.map { |submission| data_to_save(submission) }
|
submission_data = submissions.map { |submission| data_to_save(submission) }
|
||||||
PluginStore.set("#{wizard.id}_#{KEY}", user.id, submission_data)
|
PluginStore.set("#{wizard.id}_#{KEY}", wizard.actor_id, submission_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate
|
def validate
|
||||||
|
@ -93,25 +84,21 @@ class CustomWizard::Submission
|
||||||
data
|
data
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.get(wizard, user_id)
|
def self.get(wizard)
|
||||||
data = PluginStore.get("#{wizard.id}_#{KEY}", user_id).last
|
data = PluginStore.get("#{wizard.id}_#{KEY}", wizard.actor_id).last
|
||||||
new(wizard, data, user_id)
|
new(wizard, data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove
|
def remove
|
||||||
if present?
|
if present?
|
||||||
user_id = @user.id
|
data = PluginStore.get("#{@wizard.id}_#{KEY}", wizard.actor_id)
|
||||||
wizard_id = @wizard.id
|
data.delete_if { |sub| sub["id"] == @id }
|
||||||
submission_id = @id
|
PluginStore.set("#{@wizard.id}_#{KEY}", wizard.actor_id, data)
|
||||||
data = PluginStore.get("#{wizard_id}_#{KEY}", user_id)
|
|
||||||
data.delete_if { |sub| sub["id"] == submission_id }
|
|
||||||
PluginStore.set("#{wizard_id}_#{KEY}", user_id, data)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.cleanup_incomplete_submissions(wizard)
|
def self.cleanup_incomplete_submissions(wizard)
|
||||||
user_id = wizard.user.id
|
all_submissions = list(wizard)
|
||||||
all_submissions = list(wizard, user_id: user_id)
|
|
||||||
sorted_submissions = all_submissions.submissions.sort_by do |submission|
|
sorted_submissions = all_submissions.submissions.sort_by do |submission|
|
||||||
zero_epoch_time = DateTime.strptime("0", '%s')
|
zero_epoch_time = DateTime.strptime("0", '%s')
|
||||||
[
|
[
|
||||||
|
@ -129,12 +116,12 @@ class CustomWizard::Submission
|
||||||
end
|
end
|
||||||
|
|
||||||
valid_data = valid_submissions.map { |submission| submission.data_to_save(submission) }
|
valid_data = valid_submissions.map { |submission| submission.data_to_save(submission) }
|
||||||
PluginStore.set("#{wizard.id}_#{KEY}", user_id, valid_data)
|
PluginStore.set("#{wizard.id}_#{KEY}", wizard.actor_id, valid_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.list(wizard, user_id: nil, order_by: nil, page: nil)
|
def self.list(wizard, order_by: nil, page: nil)
|
||||||
params = { plugin_name: "#{wizard.id}_#{KEY}" }
|
params = { plugin_name: "#{wizard.id}_#{KEY}" }
|
||||||
params[:key] = user_id if user_id.present?
|
params[:key] = wizard.actor_id if wizard.actor_id
|
||||||
|
|
||||||
query = PluginStoreRow.where(params)
|
query = PluginStoreRow.where(params)
|
||||||
result = OpenStruct.new(submissions: [], total: nil)
|
result = OpenStruct.new(submissions: [], total: nil)
|
||||||
|
@ -142,7 +129,7 @@ class CustomWizard::Submission
|
||||||
query.each do |record|
|
query.each do |record|
|
||||||
if (submission_data = ::JSON.parse(record.value)).any?
|
if (submission_data = ::JSON.parse(record.value)).any?
|
||||||
submission_data.each do |data|
|
submission_data.each do |data|
|
||||||
result.submissions.push(new(wizard, data, record.key))
|
result.submissions.push(new(wizard, data))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,7 +17,7 @@ class CustomWizard::Subscription
|
||||||
none: [],
|
none: [],
|
||||||
standard: ['*'],
|
standard: ['*'],
|
||||||
business: ['*'],
|
business: ['*'],
|
||||||
community: ['*']
|
community: ['*', "!#{CustomWizard::Wizard::GUEST_GROUP_ID}"]
|
||||||
},
|
},
|
||||||
restart_on_revisit: {
|
restart_on_revisit: {
|
||||||
none: [],
|
none: [],
|
||||||
|
@ -114,8 +114,15 @@ class CustomWizard::Subscription
|
||||||
## Subscription type does not support the attribute.
|
## Subscription type does not support the attribute.
|
||||||
return false if values.blank?
|
return false if values.blank?
|
||||||
|
|
||||||
|
## Value is an exception for the subscription type
|
||||||
|
if (exceptions = get_exceptions(values)).any?
|
||||||
|
value = mapped_output(value) if CustomWizard::Mapper.mapped_value?(value)
|
||||||
|
value = [*value].map(&:to_s)
|
||||||
|
return false if (exceptions & value).length > 0
|
||||||
|
end
|
||||||
|
|
||||||
## Subscription type supports all values of the attribute.
|
## Subscription type supports all values of the attribute.
|
||||||
return true if values.first === "*"
|
return true if values.include?("*")
|
||||||
|
|
||||||
## Subscription type supports some values of the attributes.
|
## Subscription type supports some values of the attributes.
|
||||||
values.include?(value)
|
values.include?(value)
|
||||||
|
@ -192,4 +199,21 @@ class CustomWizard::Subscription
|
||||||
def self.includes?(feature, attribute, value)
|
def self.includes?(feature, attribute, value)
|
||||||
new.includes?(feature, attribute, value)
|
new.includes?(feature, attribute, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def get_exceptions(values)
|
||||||
|
values.reduce([]) do |result, value|
|
||||||
|
result << value.split("!").last if value.start_with?("!")
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def mapped_output(value)
|
||||||
|
value.reduce([]) do |result, v|
|
||||||
|
## We can only validate mapped assignment values at the moment
|
||||||
|
result << v["output"] if v.is_a?(Hash) && v["type"] === "assignment"
|
||||||
|
result
|
||||||
|
end.flatten
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,7 +23,6 @@ class CustomWizard::Template
|
||||||
normalize_data
|
normalize_data
|
||||||
validate_data
|
validate_data
|
||||||
prepare_data
|
prepare_data
|
||||||
|
|
||||||
return false if errors.any?
|
return false if errors.any?
|
||||||
|
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
|
|
54
lib/custom_wizard/user_history.rb
Normale Datei
54
lib/custom_wizard/user_history.rb
Normale Datei
|
@ -0,0 +1,54 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
UserHistory.actions[:custom_wizard_step] = 1000
|
||||||
|
|
||||||
|
class CustomWizard::UserHistory
|
||||||
|
def self.where(actor_id: nil, action: nil, context: nil, subject: nil)
|
||||||
|
::UserHistory.where(where_opts(actor_id, action, context, subject))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create(actor_id: nil, action: nil, context: nil, subject: nil)
|
||||||
|
::UserHistory.create(create_opts(actor_id, action, context, subject))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create!(actor_id: nil, action: nil, context: nil, subject: nil)
|
||||||
|
::UserHistory.create!(create_opts(actor_id, action, context, subject))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.actions
|
||||||
|
@actions ||=
|
||||||
|
Enum.new(
|
||||||
|
step: UserHistory.actions[:custom_wizard_step]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.where_opts(actor_id, action, context, subject)
|
||||||
|
opts = {
|
||||||
|
context: context
|
||||||
|
}
|
||||||
|
opts[:action] = action if action
|
||||||
|
opts[:subject] = subject if subject
|
||||||
|
add_actor(opts, actor_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create_opts(actor_id, action, context, subject)
|
||||||
|
opts = {
|
||||||
|
action: action,
|
||||||
|
context: context
|
||||||
|
}
|
||||||
|
opts[:subject] = subject if subject
|
||||||
|
add_actor(opts, actor_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.add_actor(opts, actor_id)
|
||||||
|
acting_user_id = actor_id
|
||||||
|
|
||||||
|
if actor_id.is_a?(String) && actor_id.include?(CustomWizard::Wizard::GUEST_ID_PREFIX)
|
||||||
|
opts[:acting_user_id] = Discourse.system_user.id
|
||||||
|
opts[:details] = actor_id
|
||||||
|
else
|
||||||
|
opts[:acting_user_id] = actor_id
|
||||||
|
end
|
||||||
|
|
||||||
|
opts
|
||||||
|
end
|
||||||
|
end
|
|
@ -30,6 +30,7 @@ class CustomWizard::TemplateValidator
|
||||||
validate_subscription(field, :field)
|
validate_subscription(field, :field)
|
||||||
check_required(field, :field)
|
check_required(field, :field)
|
||||||
validate_liquid_template(field, :field)
|
validate_liquid_template(field, :field)
|
||||||
|
validate_guests(field, :field)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -39,6 +40,7 @@ class CustomWizard::TemplateValidator
|
||||||
validate_subscription(action, :action)
|
validate_subscription(action, :action)
|
||||||
check_required(action, :action)
|
check_required(action, :action)
|
||||||
validate_liquid_template(action, :action)
|
validate_liquid_template(action, :action)
|
||||||
|
validate_guests(action, :action)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -80,6 +82,21 @@ class CustomWizard::TemplateValidator
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_guests(object, type)
|
||||||
|
guests_permitted = @data[:permitted] && @data[:permitted].any? do |m|
|
||||||
|
m["output"].include?(CustomWizard::Wizard::GUEST_GROUP_ID)
|
||||||
|
end
|
||||||
|
return unless guests_permitted
|
||||||
|
|
||||||
|
if type === :action && CustomWizard::Action::REQUIRES_USER.include?(object[:type])
|
||||||
|
errors.add :base, I18n.t("wizard.validation.not_permitted_for_guests", object_id: object[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
if type === :field && CustomWizard::Field::REQUIRES_USER.include?(object[:type])
|
||||||
|
errors.add :base, I18n.t("wizard.validation.not_permitted_for_guests", object_id: object[:id])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def validate_after_signup
|
def validate_after_signup
|
||||||
return unless ActiveRecord::Type::Boolean.new.cast(@data[:after_signup])
|
return unless ActiveRecord::Type::Boolean.new.cast(@data[:after_signup])
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,6 @@ require_dependency 'wizard/field'
|
||||||
require_dependency 'wizard/step_updater'
|
require_dependency 'wizard/step_updater'
|
||||||
require_dependency 'wizard/builder'
|
require_dependency 'wizard/builder'
|
||||||
|
|
||||||
UserHistory.actions[:custom_wizard_step] = 1000
|
|
||||||
|
|
||||||
class CustomWizard::Wizard
|
class CustomWizard::Wizard
|
||||||
include ActiveModel::SerializerSupport
|
include ActiveModel::SerializerSupport
|
||||||
|
|
||||||
|
@ -31,13 +29,22 @@ class CustomWizard::Wizard
|
||||||
:actions,
|
:actions,
|
||||||
:action_ids,
|
:action_ids,
|
||||||
:user,
|
:user,
|
||||||
|
:guest_id,
|
||||||
:submissions,
|
:submissions,
|
||||||
:template
|
:template
|
||||||
|
|
||||||
attr_reader :all_step_ids
|
attr_reader :all_step_ids
|
||||||
|
|
||||||
def initialize(attrs = {}, user = nil)
|
GUEST_ID_PREFIX ||= "guest"
|
||||||
|
GUEST_GROUP_ID = -1
|
||||||
|
|
||||||
|
def initialize(attrs = {}, user = nil, guest_id = nil)
|
||||||
|
if user
|
||||||
@user = user
|
@user = user
|
||||||
|
elsif guest_id
|
||||||
|
@guest_id = guest_id
|
||||||
|
end
|
||||||
|
|
||||||
attrs = attrs.with_indifferent_access
|
attrs = attrs.with_indifferent_access
|
||||||
|
|
||||||
@id = attrs['id']
|
@id = attrs['id']
|
||||||
|
@ -81,6 +88,10 @@ class CustomWizard::Wizard
|
||||||
@template = attrs
|
@template = attrs
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def actor_id
|
||||||
|
user ? user.id : guest_id
|
||||||
|
end
|
||||||
|
|
||||||
def cast_bool(val)
|
def cast_bool(val)
|
||||||
val.nil? ? false : ActiveRecord::Type::Boolean.new.cast(val)
|
val.nil? ? false : ActiveRecord::Type::Boolean.new.cast(val)
|
||||||
end
|
end
|
||||||
|
@ -141,17 +152,16 @@ class CustomWizard::Wizard
|
||||||
end
|
end
|
||||||
|
|
||||||
def last_completed_step_id
|
def last_completed_step_id
|
||||||
if user && unfinished? && last_completed_step = ::UserHistory.where(
|
return nil unless actor_id && unfinished?
|
||||||
acting_user_id: user.id,
|
|
||||||
action: ::UserHistory.actions[:custom_wizard_step],
|
last_completed_step = CustomWizard::UserHistory.where(
|
||||||
|
actor_id: actor_id,
|
||||||
|
action: CustomWizard::UserHistory.actions[:step],
|
||||||
context: id,
|
context: id,
|
||||||
subject: all_step_ids
|
subject: all_step_ids
|
||||||
).order("created_at").last
|
).order("created_at").last
|
||||||
|
|
||||||
last_completed_step.subject
|
last_completed_step&.subject
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_step(step_id)
|
def find_step(step_id)
|
||||||
|
@ -161,15 +171,15 @@ class CustomWizard::Wizard
|
||||||
def create_updater(step_id, submission)
|
def create_updater(step_id, submission)
|
||||||
step = @steps.find { |s| s.id == step_id }
|
step = @steps.find { |s| s.id == step_id }
|
||||||
wizard = self
|
wizard = self
|
||||||
CustomWizard::StepUpdater.new(user, wizard, step, submission)
|
CustomWizard::StepUpdater.new(wizard, step, submission)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unfinished?
|
def unfinished?
|
||||||
return nil if !user
|
return nil unless actor_id
|
||||||
|
|
||||||
most_recent = ::UserHistory.where(
|
most_recent = CustomWizard::UserHistory.where(
|
||||||
acting_user_id: user.id,
|
actor_id: actor_id,
|
||||||
action: ::UserHistory.actions[:custom_wizard_step],
|
action: CustomWizard::UserHistory.actions[:step],
|
||||||
context: id,
|
context: id,
|
||||||
).distinct.order('updated_at DESC').first
|
).distinct.order('updated_at DESC').first
|
||||||
|
|
||||||
|
@ -183,11 +193,11 @@ class CustomWizard::Wizard
|
||||||
end
|
end
|
||||||
|
|
||||||
def completed?
|
def completed?
|
||||||
return nil if !user
|
return nil unless actor_id
|
||||||
|
|
||||||
history = ::UserHistory.where(
|
history = CustomWizard::UserHistory.where(
|
||||||
acting_user_id: user.id,
|
actor_id: actor_id,
|
||||||
action: ::UserHistory.actions[:custom_wizard_step],
|
action: CustomWizard::UserHistory.actions[:step],
|
||||||
context: id
|
context: id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -200,8 +210,9 @@ class CustomWizard::Wizard
|
||||||
end
|
end
|
||||||
|
|
||||||
def permitted?
|
def permitted?
|
||||||
return false unless user
|
return nil unless actor_id
|
||||||
return true if user.admin? || permitted.blank?
|
return true if user && (user.admin? || permitted.blank?)
|
||||||
|
return false if !user && permitted.blank?
|
||||||
|
|
||||||
mapper = CustomWizard::Mapper.new(
|
mapper = CustomWizard::Mapper.new(
|
||||||
inputs: permitted,
|
inputs: permitted,
|
||||||
|
@ -215,6 +226,9 @@ class CustomWizard::Wizard
|
||||||
return true if mapper.blank?
|
return true if mapper.blank?
|
||||||
|
|
||||||
mapper.all? do |m|
|
mapper.all? do |m|
|
||||||
|
if !user
|
||||||
|
m[:type] === 'assignment' && [*m[:result]].include?(GUEST_GROUP_ID)
|
||||||
|
else
|
||||||
if m[:type] === 'assignment'
|
if m[:type] === 'assignment'
|
||||||
[*m[:result]].include?(Group::AUTO_GROUPS[:everyone]) ||
|
[*m[:result]].include?(Group::AUTO_GROUPS[:everyone]) ||
|
||||||
GroupUser.exists?(group_id: m[:result], user_id: user.id)
|
GroupUser.exists?(group_id: m[:result], user_id: user.id)
|
||||||
|
@ -225,17 +239,18 @@ class CustomWizard::Wizard
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def can_access?
|
def can_access?
|
||||||
return false unless user
|
permitted? && (user&.admin? || (multiple_submissions || !completed?))
|
||||||
return true if user.admin
|
|
||||||
permitted? && (multiple_submissions || !completed?)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset
|
def reset
|
||||||
::UserHistory.create(
|
return nil unless actor_id
|
||||||
action: ::UserHistory.actions[:custom_wizard_step],
|
|
||||||
acting_user_id: user.id,
|
CustomWizard::UserHistory.create(
|
||||||
|
action: CustomWizard::UserHistory.actions[:step],
|
||||||
|
actor_id: actor_id,
|
||||||
context: id,
|
context: id,
|
||||||
subject: "reset"
|
subject: "reset"
|
||||||
)
|
)
|
||||||
|
@ -263,8 +278,7 @@ class CustomWizard::Wizard
|
||||||
end
|
end
|
||||||
|
|
||||||
def submissions
|
def submissions
|
||||||
return nil unless user.present?
|
@submissions ||= CustomWizard::Submission.list(self).submissions
|
||||||
@submissions ||= CustomWizard::Submission.list(self, user_id: user.id).submissions
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_submission
|
def current_submission
|
||||||
|
@ -300,15 +314,17 @@ class CustomWizard::Wizard
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_user_redirect
|
def remove_user_redirect
|
||||||
|
return unless user.present?
|
||||||
|
|
||||||
if id == user.redirect_to_wizard
|
if id == user.redirect_to_wizard
|
||||||
user.custom_fields.delete('redirect_to_wizard')
|
user.custom_fields.delete('redirect_to_wizard')
|
||||||
user.save_custom_fields(true)
|
user.save_custom_fields(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.create(wizard_id, user = nil)
|
def self.create(wizard_id, user = nil, guest_id = nil)
|
||||||
if template = CustomWizard::Template.find(wizard_id)
|
if template = CustomWizard::Template.find(wizard_id)
|
||||||
new(template.to_h, user)
|
new(template.to_h, user, guest_id)
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -319,7 +335,7 @@ class CustomWizard::Wizard
|
||||||
|
|
||||||
CustomWizard::Template.list(**template_opts).reduce([]) do |result, template|
|
CustomWizard::Template.list(**template_opts).reduce([]) do |result, template|
|
||||||
wizard = new(template, user)
|
wizard = new(template, user)
|
||||||
result.push(wizard) if wizard.can_access? && (
|
result.push(wizard) if wizard.permitted? && (
|
||||||
!not_completed || !wizard.completed?
|
!not_completed || !wizard.completed?
|
||||||
)
|
)
|
||||||
result
|
result
|
||||||
|
@ -380,4 +396,8 @@ class CustomWizard::Wizard
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.generate_guest_id
|
||||||
|
"#{self::GUEST_ID_PREFIX}_#{SecureRandom.hex(12)}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
# name: discourse-custom-wizard
|
# name: discourse-custom-wizard
|
||||||
# about: Forms for Discourse. Better onboarding, structured posting, data enrichment, automated actions and much more.
|
# about: Forms for Discourse. Better onboarding, structured posting, data enrichment, automated actions and much more.
|
||||||
# version: 2.1.5
|
# version: 2.2.1
|
||||||
# authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George, Kaitlin Maddever
|
# authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George, Kaitlin Maddever
|
||||||
# url: https://github.com/paviliondev/discourse-custom-wizard
|
# url: https://github.com/paviliondev/discourse-custom-wizard
|
||||||
# contact_emails: development@pavilion.tech
|
# contact_emails: development@pavilion.tech
|
||||||
|
@ -41,6 +41,7 @@ after_initialize do
|
||||||
../app/controllers/custom_wizard/admin/logs.rb
|
../app/controllers/custom_wizard/admin/logs.rb
|
||||||
../app/controllers/custom_wizard/admin/manager.rb
|
../app/controllers/custom_wizard/admin/manager.rb
|
||||||
../app/controllers/custom_wizard/admin/custom_fields.rb
|
../app/controllers/custom_wizard/admin/custom_fields.rb
|
||||||
|
../app/controllers/custom_wizard/wizard_client.rb
|
||||||
../app/controllers/custom_wizard/wizard.rb
|
../app/controllers/custom_wizard/wizard.rb
|
||||||
../app/controllers/custom_wizard/steps.rb
|
../app/controllers/custom_wizard/steps.rb
|
||||||
../app/controllers/custom_wizard/realtime_validations.rb
|
../app/controllers/custom_wizard/realtime_validations.rb
|
||||||
|
@ -65,6 +66,7 @@ after_initialize do
|
||||||
../lib/custom_wizard/subscription.rb
|
../lib/custom_wizard/subscription.rb
|
||||||
../lib/custom_wizard/template.rb
|
../lib/custom_wizard/template.rb
|
||||||
../lib/custom_wizard/wizard.rb
|
../lib/custom_wizard/wizard.rb
|
||||||
|
../lib/custom_wizard/user_history.rb
|
||||||
../lib/custom_wizard/api/api.rb
|
../lib/custom_wizard/api/api.rb
|
||||||
../lib/custom_wizard/api/authorization.rb
|
../lib/custom_wizard/api/authorization.rb
|
||||||
../lib/custom_wizard/api/endpoint.rb
|
../lib/custom_wizard/api/endpoint.rb
|
||||||
|
|
|
@ -18,6 +18,7 @@ describe CustomWizard::Action do
|
||||||
let(:api_test_endpoint) { get_wizard_fixture("endpoints/test_endpoint") }
|
let(:api_test_endpoint) { get_wizard_fixture("endpoints/test_endpoint") }
|
||||||
let(:api_test_endpoint_body) { get_wizard_fixture("endpoints/test_endpoint_body") }
|
let(:api_test_endpoint_body) { get_wizard_fixture("endpoints/test_endpoint_body") }
|
||||||
let(:api_test_no_authorization) { get_wizard_fixture("api/no_authorization") }
|
let(:api_test_no_authorization) { get_wizard_fixture("api/no_authorization") }
|
||||||
|
let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
|
||||||
|
|
||||||
def update_template(template)
|
def update_template(template)
|
||||||
CustomWizard::Template.save(template, skip_jobs: true)
|
CustomWizard::Template.save(template, skip_jobs: true)
|
||||||
|
@ -78,8 +79,8 @@ describe CustomWizard::Action do
|
||||||
updater.update
|
updater.update
|
||||||
|
|
||||||
expect(updater.success?).to eq(true)
|
expect(updater.success?).to eq(true)
|
||||||
expect(UserHistory.where(
|
expect(CustomWizard::UserHistory.where(
|
||||||
acting_user_id: user.id,
|
actor_id: user.id,
|
||||||
context: "super_mega_fun_wizard",
|
context: "super_mega_fun_wizard",
|
||||||
subject: "step_3"
|
subject: "step_3"
|
||||||
).exists?).to eq(true)
|
).exists?).to eq(true)
|
||||||
|
@ -301,6 +302,28 @@ describe CustomWizard::Action do
|
||||||
expect(topic.first.allowed_groups.map(&:name)).to include('cool_group', 'cool_group_1')
|
expect(topic.first.allowed_groups.map(&:name)).to include('cool_group', 'cool_group_1')
|
||||||
expect(post.exists?).to eq(true)
|
expect(post.exists?).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "send_message works with guests are permitted" do
|
||||||
|
wizard_template["permitted"] = guests_permitted["permitted"]
|
||||||
|
wizard_template.delete("actions")
|
||||||
|
wizard_template['actions'] = [send_message]
|
||||||
|
update_template(wizard_template)
|
||||||
|
|
||||||
|
User.create(username: 'angus1', email: "angus1@email.com")
|
||||||
|
|
||||||
|
wizard = CustomWizard::Builder.new(wizard_template["id"], nil, CustomWizard::Wizard.generate_guest_id).build
|
||||||
|
wizard.create_updater(wizard.steps[0].id, {}).update
|
||||||
|
updater = wizard.create_updater(wizard.steps[1].id, {})
|
||||||
|
updater.update
|
||||||
|
|
||||||
|
topic = Topic.where(archetype: Archetype.private_message, title: "Message title")
|
||||||
|
post = Post.where(topic_id: topic.pluck(:id))
|
||||||
|
|
||||||
|
expect(topic.exists?).to eq(true)
|
||||||
|
expect(topic.first.topic_allowed_users.first.user.username).to eq('angus1')
|
||||||
|
expect(topic.first.topic_allowed_users.second.user.username).to eq(Discourse.system_user.username)
|
||||||
|
expect(post.exists?).to eq(true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "business subscription actions" do
|
context "business subscription actions" do
|
||||||
|
|
|
@ -80,15 +80,12 @@ describe CustomWizard::Builder do
|
||||||
|
|
||||||
it 'returns no steps if user has completed it' do
|
it 'returns no steps if user has completed it' do
|
||||||
@template[:steps].each do |step|
|
@template[:steps].each do |step|
|
||||||
UserHistory.create!(
|
CustomWizard::UserHistory.create!(
|
||||||
{
|
action: CustomWizard::UserHistory.actions[:step],
|
||||||
action: UserHistory.actions[:custom_wizard_step],
|
actor_id: user.id,
|
||||||
acting_user_id: user.id,
|
context: @template[:id],
|
||||||
context: @template[:id]
|
|
||||||
}.merge(
|
|
||||||
subject: step[:id]
|
subject: step[:id]
|
||||||
)
|
)
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
|
|
@ -4,6 +4,7 @@ describe CustomWizard::Submission do
|
||||||
fab!(:user) { Fabricate(:user) }
|
fab!(:user) { Fabricate(:user) }
|
||||||
fab!(:user2) { Fabricate(:user) }
|
fab!(:user2) { Fabricate(:user) }
|
||||||
let(:template_json) { get_wizard_fixture("wizard") }
|
let(:template_json) { get_wizard_fixture("wizard") }
|
||||||
|
let(:guest_id) { CustomWizard::Wizard.generate_guest_id }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
CustomWizard::Template.save(template_json, skip_jobs: true)
|
CustomWizard::Template.save(template_json, skip_jobs: true)
|
||||||
|
@ -13,10 +14,20 @@ describe CustomWizard::Submission do
|
||||||
|
|
||||||
it "saves a user's submission" do
|
it "saves a user's submission" do
|
||||||
expect(
|
expect(
|
||||||
described_class.get(@wizard, user.id).fields["step_1_field_1"]
|
described_class.get(@wizard).fields["step_1_field_1"]
|
||||||
).to eq("I am user submission")
|
).to eq("I am user submission")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "saves a guest's submission" do
|
||||||
|
CustomWizard::Template.save(template_json, skip_jobs: true)
|
||||||
|
@wizard = CustomWizard::Wizard.create(template_json["id"], nil, guest_id)
|
||||||
|
described_class.new(@wizard, step_1_field_1: "I am guest submission").save
|
||||||
|
|
||||||
|
expect(
|
||||||
|
described_class.get(@wizard).fields["step_1_field_1"]
|
||||||
|
).to eq("I am guest submission")
|
||||||
|
end
|
||||||
|
|
||||||
describe "#list" do
|
describe "#list" do
|
||||||
before do
|
before do
|
||||||
freeze_time Time.now
|
freeze_time Time.now
|
||||||
|
@ -37,14 +48,17 @@ describe CustomWizard::Submission do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "list submissions by wizard" do
|
it "list submissions by wizard" do
|
||||||
|
@wizard.user = nil
|
||||||
expect(described_class.list(@wizard).total).to eq(@count + 2)
|
expect(described_class.list(@wizard).total).to eq(@count + 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "list submissions by wizard and user" do
|
it "list submissions by wizard and user" do
|
||||||
expect(described_class.list(@wizard, user_id: user.id).total).to eq(@count + 1)
|
@wizard.user = user
|
||||||
|
expect(described_class.list(@wizard).total).to eq(@count + 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "paginates submission lists" do
|
it "paginates submission lists" do
|
||||||
|
@wizard.user = nil
|
||||||
expect(described_class.list(@wizard, page: 1).submissions.size).to eq((@count + 2) - CustomWizard::Submission::PAGE_LIMIT)
|
expect(described_class.list(@wizard, page: 1).submissions.size).to eq((@count + 2) - CustomWizard::Submission::PAGE_LIMIT)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -59,7 +73,7 @@ describe CustomWizard::Submission do
|
||||||
described_class.new(@wizard, step_1_field_1: "I am the second submission").save
|
described_class.new(@wizard, step_1_field_1: "I am the second submission").save
|
||||||
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
|
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
|
||||||
builder.build
|
builder.build
|
||||||
submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions
|
submissions = described_class.list(@wizard).submissions
|
||||||
|
|
||||||
expect(submissions.length).to eq(1)
|
expect(submissions.length).to eq(1)
|
||||||
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the second submission")
|
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the second submission")
|
||||||
|
@ -75,7 +89,7 @@ describe CustomWizard::Submission do
|
||||||
PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, sub_data)
|
PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, sub_data)
|
||||||
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
|
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
|
||||||
builder.build
|
builder.build
|
||||||
submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions
|
submissions = described_class.list(@wizard).submissions
|
||||||
|
|
||||||
expect(submissions.length).to eq(1)
|
expect(submissions.length).to eq(1)
|
||||||
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the second submission")
|
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the second submission")
|
||||||
|
@ -92,7 +106,7 @@ describe CustomWizard::Submission do
|
||||||
|
|
||||||
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
|
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
|
||||||
builder.build
|
builder.build
|
||||||
submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions
|
submissions = described_class.list(@wizard).submissions
|
||||||
|
|
||||||
expect(submissions.length).to eq(1)
|
expect(submissions.length).to eq(1)
|
||||||
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the third submission")
|
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the third submission")
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
describe CustomWizard::Subscription do
|
describe CustomWizard::Subscription do
|
||||||
|
let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
|
||||||
|
|
||||||
def undefine_client_classes
|
def undefine_client_classes
|
||||||
Object.send(:remove_const, :SubscriptionClient) if Object.constants.include?(:SubscriptionClient)
|
Object.send(:remove_const, :SubscriptionClient) if Object.constants.include?(:SubscriptionClient)
|
||||||
Object.send(:remove_const, :SubscriptionClientSubscription) if Object.constants.include?(:SubscriptionClientSubscription)
|
Object.send(:remove_const, :SubscriptionClientSubscription) if Object.constants.include?(:SubscriptionClientSubscription)
|
||||||
|
@ -40,7 +42,7 @@ describe CustomWizard::Subscription do
|
||||||
expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true)
|
expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "ubscriber features are not included" do
|
it "subscriber features are not included" do
|
||||||
expect(described_class.includes?(:wizard, :permitted, {})).to eq(false)
|
expect(described_class.includes?(:wizard, :permitted, {})).to eq(false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -69,6 +71,16 @@ describe CustomWizard::Subscription do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with a subscription" do
|
||||||
|
it "handles mapped values" do
|
||||||
|
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID)
|
||||||
|
expect(described_class.includes?(:wizard, :permitted, guests_permitted["permitted"])).to eq(true)
|
||||||
|
|
||||||
|
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::COMMUNITY_PRODUCT_ID)
|
||||||
|
expect(described_class.includes?(:wizard, :permitted, guests_permitted["permitted"])).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "with standard subscription" do
|
context "with standard subscription" do
|
||||||
before do
|
before do
|
||||||
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID)
|
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID)
|
||||||
|
|
|
@ -7,6 +7,8 @@ describe CustomWizard::TemplateValidator do
|
||||||
let(:user_condition) { get_wizard_fixture("condition/user_condition") }
|
let(:user_condition) { get_wizard_fixture("condition/user_condition") }
|
||||||
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
|
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
|
||||||
let(:composer_preview) { get_wizard_fixture("field/composer_preview") }
|
let(:composer_preview) { get_wizard_fixture("field/composer_preview") }
|
||||||
|
let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
|
||||||
|
let(:upload_field) { get_wizard_fixture("field/upload") }
|
||||||
|
|
||||||
let(:valid_liquid_template) {
|
let(:valid_liquid_template) {
|
||||||
<<-LIQUID.strip
|
<<-LIQUID.strip
|
||||||
|
@ -146,6 +148,20 @@ describe CustomWizard::TemplateValidator do
|
||||||
).to eq(true)
|
).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "validates user-only features" do
|
||||||
|
template[:permitted] = guests_permitted['permitted']
|
||||||
|
template[:steps][0][:fields] << upload_field
|
||||||
|
validator = CustomWizard::TemplateValidator.new(template)
|
||||||
|
expect(validator.perform).to eq(false)
|
||||||
|
errors = validator.errors.to_a
|
||||||
|
expect(errors).to include(
|
||||||
|
I18n.t("wizard.validation.not_permitted_for_guests", object_id: "action_1")
|
||||||
|
)
|
||||||
|
expect(errors).to include(
|
||||||
|
I18n.t("wizard.validation.not_permitted_for_guests", object_id: "step_2_field_7")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
it "validates step attributes" do
|
it "validates step attributes" do
|
||||||
template[:steps][0][:condition] = user_condition['condition']
|
template[:steps][0][:condition] = user_condition['condition']
|
||||||
expect(
|
expect(
|
||||||
|
|
|
@ -21,10 +21,10 @@ describe CustomWizard::Wizard do
|
||||||
@wizard.update!
|
@wizard.update!
|
||||||
end
|
end
|
||||||
|
|
||||||
def progress_step(step_id, acting_user: user, wizard: @wizard)
|
def progress_step(step_id, actor_id: user.id, wizard: @wizard)
|
||||||
UserHistory.create(
|
CustomWizard::UserHistory.create(
|
||||||
action: UserHistory.actions[:custom_wizard_step],
|
action: CustomWizard::UserHistory.actions[:step],
|
||||||
acting_user_id: acting_user.id,
|
actor_id: actor_id,
|
||||||
context: wizard.id,
|
context: wizard.id,
|
||||||
subject: step_id
|
subject: step_id
|
||||||
)
|
)
|
||||||
|
@ -158,9 +158,9 @@ describe CustomWizard::Wizard do
|
||||||
it "lets a permitted user access a complete wizard with multiple submissions" do
|
it "lets a permitted user access a complete wizard with multiple submissions" do
|
||||||
append_steps
|
append_steps
|
||||||
|
|
||||||
progress_step("step_1", acting_user: trusted_user)
|
progress_step("step_1", actor_id: trusted_user.id)
|
||||||
progress_step("step_2", acting_user: trusted_user)
|
progress_step("step_2", actor_id: trusted_user.id)
|
||||||
progress_step("step_3", acting_user: trusted_user)
|
progress_step("step_3", actor_id: trusted_user.id)
|
||||||
|
|
||||||
@permitted_template["multiple_submissions"] = true
|
@permitted_template["multiple_submissions"] = true
|
||||||
|
|
||||||
|
@ -172,9 +172,9 @@ describe CustomWizard::Wizard do
|
||||||
it "does not let an unpermitted user access a complete wizard without multiple submissions" do
|
it "does not let an unpermitted user access a complete wizard without multiple submissions" do
|
||||||
append_steps
|
append_steps
|
||||||
|
|
||||||
progress_step("step_1", acting_user: trusted_user)
|
progress_step("step_1", actor_id: trusted_user.id)
|
||||||
progress_step("step_2", acting_user: trusted_user)
|
progress_step("step_2", actor_id: trusted_user.id)
|
||||||
progress_step("step_3", acting_user: trusted_user)
|
progress_step("step_3", actor_id: trusted_user.id)
|
||||||
|
|
||||||
@permitted_template['multiple_submissions'] = false
|
@permitted_template['multiple_submissions'] = false
|
||||||
|
|
||||||
|
|
12
spec/fixtures/actions/route_to.json
gevendort
Normale Datei
12
spec/fixtures/actions/route_to.json
gevendort
Normale Datei
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"id": "route_to",
|
||||||
|
"type": "route_to",
|
||||||
|
"url": [
|
||||||
|
{
|
||||||
|
"type": "assignment",
|
||||||
|
"output": "https://google.com",
|
||||||
|
"output_type": "text",
|
||||||
|
"output_connector": "set"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
6
spec/fixtures/field/upload.json
gevendort
Normale Datei
6
spec/fixtures/field/upload.json
gevendort
Normale Datei
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"id": "step_2_field_7",
|
||||||
|
"label": "Upload",
|
||||||
|
"type": "upload",
|
||||||
|
"file_types": ".jpg,.jpeg,.png"
|
||||||
|
}
|
6
spec/fixtures/wizard.json
gevendort
6
spec/fixtures/wizard.json
gevendort
|
@ -74,12 +74,6 @@
|
||||||
"id": "step_2_field_5",
|
"id": "step_2_field_5",
|
||||||
"label": "Checkbox",
|
"label": "Checkbox",
|
||||||
"type": "checkbox"
|
"type": "checkbox"
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "step_2_field_7",
|
|
||||||
"label": "Upload",
|
|
||||||
"type": "upload",
|
|
||||||
"file_types": ".jpg,.jpeg,.png"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Because I couldn't think of another name for this step :)"
|
"description": "Because I couldn't think of another name for this step :)"
|
||||||
|
|
12
spec/fixtures/wizard/guests_permitted.json
gevendort
Normale Datei
12
spec/fixtures/wizard/guests_permitted.json
gevendort
Normale Datei
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"permitted": [
|
||||||
|
{
|
||||||
|
"type": "assignment",
|
||||||
|
"output_type": "group",
|
||||||
|
"output_connector": "set",
|
||||||
|
"output": [
|
||||||
|
-1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -7,9 +7,111 @@ describe CustomWizard::StepsController do
|
||||||
let(:wizard_field_condition_template) { get_wizard_fixture("condition/wizard_field_condition") }
|
let(:wizard_field_condition_template) { get_wizard_fixture("condition/wizard_field_condition") }
|
||||||
let(:user_condition_template) { get_wizard_fixture("condition/user_condition") }
|
let(:user_condition_template) { get_wizard_fixture("condition/user_condition") }
|
||||||
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
|
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
|
||||||
|
let(:route_to_template) { get_wizard_fixture("actions/route_to") }
|
||||||
|
let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
CustomWizard::Template.save(wizard_template, skip_jobs: true)
|
CustomWizard::Template.save(wizard_template, skip_jobs: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def guest_template
|
||||||
|
temp = wizard_template.dup
|
||||||
|
temp["permitted"] = guests_permitted["permitted"]
|
||||||
|
temp.delete("actions")
|
||||||
|
temp["actions"] = [route_to_template]
|
||||||
|
temp
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with guest" do
|
||||||
|
it "does not perform a step update" do
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
|
||||||
|
fields: {
|
||||||
|
step_1_field_1: "Text input"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with guests permitted" do
|
||||||
|
before do
|
||||||
|
enable_subscription("standard")
|
||||||
|
result = CustomWizard::Template.save(guest_template, skip_jobs: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "performs a step update" do
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
|
||||||
|
fields: {
|
||||||
|
step_1_field_1: "Text input"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.parsed_body['wizard']['start']).to eq("step_2")
|
||||||
|
|
||||||
|
wizard_id = response.parsed_body['wizard']['id']
|
||||||
|
wizard = CustomWizard::Wizard.create(wizard_id, nil, cookies[:custom_wizard_guest_id])
|
||||||
|
expect(wizard.current_submission.fields['step_1_field_1']).to eq("Text input")
|
||||||
|
end
|
||||||
|
|
||||||
|
context "raises an error" do
|
||||||
|
it "when the wizard doesnt exist" do
|
||||||
|
put '/w/not-super-mega-fun-wizard/steps/step_1.json'
|
||||||
|
expect(response.status).to eq(400)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "when the user cant access the wizard" do
|
||||||
|
enable_subscription("standard")
|
||||||
|
new_template = guest_template.dup
|
||||||
|
new_template["permitted"] = permitted_json["permitted"]
|
||||||
|
CustomWizard::Template.save(new_template, skip_jobs: true)
|
||||||
|
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_1.json'
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "when the step doesnt exist" do
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_10.json'
|
||||||
|
expect(response.status).to eq(400)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "works if the step has no fields" do
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_1.json'
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.parsed_body['wizard']['start']).to eq("step_2")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an updated wizard when condition passes" do
|
||||||
|
new_template = guest_template.dup
|
||||||
|
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
|
||||||
|
CustomWizard::Template.save(new_template, skip_jobs: true)
|
||||||
|
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
|
||||||
|
fields: {
|
||||||
|
step_1_field_1: "Condition will pass"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.parsed_body['wizard']['start']).to eq("step_2")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "runs completion actions if guest has completed wizard" do
|
||||||
|
new_template = guest_template.dup
|
||||||
|
|
||||||
|
## route_to action
|
||||||
|
new_template['actions'].last['run_after'] = 'wizard_completion'
|
||||||
|
CustomWizard::Template.save(new_template, skip_jobs: true)
|
||||||
|
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_1.json'
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_2.json'
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_3.json'
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.parsed_body['redirect_on_complete']).to eq("https://google.com")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with user" do
|
||||||
|
before do
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -267,3 +369,4 @@ describe CustomWizard::StepsController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -8,7 +8,6 @@ describe CustomWizard::WizardController do
|
||||||
before do
|
before do
|
||||||
CustomWizard::Template.save(wizard_template, skip_jobs: true)
|
CustomWizard::Template.save(wizard_template, skip_jobs: true)
|
||||||
@template = CustomWizard::Template.find("super_mega_fun_wizard")
|
@template = CustomWizard::Template.find("super_mega_fun_wizard")
|
||||||
sign_in(user)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'plugin disabled' do
|
context 'plugin disabled' do
|
||||||
|
@ -32,8 +31,12 @@ describe CustomWizard::WizardController do
|
||||||
expect(response.parsed_body["error"]).to eq("We couldn't find a wizard at that address.")
|
expect(response.parsed_body["error"]).to eq("We couldn't find a wizard at that address.")
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when user skips the wizard' do
|
context "with user" do
|
||||||
|
before do
|
||||||
|
sign_in(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user skips' do
|
||||||
it 'skips a wizard if user is allowed to skip' do
|
it 'skips a wizard if user is allowed to skip' do
|
||||||
put '/w/super-mega-fun-wizard/skip.json'
|
put '/w/super-mega-fun-wizard/skip.json'
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
@ -94,3 +97,4 @@ describe CustomWizard::WizardController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -29,6 +29,5 @@ describe CustomWizard::FieldSerializer do
|
||||||
scope: Guardian.new(user)
|
scope: Guardian.new(user)
|
||||||
).as_json
|
).as_json
|
||||||
expect(json_array[0][:format]).to eq("YYYY-MM-DD")
|
expect(json_array[0][:format]).to eq("YYYY-MM-DD")
|
||||||
expect(json_array[5][:file_types]).to eq(".jpg,.jpeg,.png")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
import {
|
import {
|
||||||
wizard,
|
wizard,
|
||||||
wizardCompleted,
|
wizardCompleted,
|
||||||
|
wizardGuest,
|
||||||
wizardNoUser,
|
wizardNoUser,
|
||||||
wizardNotPermitted,
|
wizardNotPermitted,
|
||||||
} from "../helpers/wizard";
|
} from "../helpers/wizard";
|
||||||
|
@ -106,3 +107,59 @@ acceptance("Wizard | Wizard", function (needs) {
|
||||||
assert.strictEqual($("body.custom-wizard").length, 0);
|
assert.strictEqual($("body.custom-wizard").length, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
acceptance("Wizard | Guest access", function (needs) {
|
||||||
|
needs.pretender((server, helper) => {
|
||||||
|
server.get("/w/wizard.json", () => helper.response(wizardGuest));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Does not require login", async function (assert) {
|
||||||
|
await visit("/w/wizard");
|
||||||
|
assert.ok(!exists(".wizard-no-access.requires-login"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Starts", async function (assert) {
|
||||||
|
await visit("/w/wizard");
|
||||||
|
assert.ok(query(".wizard-column"), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Applies the wizard body class", async function (assert) {
|
||||||
|
await visit("/w/wizard");
|
||||||
|
assert.ok($("body.custom-wizard").length);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Applies the body background color", async function (assert) {
|
||||||
|
await visit("/w/wizard");
|
||||||
|
assert.ok($("body")[0].style.background);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Renders the wizard form", async function (assert) {
|
||||||
|
await visit("/w/wizard");
|
||||||
|
assert.ok(exists(".wizard-column-contents .wizard-step"), true);
|
||||||
|
assert.ok(exists(".wizard-footer img"), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Renders the first step", async function (assert) {
|
||||||
|
await visit("/w/wizard");
|
||||||
|
assert.strictEqual(
|
||||||
|
query(".wizard-step-title p").textContent.trim(),
|
||||||
|
"Text"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
query(".wizard-step-description p").textContent.trim(),
|
||||||
|
"Text inputs!"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
query(".wizard-step-description p").textContent.trim(),
|
||||||
|
"Text inputs!"
|
||||||
|
);
|
||||||
|
assert.strictEqual(count(".wizard-step-form .wizard-field"), 6);
|
||||||
|
assert.ok(exists(".wizard-step-footer .wizard-progress"), true);
|
||||||
|
assert.ok(exists(".wizard-step-footer .wizard-buttons"), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Removes the wizard body class when navigating away", async function (assert) {
|
||||||
|
await visit("/");
|
||||||
|
assert.strictEqual($("body.custom-wizard").length, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -6,7 +6,7 @@ export default {
|
||||||
submission_last_updated_at: "2022-03-15T21:11:01+01:00",
|
submission_last_updated_at: "2022-03-15T21:11:01+01:00",
|
||||||
theme_id: 2,
|
theme_id: 2,
|
||||||
required: false,
|
required: false,
|
||||||
permitted: true,
|
permitted: false,
|
||||||
uncategorized_category_id: 1,
|
uncategorized_category_id: 1,
|
||||||
categories: [],
|
categories: [],
|
||||||
subscribed: false,
|
subscribed: false,
|
||||||
|
|
|
@ -6,8 +6,11 @@ import updateJson from "../fixtures/update";
|
||||||
import { cloneJSON } from "discourse-common/lib/object";
|
import { cloneJSON } from "discourse-common/lib/object";
|
||||||
|
|
||||||
const wizardNoUser = cloneJSON(wizardJson);
|
const wizardNoUser = cloneJSON(wizardJson);
|
||||||
|
const wizardGuest = cloneJSON(wizardJson);
|
||||||
|
wizardGuest.permitted = true;
|
||||||
const wizard = cloneJSON(wizardJson);
|
const wizard = cloneJSON(wizardJson);
|
||||||
wizard.user = cloneJSON(userJson);
|
wizard.user = cloneJSON(userJson);
|
||||||
|
wizard.permitted = true;
|
||||||
|
|
||||||
const wizardNotPermitted = cloneJSON(wizard);
|
const wizardNotPermitted = cloneJSON(wizard);
|
||||||
wizardNotPermitted.permitted = false;
|
wizardNotPermitted.permitted = false;
|
||||||
|
@ -40,6 +43,7 @@ export {
|
||||||
wizardNoUser,
|
wizardNoUser,
|
||||||
wizardNotPermitted,
|
wizardNotPermitted,
|
||||||
wizardCompleted,
|
wizardCompleted,
|
||||||
|
wizardGuest,
|
||||||
stepNotPermitted,
|
stepNotPermitted,
|
||||||
allFieldsWizard,
|
allFieldsWizard,
|
||||||
wizard,
|
wizard,
|
||||||
|
|
Laden …
In neuem Issue referenzieren