0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-11-25 18:50:27 +01:00

Merge pull request #216 from paviliondev/add_guest_support

Add guest support
Dieser Commit ist enthalten in:
Robert 2023-02-10 13:51:21 +00:00 committet von GitHub
Commit f5d265846d
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
46 geänderte Dateien mit 899 neuen und 417 gelöschten Zeilen

Datei anzeigen

@ -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
it under the terms of the GNU General Public License as published by

Datei anzeigen

@ -1,6 +1,5 @@
# frozen_string_literal: true
class CustomWizard::StepsController < ::ApplicationController
before_action :ensure_logged_in
class CustomWizard::StepsController < ::CustomWizard::WizardClientController
before_action :ensure_can_update
def update
@ -22,7 +21,7 @@ class CustomWizard::StepsController < ::ApplicationController
if updater.success?
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)
current_step = @wizard.find_step(update[:step_id])
@ -85,7 +84,6 @@ class CustomWizard::StepsController < ::ApplicationController
private
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::InvalidAccess.new if !@builder.wizard || !@builder.wizard.can_access?

Datei anzeigen

@ -1,8 +1,5 @@
# frozen_string_literal: true
class CustomWizard::WizardController < ::ApplicationController
before_action :ensure_plugin_enabled
before_action :ensure_logged_in, only: [:skip]
class CustomWizard::WizardController < ::CustomWizard::WizardClientController
def show
if wizard.present?
render json: CustomWizard::WizardSerializer.new(wizard, scope: guardian, root: false).as_json, status: 200
@ -35,19 +32,8 @@ class CustomWizard::WizardController < ::ApplicationController
def wizard
@wizard ||= begin
builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user)
return nil unless builder.present?
opts = {}
opts[:reset] = params[:reset]
builder.build(opts, params)
end
end
private
def ensure_plugin_enabled
unless SiteSetting.custom_wizard_enabled
redirect_to path("/")
return nil unless @builder.present?
@builder.build({ reset: params[:reset] }, params)
end
end
end

Datei anzeigen

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

Datei anzeigen

@ -2,12 +2,15 @@
class CustomWizard::SubmissionSerializer < ApplicationSerializer
attributes :id,
:fields,
:submitted_at
has_one :user, serializer: ::BasicUserSerializer, embed: :objects
:submitted_at,
:user
def include_user?
object.user.present?
object.wizard.user.present?
end
def user
::BasicUserSerializer.new(object.wizard.user).as_json
end
def fields

Datei anzeigen

