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
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -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?
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
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
|
||||
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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"];
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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([
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
feature="action"
|
||||
attribute="type"
|
||||
onChange=(action "changeType")
|
||||
wizard=wizard
|
||||
options=(hash
|
||||
none="admin.wizard.select_type"
|
||||
)
|
||||
|
|
|
@ -17,7 +17,7 @@ body.custom-wizard .wizard-column {
|
|||
}
|
||||
}
|
||||
|
||||
img.emoji {
|
||||
.emoji {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
|
@ -29,7 +29,7 @@ body.custom-wizard .wizard-column {
|
|||
|
||||
p {
|
||||
img {
|
||||
@extend img.emoji;
|
||||
@extend .emoji;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -217,6 +217,8 @@ en:
|
|||
list: "list"
|
||||
custom_field: "custom field"
|
||||
value: "value"
|
||||
users: "users"
|
||||
guests: "users and guests"
|
||||
|
||||
placeholder:
|
||||
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_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."
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -29,6 +29,11 @@ class CustomWizard::Field
|
|||
attr_accessor :index,
|
||||
:step
|
||||
|
||||
REQUIRES_USER = %w[
|
||||
composer
|
||||
upload
|
||||
]
|
||||
|
||||
def initialize(attrs)
|
||||
@raw = attrs || {}
|
||||
@id = attrs[:id]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -23,7 +23,6 @@ class CustomWizard::Template
|
|||
normalize_data
|
||||
validate_data
|
||||
prepare_data
|
||||
|
||||
return false if errors.any?
|
||||
|
||||
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)
|
||||
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])
|
||||
|
||||
|
|
|
@ -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)
|
||||
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],
|
||||
return nil unless actor_id && unfinished?
|
||||
|
||||
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
|
||||
else
|
||||
nil
|
||||
end
|
||||
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,6 +226,9 @@ class CustomWizard::Wizard
|
|||
return true if mapper.blank?
|
||||
|
||||
mapper.all? do |m|
|
||||
if !user
|
||||
m[:type] === 'assignment' && [*m[:result]].include?(GUEST_GROUP_ID)
|
||||
else
|
||||
if m[:type] === 'assignment'
|
||||
[*m[:result]].include?(Group::AUTO_GROUPS[:everyone]) ||
|
||||
GroupUser.exists?(group_id: m[:result], user_id: user.id)
|
||||
|
@ -225,17 +239,18 @@ class CustomWizard::Wizard
|
|||
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
|
||||
|
|
|
@ -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.1
|
||||
# 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -80,15 +80,12 @@ 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(
|
||||
CustomWizard::UserHistory.create!(
|
||||
action: CustomWizard::UserHistory.actions[:step],
|
||||
actor_id: user.id,
|
||||
context: @template[:id],
|
||||
subject: step[:id]
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
expect(
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
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",
|
||||
"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
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(: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)
|
||||
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)
|
||||
end
|
||||
|
||||
|
@ -266,4 +368,5 @@ describe CustomWizard::StepsController do
|
|||
expect(submission.fields.keys).not_to include("step_2_field_1")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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,8 +31,12 @@ 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
|
||||
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
|
||||
put '/w/super-mega-fun-wizard/skip.json'
|
||||
expect(response.status).to eq(200)
|
||||
|
@ -93,4 +96,5 @@ describe CustomWizard::WizardController do
|
|||
expect(response.parsed_body["start"]).to eq('step_1')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
Laden …
In neuem Issue referenzieren