@ -15,6 +15,7 @@ import {
import Component from "@ember/component";
import { bind, later } from "@ember/runloop";
import I18n from "I18n";
import Subscription from "../mixins/subscription";
const customFieldActionMap = {
topic: ["create_topic", "send_message"],
@ -26,7 +27,7 @@ const customFieldActionMap = {
const values = ["present", "true", "false"];
export default Component.extend({
export default Component.extend(Subscription, {
classNameBindings: [":mapper-selector", "activeType"],
showText: computed("activeType", function () {
@ -116,6 +117,9 @@ export default Component.extend({
groupEnabled: computed("options.groupSelection", "inputType", function () {
return this.optionEnabled("groupSelection");
}),
guestGroup: computed("options.guestGroup", "inputType", function () {
return this.optionEnabled("guestGroup");
}),
userEnabled: computed("options.userSelection", "inputType", function () {
return this.optionEnabled("userSelection");
}),
@ -126,7 +130,29 @@ export default Component.extend({
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"),
showComboBox: or(
"showWizardField",

Datei anzeigen

@ -32,6 +32,7 @@ export default Component.extend({
pairConnector: options.pairConnector || null,
outputConnector: options.outputConnector || null,
context: options.context || null,
guestGroup: options.guestGroup || false,
};
let inputTypes = ["key", "value", "output"];

Datei anzeigen

@ -1,6 +1,6 @@
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 { filterValues } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n";
@ -40,9 +40,9 @@ export default SingleSelectComponent.extend(Subscription, {
return allowedTypes;
},
@discourseComputed("feature", "attribute")
@discourseComputed("feature", "attribute", "wizard.allowGuests")
content(feature, attribute) {
return wizardSchema[feature][attribute]
return filterValues(this.wizard, feature, attribute)
.map((value) => {
let allowedSubscriptionTypes = this.allowedSubscriptionTypes(
feature,

Datei anzeigen

@ -10,6 +10,7 @@ import { later, scheduleOnce } from "@ember/runloop";
import Controller from "@ember/controller";
import copyText from "discourse/lib/copy-text";
import I18n from "I18n";
import { filterValues } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
export default Controller.extend({
hasName: notEmpty("wizard.name"),
@ -59,6 +60,19 @@ export default Controller.extend({
}
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) {
if (result.backend_validation_error) {
return result.backend_validation_error;

Datei anzeigen

@ -210,6 +210,32 @@ const action = {
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 = {
klass: ["topic", "post", "group", "category"],
type: ["string", "boolean", "integer", "json"],
@ -224,16 +250,29 @@ const wizardSchema = {
field,
custom_field,
action,
filters,
};
export function buildFieldTypes(types) {
wizardSchema.field.types = types;
}
export function buildFieldValidations(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");
if (siteSettings.wizard_apis_enabled) {
wizardSchema.action.types.send_to_api = {

Datei anzeigen

@ -5,8 +5,20 @@ import wizardSchema from "../lib/wizard-schema";
import { Promise } from "rsvp";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import discourseComputed from "discourse-common/utils/decorators";
const GUEST_GROUP_ID = -1;
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) {
return new Promise((resolve, reject) => {
let wizard = this.buildJson(this, "wizard");

Datei anzeigen

@ -1,5 +1,5 @@
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 { A } from "@ember/array";
import { all } from "rsvp";
@ -11,7 +11,6 @@ export default DiscourseRoute.extend({
},
afterModel(model) {
buildFieldTypes(model.field_types);
buildFieldValidations(model.realtime_validations);
return all([

Datei anzeigen

@ -4,13 +4,7 @@ import Route from "@ember/routing/route";
export default Route.extend({
beforeModel() {
const wizard = getCachedWizard();
if (
wizard &&
wizard.user &&
wizard.permitted &&
!wizard.completed &&
wizard.start
) {
if (wizard && wizard.permitted && !wizard.completed && wizard.start) {
this.replaceWith("customWizardStep", wizard.start);
}
},
@ -26,7 +20,7 @@ export default Route.extend({
const wizardId = model.get("id");
const user = model.get("user");
const name = model.get("name");
const requiresLogin = !user;
const requiresLogin = !user && !permitted;
const notPermitted = !permitted;
const props = {

Datei anzeigen

@ -7,7 +7,7 @@ export default Route.extend({
const wizard = getCachedWizard();
this.set("wizard", wizard);
if (!wizard || !wizard.user || !wizard.permitted || wizard.completed) {
if (!wizard || !wizard.permitted || wizard.completed) {
this.replaceWith("customWizard");
}
},

Datei anzeigen

@ -140,6 +140,7 @@
context="wizard"
inputTypes="assignment,validation"
groupSelection="output"
guestGroup=true
userFieldSelection="key"
textSelection="value"
inputConnector="and"
@ -160,7 +161,7 @@
wizard=wizard
currentField=currentField
wizardFields=wizardFields
fieldTypes=fieldTypes
fieldTypes=filteredFieldTypes
subscribed=subscribed}}
{{/if}}
@ -178,7 +179,7 @@
apis=apis
removeAction="removeAction"
wizardFields=wizardFields
fieldTypes=fieldTypes}}
fieldTypes=filteredFieldTypes}}
{{/each}}
<div class="admin-wizard-buttons">

Datei anzeigen

@ -17,6 +17,7 @@
feature="action"
attribute="type"
onChange=(action "changeType")
wizard=wizard
options=(hash
none="admin.wizard.select_type"
)

Datei anzeigen

@ -217,6 +217,8 @@ en:
list: "list"
custom_field: "custom field"
value: "value"
users: "users"
guests: "users and guests"
placeholder:
text: "Enter text"

Datei anzeigen

@ -53,7 +53,8 @@ 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}"
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:
custom_wizard_enabled: "Enable custom wizards."

Datei anzeigen

@ -6,6 +6,14 @@ class CustomWizard::Action
:guardian,
:result
REQUIRES_USER = %w[
create_topic
update_profile
open_composer
watch_categories
add_to_group
]
def initialize(opts)
@wizard = opts[:wizard]
@action = opts[:action]
@ -17,6 +25,12 @@ class CustomWizard::Action
end
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
self.send(action['type'].to_sym)
end
@ -76,7 +90,6 @@ class CustomWizard::Action
end
def send_message
if action['required'].present?
required = CustomWizard::Mapper.new(
inputs: action['required'],
@ -123,13 +136,14 @@ class CustomWizard::Action
params[:archetype] = Archetype.private_message
creator = PostCreator.new(user, params)
poster = user || Discourse.system_user
creator = PostCreator.new(poster, params)
post = creator.create
if creator.errors.present?
messages = creator.errors.full_messages.join(" ")
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
end
@ -809,10 +823,12 @@ class CustomWizard::Action
end
def save_log
username = user ? user.username : @wizard.actor_id
CustomWizard::Log.create(
@wizard.id,
action['type'],
user.username,
username,
@log.join('; ')
)
end

Datei anzeigen

@ -2,10 +2,10 @@
class CustomWizard::Builder
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)
return nil if @template.nil?
@wizard = CustomWizard::Wizard.new(template.data, user)
@wizard = CustomWizard::Wizard.new(template.data, user, guest_id)
end
def self.sorted_handlers
@ -182,7 +182,7 @@ class CustomWizard::Builder
if field_template['description'].present?
params[:description] = mapper.interpolate(
field_template['description'],
user: true,
user: @wizard.user,
value: true,
wizard: true,
template: true
@ -192,7 +192,7 @@ class CustomWizard::Builder
if field_template['preview_template'].present?
preview_template = mapper.interpolate(
field_template['preview_template'],
user: true,
user: @wizard.user,
value: true,
wizard: true,
template: true
@ -204,7 +204,7 @@ class CustomWizard::Builder
if field_template['placeholder'].present?
params[:placeholder] = mapper.interpolate(
field_template['placeholder'],
user: true,
user: @wizard.user,
value: true,
wizard: true,
template: true
@ -248,7 +248,7 @@ class CustomWizard::Builder
if step_template['description']
step.description = mapper.interpolate(
step_template['description'],
user: true,
user: @wizard.user,
value: true,
wizard: true,
template: true

Datei anzeigen

@ -29,6 +29,11 @@ class CustomWizard::Field
attr_accessor :index,
:step
REQUIRES_USER = %w[
composer
upload
]
def initialize(attrs)
@raw = attrs || {}
@id = attrs[:id]

Datei anzeigen

@ -203,6 +203,8 @@ class CustomWizard::Mapper
end
def map_user_field(value)
return nil unless user
if value.include?(User::USER_FIELD_PREFIX)
user.custom_fields[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 })
return string if string.blank? || string.frozen?
if opts[:user]
if opts[:user] && @user.present?
string.gsub!(/u\{(.*?)\}/) { |match| map_user_field($1) || '' }
end
@ -282,4 +284,8 @@ class CustomWizard::Mapper
user.avatar_template_url.gsub("{size}", parts.last)
end
end
def self.mapped_value?(value)
value.is_a?(Array) && value.all? { |v| v.is_a?(Hash) && v.key?("type") }
end
end

Datei anzeigen

@ -5,8 +5,7 @@ class CustomWizard::StepUpdater
attr_accessor :refresh_required, :result
attr_reader :step, :submission
def initialize(current_user, wizard, step, submission)
@current_user = current_user
def initialize(wizard, step, submission)
@wizard = wizard
@step = step
@refresh_required = false
@ -22,9 +21,9 @@ class CustomWizard::StepUpdater
@step.updater.call(self)
UserHistory.create(
action: UserHistory.actions[:custom_wizard_step],
acting_user_id: @current_user.id,
CustomWizard::UserHistory.create(
action: CustomWizard::UserHistory.actions[:step],
actor_id: @wizard.actor_id,
context: @wizard.id,
subject: @step.id
)

Datei anzeigen

@ -7,8 +7,6 @@ class CustomWizard::Submission
META ||= %w(updated_at submitted_at route_to redirect_on_complete redirect_to)
attr_reader :id,
:user,
:user_id,
:wizard
attr_accessor :fields,
@ -18,15 +16,8 @@ class CustomWizard::Submission
class_eval { attr_accessor attr }
end
def initialize(wizard, data = {}, user_id = nil)
def initialize(wizard, data = {})
@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
@id = data['id'] || SecureRandom.hex(12)
@ -44,13 +35,13 @@ class CustomWizard::Submission
return nil unless wizard.save_submissions
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 }
self.updated_at = Time.now.iso8601
submissions.push(self)
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
def validate
@ -93,25 +84,21 @@ class CustomWizard::Submission
data
end
def self.get(wizard, user_id)
data = PluginStore.get("#{wizard.id}_#{KEY}", user_id).last
new(wizard, data, user_id)
def self.get(wizard)
data = PluginStore.get("#{wizard.id}_#{KEY}", wizard.actor_id).last
new(wizard, data)
end
def remove
if present?
user_id = @user.id
wizard_id = @wizard.id
submission_id = @id
data = PluginStore.get("#{wizard_id}_#{KEY}", user_id)
data.delete_if { |sub| sub["id"] == submission_id }
PluginStore.set("#{wizard_id}_#{KEY}", user_id, data)
data = PluginStore.get("#{@wizard.id}_#{KEY}", wizard.actor_id)
data.delete_if { |sub| sub["id"] == @id }
PluginStore.set("#{@wizard.id}_#{KEY}", wizard.actor_id, data)
end
end
def self.cleanup_incomplete_submissions(wizard)
user_id = wizard.user.id
all_submissions = list(wizard, user_id: user_id)
all_submissions = list(wizard)
sorted_submissions = all_submissions.submissions.sort_by do |submission|
zero_epoch_time = DateTime.strptime("0", '%s')
[
@ -129,12 +116,12 @@ class CustomWizard::Submission
end
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
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[:key] = user_id if user_id.present?
params[:key] = wizard.actor_id if wizard.actor_id
query = PluginStoreRow.where(params)
result = OpenStruct.new(submissions: [], total: nil)
@ -142,7 +129,7 @@ class CustomWizard::Submission
query.each do |record|
if (submission_data = ::JSON.parse(record.value)).any?
submission_data.each do |data|
result.submissions.push(new(wizard, data, record.key))
result.submissions.push(new(wizard, data))
end
end
end

Datei anzeigen

@ -17,7 +17,7 @@ class CustomWizard::Subscription
none: [],
standard: ['*'],
business: ['*'],
community: ['*']
community: ['*', "!#{CustomWizard::Wizard::GUEST_GROUP_ID}"]
},
restart_on_revisit: {
none: [],
@ -114,8 +114,15 @@ class CustomWizard::Subscription
## Subscription type does not support the attribute.
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.
return true if values.first === "*"
return true if values.include?("*")
## Subscription type supports some values of the attributes.
values.include?(value)
@ -192,4 +199,21 @@ class CustomWizard::Subscription
def self.includes?(feature, attribute, value)
new.includes?(feature, attribute, value)
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

Datei anzeigen

@ -23,7 +23,6 @@ class CustomWizard::Template
normalize_data
validate_data
prepare_data
return false if errors.any?
ActiveRecord::Base.transaction do

Datei anzeigen

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

Datei anzeigen

@ -30,6 +30,7 @@ class CustomWizard::TemplateValidator
validate_subscription(field, :field)
check_required(field, :field)
validate_liquid_template(field, :field)
validate_guests(field, :field)
end
end
end
@ -39,6 +40,7 @@ class CustomWizard::TemplateValidator
validate_subscription(action, :action)
check_required(action, :action)
validate_liquid_template(action, :action)
validate_guests(action, :action)
end
end
@ -80,6 +82,21 @@ class CustomWizard::TemplateValidator
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
return unless ActiveRecord::Type::Boolean.new.cast(@data[:after_signup])

Datei anzeigen

@ -4,8 +4,6 @@ require_dependency 'wizard/field'
require_dependency 'wizard/step_updater'
require_dependency 'wizard/builder'
UserHistory.actions[:custom_wizard_step] = 1000
class CustomWizard::Wizard
include ActiveModel::SerializerSupport
@ -31,13 +29,22 @@ class CustomWizard::Wizard
:actions,
:action_ids,
:user,
:guest_id,
:submissions,
:template
attr_reader :all_step_ids
def initialize(attrs = {}, user = nil)
@user = user
GUEST_ID_PREFIX ||= "guest"
GUEST_GROUP_ID = -1
def initialize(attrs = {}, user = nil, guest_id = nil)
if user
@user = user
elsif guest_id
@guest_id = guest_id
end
attrs = attrs.with_indifferent_access
@id = attrs['id']
@ -81,6 +88,10 @@ class CustomWizard::Wizard
@template = attrs
end
def actor_id
user ? user.id : guest_id
end
def cast_bool(val)
val.nil? ? false : ActiveRecord::Type::Boolean.new.cast(val)
end
@ -141,17 +152,16 @@ class CustomWizard::Wizard
end
def last_completed_step_id
if user && unfinished? && last_completed_step = ::UserHistory.where(
acting_user_id: user.id,
action: ::UserHistory.actions[:custom_wizard_step],
context: id,
subject: all_step_ids
).order("created_at").last
return nil unless actor_id && unfinished?
last_completed_step.subject
else
nil
end
last_completed_step = CustomWizard::UserHistory.where(
actor_id: actor_id,
action: CustomWizard::UserHistory.actions[:step],
context: id,
subject: all_step_ids
).order("created_at").last
last_completed_step&.subject
end
def find_step(step_id)
@ -161,15 +171,15 @@ class CustomWizard::Wizard
def create_updater(step_id, submission)
step = @steps.find { |s| s.id == step_id }
wizard = self
CustomWizard::StepUpdater.new(user, wizard, step, submission)
CustomWizard::StepUpdater.new(wizard, step, submission)
end
def unfinished?
return nil if !user
return nil unless actor_id
most_recent = ::UserHistory.where(
acting_user_id: user.id,
action: ::UserHistory.actions[:custom_wizard_step],
most_recent = CustomWizard::UserHistory.where(
actor_id: actor_id,
action: CustomWizard::UserHistory.actions[:step],
context: id,
).distinct.order('updated_at DESC').first
@ -183,11 +193,11 @@ class CustomWizard::Wizard
end
def completed?
return nil if !user
return nil unless actor_id
history = ::UserHistory.where(
acting_user_id: user.id,
action: ::UserHistory.actions[:custom_wizard_step],
history = CustomWizard::UserHistory.where(
actor_id: actor_id,
action: CustomWizard::UserHistory.actions[:step],
context: id
)
@ -200,8 +210,9 @@ class CustomWizard::Wizard
end
def permitted?
return false unless user
return true if user.admin? || permitted.blank?
return nil unless actor_id
return true if user && (user.admin? || permitted.blank?)
return false if !user && permitted.blank?
mapper = CustomWizard::Mapper.new(
inputs: permitted,
@ -215,27 +226,31 @@ class CustomWizard::Wizard
return true if mapper.blank?
mapper.all? do |m|
if m[:type] === 'assignment'
[*m[:result]].include?(Group::AUTO_GROUPS[:everyone]) ||
GroupUser.exists?(group_id: m[:result], user_id: user.id)
elsif m[:type] === 'validation'
m[:result]
if !user
m[:type] === 'assignment' && [*m[:result]].include?(GUEST_GROUP_ID)
else
true
if m[:type] === 'assignment'
[*m[:result]].include?(Group::AUTO_GROUPS[:everyone]) ||
GroupUser.exists?(group_id: m[:result], user_id: user.id)
elsif m[:type] === 'validation'
m[:result]
else
true
end
end
end
end
def can_access?
return false unless user
return true if user.admin
permitted? && (multiple_submissions || !completed?)
permitted? && (user&.admin? || (multiple_submissions || !completed?))
end
def reset
::UserHistory.create(
action: ::UserHistory.actions[:custom_wizard_step],
acting_user_id: user.id,
return nil unless actor_id
CustomWizard::UserHistory.create(
action: CustomWizard::UserHistory.actions[:step],
actor_id: actor_id,
context: id,
subject: "reset"
)
@ -263,8 +278,7 @@ class CustomWizard::Wizard
end
def submissions
return nil unless user.present?
@submissions ||= CustomWizard::Submission.list(self, user_id: user.id).submissions
@submissions ||= CustomWizard::Submission.list(self).submissions
end
def current_submission
@ -300,15 +314,17 @@ class CustomWizard::Wizard
end
def remove_user_redirect
return unless user.present?
if id == user.redirect_to_wizard
user.custom_fields.delete('redirect_to_wizard')
user.save_custom_fields(true)
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)
new(template.to_h, user)
new(template.to_h, user, guest_id)
else
false
end
@ -319,7 +335,7 @@ class CustomWizard::Wizard
CustomWizard::Template.list(**template_opts).reduce([]) do |result, template|
wizard = new(template, user)
result.push(wizard) if wizard.can_access? && (
result.push(wizard) if wizard.permitted? && (
!not_completed || !wizard.completed?
)
result
@ -380,4 +396,8 @@ class CustomWizard::Wizard
false
end
end
def self.generate_guest_id
"#{self::GUEST_ID_PREFIX}_#{SecureRandom.hex(12)}"
end
end

Datei anzeigen

@ -1,7 +1,7 @@
# frozen_string_literal: true
# name: discourse-custom-wizard
# about: Forms for Discourse. Better onboarding, structured posting, data enrichment, automated actions and much more.
# version: 2.1.5
# version: 2.2.0
# authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George, Kaitlin Maddever
# url: https://github.com/paviliondev/discourse-custom-wizard
# contact_emails: development@pavilion.tech
@ -41,6 +41,7 @@ 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/wizard_client.rb
../app/controllers/custom_wizard/wizard.rb
../app/controllers/custom_wizard/steps.rb
../app/controllers/custom_wizard/realtime_validations.rb
@ -65,6 +66,7 @@ after_initialize do
../lib/custom_wizard/subscription.rb
../lib/custom_wizard/template.rb
../lib/custom_wizard/wizard.rb
../lib/custom_wizard/user_history.rb
../lib/custom_wizard/api/api.rb
../lib/custom_wizard/api/authorization.rb
../lib/custom_wizard/api/endpoint.rb

Datei anzeigen

@ -18,6 +18,7 @@ describe CustomWizard::Action do
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_no_authorization) { get_wizard_fixture("api/no_authorization") }
let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
def update_template(template)
CustomWizard::Template.save(template, skip_jobs: true)
@ -78,8 +79,8 @@ describe CustomWizard::Action do
updater.update
expect(updater.success?).to eq(true)
expect(UserHistory.where(
acting_user_id: user.id,
expect(CustomWizard::UserHistory.where(
actor_id: user.id,
context: "super_mega_fun_wizard",
subject: "step_3"
).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(post.exists?).to eq(true)
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
context "business subscription actions" do

Datei anzeigen

@ -80,14 +80,11 @@ describe CustomWizard::Builder do
it 'returns no steps if user has completed it' do
@template[:steps].each do |step|
UserHistory.create!(
{
action: UserHistory.actions[:custom_wizard_step],
acting_user_id: user.id,
context: @template[:id]
}.merge(
subject: step[:id]
)
CustomWizard::UserHistory.create!(
action: CustomWizard::UserHistory.actions[:step],
actor_id: user.id,
context: @template[:id],
subject: step[:id]
)
end

Datei anzeigen

@ -4,6 +4,7 @@ describe CustomWizard::Submission do
fab!(:user) { Fabricate(:user) }
fab!(:user2) { Fabricate(:user) }
let(:template_json) { get_wizard_fixture("wizard") }
let(:guest_id) { CustomWizard::Wizard.generate_guest_id }
before do
CustomWizard::Template.save(template_json, skip_jobs: true)
@ -13,10 +14,20 @@ describe CustomWizard::Submission do
it "saves a user's submission" do
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")
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
before do
freeze_time Time.now
@ -37,14 +48,17 @@ describe CustomWizard::Submission do
end
it "list submissions by wizard" do
@wizard.user = nil
expect(described_class.list(@wizard).total).to eq(@count + 2)
end
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
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)
end
@ -59,7 +73,7 @@ describe CustomWizard::Submission do
described_class.new(@wizard, step_1_field_1: "I am the second submission").save
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
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.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)
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
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.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.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.first.fields["step_1_field_1"]).to eq("I am the third submission")

Datei anzeigen

@ -1,6 +1,8 @@
# frozen_string_literal: true
describe CustomWizard::Subscription do
let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
def undefine_client_classes
Object.send(:remove_const, :SubscriptionClient) if Object.constants.include?(:SubscriptionClient)
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)
end
it "ubscriber features are not included" do
it "subscriber features are not included" do
expect(described_class.includes?(:wizard, :permitted, {})).to eq(false)
end
end
@ -69,6 +71,16 @@ describe CustomWizard::Subscription do
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
before do
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID)

Datei anzeigen

@ -7,6 +7,8 @@ describe CustomWizard::TemplateValidator do
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(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
let(:upload_field) { get_wizard_fixture("field/upload") }
let(:valid_liquid_template) {
<<-LIQUID.strip
@ -146,6 +148,20 @@ describe CustomWizard::TemplateValidator do
).to eq(true)
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
template[:steps][0][:condition] = user_condition['condition']
expect(

Datei anzeigen

@ -21,10 +21,10 @@ describe CustomWizard::Wizard do
@wizard.update!
end
def progress_step(step_id, acting_user: user, wizard: @wizard)
UserHistory.create(
action: UserHistory.actions[:custom_wizard_step],
acting_user_id: acting_user.id,
def progress_step(step_id, actor_id: user.id, wizard: @wizard)
CustomWizard::UserHistory.create(
action: CustomWizard::UserHistory.actions[:step],
actor_id: actor_id,
context: wizard.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
append_steps
progress_step("step_1", acting_user: trusted_user)
progress_step("step_2", acting_user: trusted_user)
progress_step("step_3", acting_user: trusted_user)
progress_step("step_1", actor_id: trusted_user.id)
progress_step("step_2", actor_id: trusted_user.id)
progress_step("step_3", actor_id: trusted_user.id)
@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
append_steps
progress_step("step_1", acting_user: trusted_user)
progress_step("step_2", acting_user: trusted_user)
progress_step("step_3", acting_user: trusted_user)
progress_step("step_1", actor_id: trusted_user.id)
progress_step("step_2", actor_id: trusted_user.id)
progress_step("step_3", actor_id: trusted_user.id)
@permitted_template['multiple_submissions'] = false

12
spec/fixtures/actions/route_to.json gevendort Normale Datei
Datei anzeigen

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

@ -0,0 +1,6 @@
{
"id": "step_2_field_7",
"label": "Upload",
"type": "upload",
"file_types": ".jpg,.jpeg,.png"
}

Datei anzeigen

@ -74,12 +74,6 @@
"id": "step_2_field_5",
"label": "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 :)"

12
spec/fixtures/wizard/guests_permitted.json gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,12 @@
{
"permitted": [
{
"type": "assignment",
"output_type": "group",
"output_connector": "set",
"output": [
-1
]
}
]
}

Datei anzeigen

@ -7,192 +7,209 @@ describe CustomWizard::StepsController do
let(:wizard_field_condition_template) { get_wizard_fixture("condition/wizard_field_condition") }
let(:user_condition_template) { get_wizard_fixture("condition/user_condition") }
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
CustomWizard::Template.save(wizard_template, skip_jobs: true)
sign_in(user)
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"
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(200)
expect(response.parsed_body['wizard']['start']).to eq("step_2")
wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user)
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 = wizard_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)
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
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 = wizard_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 user has completed wizard" do
new_template = wizard_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
it "saves results of completion actions if user has completed wizard" do
new_template = wizard_template.dup
new_template['actions'].first['run_after'] = 'wizard_completion'
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Topic title",
step_1_field_2: "Topic post"
}
}
put '/w/super-mega-fun-wizard/steps/step_2.json'
put '/w/super-mega-fun-wizard/steps/step_3.json'
wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user)
topic_id = wizard.submissions.first.fields[new_template['actions'].first['id']]
topic = Topic.find(topic_id)
expect(topic.present?).to eq(true)
end
it "returns a final step without conditions" do
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_2.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_3.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
context "subscription" do
context "with user" do
before do
enable_subscription("standard")
sign_in(user)
end
it "raises an error when user cant see the step due to conditions" do
sign_in(user2)
new_wizard_template = wizard_template.dup
new_wizard_template['steps'][0]['condition'] = user_condition_template['condition']
CustomWizard::Template.save(new_wizard_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(403)
end
it "returns an updated wizard when condition doesnt pass" do
new_template = wizard_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
it 'performs a step update' do
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition wont pass"
step_1_field_1: "Text input"
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_3")
expect(response.parsed_body['wizard']['start']).to eq("step_2")
wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user)
expect(wizard.current_submission.fields['step_1_field_1']).to eq("Text input")
end
it "returns the correct final step when the conditional final step and last step are the same" do
new_template = wizard_template.dup
new_template['steps'][0]['condition'] = user_condition_template['condition']
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
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 = wizard_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 "raises an error when user cant see the step due to conditions" do
sign_in(user2)
new_wizard_template = wizard_template.dup
new_wizard_template['steps'][0]['condition'] = user_condition_template['condition']
CustomWizard::Template.save(new_wizard_template, skip_jobs: true)
it "works if the step has no fields" do
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(403)
expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_2")
end
it "returns an updated wizard when condition doesnt pass" do
it "returns an updated wizard when condition passes" do
new_template = wizard_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 wont pass"
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_3")
end
it "returns the correct final step when the conditional final step and last step are the same" do
new_template = wizard_template.dup
new_template['steps'][0]['condition'] = user_condition_template['condition']
new_template['steps'][2]['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 user has completed wizard" do
new_template = wizard_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
it "saves results of completion actions if user has completed wizard" do
new_template = wizard_template.dup
new_template['actions'].first['run_after'] = 'wizard_completion'
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Topic title",
step_1_field_2: "Topic post"
}
}
put '/w/super-mega-fun-wizard/steps/step_2.json'
put '/w/super-mega-fun-wizard/steps/step_3.json'
wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user)
topic_id = wizard.submissions.first.fields[new_template['actions'].first['id']]
topic = Topic.find(topic_id)
expect(topic.present?).to eq(true)
end
it "returns a final step without conditions" do
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_2.json'
@ -204,66 +221,152 @@ describe CustomWizard::StepsController do
expect(response.parsed_body['final']).to eq(true)
end
it "returns the correct final step when the conditional final step and last step are different" do
new_template = wizard_template.dup
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
context "subscription" do
before do
enable_subscription("standard")
end
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will not pass"
it "raises an error when user cant see the step due to conditions" do
sign_in(user2)
new_wizard_template = wizard_template.dup
new_wizard_template['steps'][0]['condition'] = user_condition_template['condition']
CustomWizard::Template.save(new_wizard_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(403)
end
it "returns an updated wizard when condition doesnt pass" do
new_template = wizard_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 wont pass"
}
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_3")
end
put '/w/super-mega-fun-wizard/steps/step_2.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
it "returns the correct final step when the conditional final step and last step are the same" do
new_template = wizard_template.dup
new_template['steps'][0]['condition'] = user_condition_template['condition']
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
end
it "returns the correct final step when the conditional final step is determined in the same action" do
new_template = wizard_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
it "raises an error when user cant see the step due to conditions" do
sign_in(user2)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will not pass"
new_wizard_template = wizard_template.dup
new_wizard_template['steps'][0]['condition'] = user_condition_template['condition']
CustomWizard::Template.save(new_wizard_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(403)
end
it "returns an updated wizard when condition doesnt pass" do
new_template = wizard_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 wont pass"
}
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_3")
end
it "excludes the non-included conditional fields from the submissions" do
new_template = wizard_template.dup
new_template['steps'][1]['fields'][0]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
it "returns the correct final step when the conditional final step and last step are the same" do
new_template = wizard_template.dup
new_template['steps'][0]['condition'] = user_condition_template['condition']
new_template['steps'][2]['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"
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['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_2.json', params: {
fields: {
step_2_field_1: "1995-04-23"
put '/w/super-mega-fun-wizard/steps/step_2.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_3.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
it "returns the correct final step when the conditional final step and last step are different" do
new_template = wizard_template.dup
new_template['steps'][2]['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 not pass"
}
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will not pass"
put '/w/super-mega-fun-wizard/steps/step_2.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
it "returns the correct final step when the conditional final step is determined in the same action" do
new_template = wizard_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
new_template['steps'][2]['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 not pass"
}
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user)
submission = wizard.current_submission
expect(submission.fields.keys).not_to include("step_2_field_1")
it "excludes the non-included conditional fields from the submissions" do
new_template = wizard_template.dup
new_template['steps'][1]['fields'][0]['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"
}
}
put '/w/super-mega-fun-wizard/steps/step_2.json', params: {
fields: {
step_2_field_1: "1995-04-23"
}
}
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will not pass"
}
}
wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user)
submission = wizard.current_submission
expect(submission.fields.keys).not_to include("step_2_field_1")
end
end
end
end

Datei anzeigen

@ -8,7 +8,6 @@ describe CustomWizard::WizardController do
before do
CustomWizard::Template.save(wizard_template, skip_jobs: true)
@template = CustomWizard::Template.find("super_mega_fun_wizard")
sign_in(user)
end
context 'plugin disabled' do
@ -32,65 +31,70 @@ describe CustomWizard::WizardController do
expect(response.parsed_body["error"]).to eq("We couldn't find a wizard at that address.")
end
context 'when user skips the wizard' do
it 'skips a wizard if user is allowed to skip' do
put '/w/super-mega-fun-wizard/skip.json'
expect(response.status).to eq(200)
context "with user" do
before do
sign_in(user)
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
context 'when user skips' do
it 'skips a wizard if user is allowed to skip' do
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'
expect(response.parsed_body['error']).to eq("Wizard can't be skipped")
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 'skip response contains a redirect_to if in users submissions' do
@wizard = CustomWizard::Wizard.create(@template["id"], user)
CustomWizard::Submission.new(@wizard, redirect_to: "/t/2").save
put '/w/super-mega-fun-wizard/skip.json'
expect(response.parsed_body['redirect_to']).to eq('/t/2')
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'
expect(response.parsed_body['error']).to eq("Wizard can't be skipped")
end
it 'deletes the users redirect_to_wizard if present' do
user.custom_fields['redirect_to_wizard'] = @template["id"]
user.save_custom_fields(true)
@wizard = CustomWizard::Wizard.create(@template["id"], user)
put '/w/super-mega-fun-wizard/skip.json'
expect(response.status).to eq(200)
expect(user.reload.redirect_to_wizard).to eq(nil)
end
it 'skip response contains a redirect_to if in users submissions' do
@wizard = CustomWizard::Wizard.create(@template["id"], user)
CustomWizard::Submission.new(@wizard, redirect_to: "/t/2").save
put '/w/super-mega-fun-wizard/skip.json'
expect(response.parsed_body['redirect_to']).to eq('/t/2')
end
it "deletes the submission if user has filled up some data" do
@wizard = CustomWizard::Wizard.create(@template["id"], user)
CustomWizard::Submission.new(@wizard, step_1_field_1: "Hello World").save
current_submission = @wizard.current_submission
put '/w/super-mega-fun-wizard/skip.json'
submissions = CustomWizard::Submission.list(@wizard).submissions
it 'deletes the users redirect_to_wizard if present' do
user.custom_fields['redirect_to_wizard'] = @template["id"]
user.save_custom_fields(true)
@wizard = CustomWizard::Wizard.create(@template["id"], user)
put '/w/super-mega-fun-wizard/skip.json'
expect(response.status).to eq(200)
expect(user.reload.redirect_to_wizard).to eq(nil)
end
expect(submissions.any? { |submission| submission.id == current_submission.id }).to eq(false)
end
it "deletes the submission if user has filled up some data" do
@wizard = CustomWizard::Wizard.create(@template["id"], user)
CustomWizard::Submission.new(@wizard, step_1_field_1: "Hello World").save
current_submission = @wizard.current_submission
put '/w/super-mega-fun-wizard/skip.json'
submissions = CustomWizard::Submission.list(@wizard).submissions
it "starts from the first step if user visits after skipping the wizard" do
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Text input"
expect(submissions.any? { |submission| submission.id == current_submission.id }).to eq(false)
end
it "starts from the first step if user visits after skipping the wizard" do
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Text input"
}
}
}
put '/w/super-mega-fun-wizard/skip.json'
get '/w/super-mega-fun-wizard.json'
put '/w/super-mega-fun-wizard/skip.json'
get '/w/super-mega-fun-wizard.json'
expect(response.parsed_body["start"]).to eq('step_1')
expect(response.parsed_body["start"]).to eq('step_1')
end
end
end
end

Datei anzeigen

@ -29,6 +29,5 @@ describe CustomWizard::FieldSerializer do
scope: Guardian.new(user)
).as_json
expect(json_array[0][:format]).to eq("YYYY-MM-DD")
expect(json_array[5][:file_types]).to eq(".jpg,.jpeg,.png")
end
end

Datei anzeigen

@ -9,6 +9,7 @@ import {
import {
wizard,
wizardCompleted,
wizardGuest,
wizardNoUser,
wizardNotPermitted,
} from "../helpers/wizard";
@ -106,3 +107,59 @@ acceptance("Wizard | Wizard", function (needs) {
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);
});
});

Datei anzeigen

@ -6,7 +6,7 @@ export default {
submission_last_updated_at: "2022-03-15T21:11:01+01:00",
theme_id: 2,
required: false,
permitted: true,
permitted: false,
uncategorized_category_id: 1,
categories: [],
subscribed: false,

Datei anzeigen

@ -6,8 +6,11 @@ import updateJson from "../fixtures/update";
import { cloneJSON } from "discourse-common/lib/object";
const wizardNoUser = cloneJSON(wizardJson);
const wizardGuest = cloneJSON(wizardJson);
wizardGuest.permitted = true;
const wizard = cloneJSON(wizardJson);
wizard.user = cloneJSON(userJson);
wizard.permitted = true;
const wizardNotPermitted = cloneJSON(wizard);
wizardNotPermitted.permitted = false;
@ -40,6 +43,7 @@ export {
wizardNoUser,
wizardNotPermitted,
wizardCompleted,
wizardGuest,
stepNotPermitted,
allFieldsWizard,
wizard,