1
0
Fork 0

Merge branch 'main' into tag_group_restriction_fix

Dieser Commit ist enthalten in:
Richard Odekerken 2023-02-10 18:13:03 +01:00 committet von GitHub
Commit 75d429388e
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
47 geänderte Dateien mit 901 neuen und 419 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 This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

Datei anzeigen

@ -1,6 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
class CustomWizard::StepsController < ::ApplicationController class CustomWizard::StepsController < ::CustomWizard::WizardClientController
before_action :ensure_logged_in
before_action :ensure_can_update before_action :ensure_can_update
def update def update
@ -22,7 +21,7 @@ class CustomWizard::StepsController < ::ApplicationController
if updater.success? if updater.success?
wizard_id = update_params[:wizard_id] wizard_id = update_params[:wizard_id]
builder = CustomWizard::Builder.new(wizard_id, current_user) builder = CustomWizard::Builder.new(wizard_id, current_user, guest_id)
@wizard = builder.build(force: true) @wizard = builder.build(force: true)
current_step = @wizard.find_step(update[:step_id]) current_step = @wizard.find_step(update[:step_id])
@ -85,7 +84,6 @@ class CustomWizard::StepsController < ::ApplicationController
private private
def ensure_can_update def ensure_can_update
@builder = CustomWizard::Builder.new(update_params[:wizard_id], current_user)
raise Discourse::InvalidParameters.new(:wizard_id) if @builder.template.nil? raise Discourse::InvalidParameters.new(:wizard_id) if @builder.template.nil?
raise Discourse::InvalidAccess.new if !@builder.wizard || !@builder.wizard.can_access? raise Discourse::InvalidAccess.new if !@builder.wizard || !@builder.wizard.can_access?

Datei anzeigen

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

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 class CustomWizard::SubmissionSerializer < ApplicationSerializer
attributes :id, attributes :id,
:fields, :fields,
:submitted_at :submitted_at,
:user
has_one :user, serializer: ::BasicUserSerializer, embed: :objects
def include_user? def include_user?
object.user.present? object.wizard.user.present?
end
def user
::BasicUserSerializer.new(object.wizard.user).as_json
end end
def fields def fields

Datei anzeigen

@ -15,6 +15,7 @@ import {
import Component from "@ember/component"; import Component from "@ember/component";
import { bind, later } from "@ember/runloop"; import { bind, later } from "@ember/runloop";
import I18n from "I18n"; import I18n from "I18n";
import Subscription from "../mixins/subscription";
const customFieldActionMap = { const customFieldActionMap = {
topic: ["create_topic", "send_message"], topic: ["create_topic", "send_message"],
@ -26,7 +27,7 @@ const customFieldActionMap = {
const values = ["present", "true", "false"]; const values = ["present", "true", "false"];
export default Component.extend({ export default Component.extend(Subscription, {
classNameBindings: [":mapper-selector", "activeType"], classNameBindings: [":mapper-selector", "activeType"],
showText: computed("activeType", function () { showText: computed("activeType", function () {
@ -116,6 +117,9 @@ export default Component.extend({
groupEnabled: computed("options.groupSelection", "inputType", function () { groupEnabled: computed("options.groupSelection", "inputType", function () {
return this.optionEnabled("groupSelection"); return this.optionEnabled("groupSelection");
}), }),
guestGroup: computed("options.guestGroup", "inputType", function () {
return this.optionEnabled("guestGroup");
}),
userEnabled: computed("options.userSelection", "inputType", function () { userEnabled: computed("options.userSelection", "inputType", function () {
return this.optionEnabled("userSelection"); return this.optionEnabled("userSelection");
}), }),
@ -126,7 +130,29 @@ export default Component.extend({
return this.connector === "is"; return this.connector === "is";
}), }),
groups: alias("site.groups"), @discourseComputed("site.groups", "guestGroup", "subscriptionType")
groups(groups, guestGroup, subscriptionType) {
let result = groups;
if (!guestGroup) {
return result;
}
if (["standard", "business"].includes(subscriptionType)) {
let guestIndex;
result.forEach((r, index) => {
if (r.id === 0) {
r.name = I18n.t("admin.wizard.selector.label.users");
guestIndex = index;
}
});
result.splice(guestIndex, 0, {
id: -1,
name: I18n.t("admin.wizard.selector.label.guests"),
});
}
return result;
},
categories: alias("site.categories"), categories: alias("site.categories"),
showComboBox: or( showComboBox: or(
"showWizardField", "showWizardField",

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -10,6 +10,7 @@ import { later, scheduleOnce } from "@ember/runloop";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import copyText from "discourse/lib/copy-text"; import copyText from "discourse/lib/copy-text";
import I18n from "I18n"; import I18n from "I18n";
import { filterValues } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
export default Controller.extend({ export default Controller.extend({
hasName: notEmpty("wizard.name"), hasName: notEmpty("wizard.name"),
@ -59,6 +60,19 @@ export default Controller.extend({
} }
return wizardFieldList(steps); return wizardFieldList(steps);
}, },
@discourseComputed("fieldTypes", "wizard.allowGuests")
filteredFieldTypes(fieldTypes) {
const fieldTypeIds = fieldTypes.map((f) => f.id);
const allowedTypeIds = filterValues(
this.wizard,
"field",
"type",
fieldTypeIds
);
return fieldTypes.filter((f) => allowedTypeIds.includes(f.id));
},
getErrorMessage(result) { getErrorMessage(result) {
if (result.backend_validation_error) { if (result.backend_validation_error) {
return result.backend_validation_error; return result.backend_validation_error;

Datei anzeigen

@ -210,6 +210,32 @@ const action = {
objectArrays: {}, objectArrays: {},
}; };
const filters = {
allow_guests: {
field: {
type: [
"text",
"textarea",
"text_only",
"date",
"time",
"date_time",
"number",
"checkbox",
"url",
"dropdown",
"tag",
"category",
"group",
"user_selector",
],
},
action: {
type: ["route_to", "send_message"],
},
},
};
const custom_field = { const custom_field = {
klass: ["topic", "post", "group", "category"], klass: ["topic", "post", "group", "category"],
type: ["string", "boolean", "integer", "json"], type: ["string", "boolean", "integer", "json"],
@ -224,16 +250,29 @@ const wizardSchema = {
field, field,
custom_field, custom_field,
action, action,
filters,
}; };
export function buildFieldTypes(types) {
wizardSchema.field.types = types;
}
export function buildFieldValidations(validations) { export function buildFieldValidations(validations) {
wizardSchema.field.validations = validations; wizardSchema.field.validations = validations;
} }
export function filterValues(currentWizard, feature, attribute, values = null) {
values = values || wizardSchema[feature][attribute];
if (currentWizard.allowGuests) {
const filteredFeature = wizardSchema.filters.allow_guests[feature];
if (filteredFeature) {
const filtered = filteredFeature[attribute];
if (filtered) {
values = values.filter((v) => filtered.includes(v));
}
}
}
return values;
}
const siteSettings = getOwner(this).lookup("service:site-settings"); const siteSettings = getOwner(this).lookup("service:site-settings");
if (siteSettings.wizard_apis_enabled) { if (siteSettings.wizard_apis_enabled) {
wizardSchema.action.types.send_to_api = { wizardSchema.action.types.send_to_api = {

Datei anzeigen

@ -5,8 +5,20 @@ import wizardSchema from "../lib/wizard-schema";
import { Promise } from "rsvp"; import { Promise } from "rsvp";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import discourseComputed from "discourse-common/utils/decorators";
const GUEST_GROUP_ID = -1;
const CustomWizardAdmin = EmberObject.extend({ const CustomWizardAdmin = EmberObject.extend({
@discourseComputed("permitted.@each.output")
allowGuests(permitted) {
return (
permitted &&
permitted.filter((p) => p.output && p.output.includes(GUEST_GROUP_ID))
.length
);
},
save(opts) { save(opts) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let wizard = this.buildJson(this, "wizard"); let wizard = this.buildJson(this, "wizard");

Datei anzeigen

@ -1,5 +1,5 @@
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
import { buildFieldTypes, buildFieldValidations } from "../lib/wizard-schema"; import { buildFieldValidations } from "../lib/wizard-schema";
import EmberObject, { set } from "@ember/object"; import EmberObject, { set } from "@ember/object";
import { A } from "@ember/array"; import { A } from "@ember/array";
import { all } from "rsvp"; import { all } from "rsvp";
@ -11,7 +11,6 @@ export default DiscourseRoute.extend({
}, },
afterModel(model) { afterModel(model) {
buildFieldTypes(model.field_types);
buildFieldValidations(model.realtime_validations); buildFieldValidations(model.realtime_validations);
return all([ return all([

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -17,7 +17,7 @@ body.custom-wizard .wizard-column {
} }
} }
img.emoji { .emoji {
width: 20px; width: 20px;
height: 20px; height: 20px;
vertical-align: middle; vertical-align: middle;
@ -29,7 +29,7 @@ body.custom-wizard .wizard-column {
p { p {
img { img {
@extend img.emoji; @extend .emoji;
} }
} }

Datei anzeigen

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

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_signup_after_time: "You can't use 'after time' and 'after signup' on the same wizard."
after_time: "After time setting is invalid." after_time: "After time setting is invalid."
liquid_syntax_error: "Liquid syntax error in %{attribute}: %{message}" liquid_syntax_error: "Liquid syntax error in %{attribute}: %{message}"
subscription: "%{type} %{property} is subscription only" subscription: "%{type} %{property} usage is not supported on your subscription"
not_permitted_for_guests: "%{object_id} is not permitted when guests can access the wizard"
site_settings: site_settings:
custom_wizard_enabled: "Enable custom wizards." custom_wizard_enabled: "Enable custom wizards."

Datei anzeigen

@ -6,6 +6,14 @@ class CustomWizard::Action
:guardian, :guardian,
:result :result
REQUIRES_USER = %w[
create_topic
update_profile
open_composer
watch_categories
add_to_group
]
def initialize(opts) def initialize(opts)
@wizard = opts[:wizard] @wizard = opts[:wizard]
@action = opts[:action] @action = opts[:action]
@ -17,6 +25,12 @@ class CustomWizard::Action
end end
def perform def perform
if REQUIRES_USER.include?(action['id']) && !@user
log_error("action requires user", "id: #{action['id']};")
@result.success = false
return @result
end
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
self.send(action['type'].to_sym) self.send(action['type'].to_sym)
end end
@ -76,7 +90,6 @@ class CustomWizard::Action
end end
def send_message def send_message
if action['required'].present? if action['required'].present?
required = CustomWizard::Mapper.new( required = CustomWizard::Mapper.new(
inputs: action['required'], inputs: action['required'],
@ -123,13 +136,14 @@ class CustomWizard::Action
params[:archetype] = Archetype.private_message params[:archetype] = Archetype.private_message
creator = PostCreator.new(user, params) poster = user || Discourse.system_user
creator = PostCreator.new(poster, params)
post = creator.create post = creator.create
if creator.errors.present? if creator.errors.present?
messages = creator.errors.full_messages.join(" ") messages = creator.errors.full_messages.join(" ")
log_error("failed to create message", messages) log_error("failed to create message", messages)
elsif action['skip_redirect'].blank? elsif user && action['skip_redirect'].blank?
@submission.redirect_on_complete = post.topic.url @submission.redirect_on_complete = post.topic.url
end end
@ -809,10 +823,12 @@ class CustomWizard::Action
end end
def save_log def save_log
username = user ? user.username : @wizard.actor_id
CustomWizard::Log.create( CustomWizard::Log.create(
@wizard.id, @wizard.id,
action['type'], action['type'],
user.username, username,
@log.join('; ') @log.join('; ')
) )
end end

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -17,7 +17,7 @@ class CustomWizard::Subscription
none: [], none: [],
standard: ['*'], standard: ['*'],
business: ['*'], business: ['*'],
community: ['*'] community: ['*', "!#{CustomWizard::Wizard::GUEST_GROUP_ID}"]
}, },
restart_on_revisit: { restart_on_revisit: {
none: [], none: [],
@ -114,8 +114,15 @@ class CustomWizard::Subscription
## Subscription type does not support the attribute. ## Subscription type does not support the attribute.
return false if values.blank? return false if values.blank?
## Value is an exception for the subscription type
if (exceptions = get_exceptions(values)).any?
value = mapped_output(value) if CustomWizard::Mapper.mapped_value?(value)
value = [*value].map(&:to_s)
return false if (exceptions & value).length > 0
end
## Subscription type supports all values of the attribute. ## Subscription type supports all values of the attribute.
return true if values.first === "*" return true if values.include?("*")
## Subscription type supports some values of the attributes. ## Subscription type supports some values of the attributes.
values.include?(value) values.include?(value)
@ -192,4 +199,21 @@ class CustomWizard::Subscription
def self.includes?(feature, attribute, value) def self.includes?(feature, attribute, value)
new.includes?(feature, attribute, value) new.includes?(feature, attribute, value)
end end
protected
def get_exceptions(values)
values.reduce([]) do |result, value|
result << value.split("!").last if value.start_with?("!")
result
end
end
def mapped_output(value)
value.reduce([]) do |result, v|
## We can only validate mapped assignment values at the moment
result << v["output"] if v.is_a?(Hash) && v["type"] === "assignment"
result
end.flatten
end
end end

Datei anzeigen

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

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) validate_subscription(field, :field)
check_required(field, :field) check_required(field, :field)
validate_liquid_template(field, :field) validate_liquid_template(field, :field)
validate_guests(field, :field)
end end
end end
end end
@ -39,6 +40,7 @@ class CustomWizard::TemplateValidator
validate_subscription(action, :action) validate_subscription(action, :action)
check_required(action, :action) check_required(action, :action)
validate_liquid_template(action, :action) validate_liquid_template(action, :action)
validate_guests(action, :action)
end end
end end
@ -80,6 +82,21 @@ class CustomWizard::TemplateValidator
end end
end end
def validate_guests(object, type)
guests_permitted = @data[:permitted] && @data[:permitted].any? do |m|
m["output"].include?(CustomWizard::Wizard::GUEST_GROUP_ID)
end
return unless guests_permitted
if type === :action && CustomWizard::Action::REQUIRES_USER.include?(object[:type])
errors.add :base, I18n.t("wizard.validation.not_permitted_for_guests", object_id: object[:id])
end
if type === :field && CustomWizard::Field::REQUIRES_USER.include?(object[:type])
errors.add :base, I18n.t("wizard.validation.not_permitted_for_guests", object_id: object[:id])
end
end
def validate_after_signup def validate_after_signup
return unless ActiveRecord::Type::Boolean.new.cast(@data[:after_signup]) return unless ActiveRecord::Type::Boolean.new.cast(@data[:after_signup])

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -18,6 +18,7 @@ describe CustomWizard::Action do
let(:api_test_endpoint) { get_wizard_fixture("endpoints/test_endpoint") } let(:api_test_endpoint) { get_wizard_fixture("endpoints/test_endpoint") }
let(:api_test_endpoint_body) { get_wizard_fixture("endpoints/test_endpoint_body") } let(:api_test_endpoint_body) { get_wizard_fixture("endpoints/test_endpoint_body") }
let(:api_test_no_authorization) { get_wizard_fixture("api/no_authorization") } let(:api_test_no_authorization) { get_wizard_fixture("api/no_authorization") }
let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
def update_template(template) def update_template(template)
CustomWizard::Template.save(template, skip_jobs: true) CustomWizard::Template.save(template, skip_jobs: true)
@ -78,8 +79,8 @@ describe CustomWizard::Action do
updater.update updater.update
expect(updater.success?).to eq(true) expect(updater.success?).to eq(true)
expect(UserHistory.where( expect(CustomWizard::UserHistory.where(
acting_user_id: user.id, actor_id: user.id,
context: "super_mega_fun_wizard", context: "super_mega_fun_wizard",
subject: "step_3" subject: "step_3"
).exists?).to eq(true) ).exists?).to eq(true)
@ -301,6 +302,28 @@ describe CustomWizard::Action do
expect(topic.first.allowed_groups.map(&:name)).to include('cool_group', 'cool_group_1') expect(topic.first.allowed_groups.map(&:name)).to include('cool_group', 'cool_group_1')
expect(post.exists?).to eq(true) expect(post.exists?).to eq(true)
end end
it "send_message works with guests are permitted" do
wizard_template["permitted"] = guests_permitted["permitted"]
wizard_template.delete("actions")
wizard_template['actions'] = [send_message]
update_template(wizard_template)
User.create(username: 'angus1', email: "angus1@email.com")
wizard = CustomWizard::Builder.new(wizard_template["id"], nil, CustomWizard::Wizard.generate_guest_id).build
wizard.create_updater(wizard.steps[0].id, {}).update
updater = wizard.create_updater(wizard.steps[1].id, {})
updater.update
topic = Topic.where(archetype: Archetype.private_message, title: "Message title")
post = Post.where(topic_id: topic.pluck(:id))
expect(topic.exists?).to eq(true)
expect(topic.first.topic_allowed_users.first.user.username).to eq('angus1')
expect(topic.first.topic_allowed_users.second.user.username).to eq(Discourse.system_user.username)
expect(post.exists?).to eq(true)
end
end end
context "business subscription actions" do context "business subscription actions" do

Datei anzeigen

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

Datei anzeigen

@ -4,6 +4,7 @@ describe CustomWizard::Submission do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
fab!(:user2) { Fabricate(:user) } fab!(:user2) { Fabricate(:user) }
let(:template_json) { get_wizard_fixture("wizard") } let(:template_json) { get_wizard_fixture("wizard") }
let(:guest_id) { CustomWizard::Wizard.generate_guest_id }
before do before do
CustomWizard::Template.save(template_json, skip_jobs: true) CustomWizard::Template.save(template_json, skip_jobs: true)
@ -13,10 +14,20 @@ describe CustomWizard::Submission do
it "saves a user's submission" do it "saves a user's submission" do
expect( expect(
described_class.get(@wizard, user.id).fields["step_1_field_1"] described_class.get(@wizard).fields["step_1_field_1"]
).to eq("I am user submission") ).to eq("I am user submission")
end end
it "saves a guest's submission" do
CustomWizard::Template.save(template_json, skip_jobs: true)
@wizard = CustomWizard::Wizard.create(template_json["id"], nil, guest_id)
described_class.new(@wizard, step_1_field_1: "I am guest submission").save
expect(
described_class.get(@wizard).fields["step_1_field_1"]
).to eq("I am guest submission")
end
describe "#list" do describe "#list" do
before do before do
freeze_time Time.now freeze_time Time.now
@ -37,14 +48,17 @@ describe CustomWizard::Submission do
end end
it "list submissions by wizard" do it "list submissions by wizard" do
@wizard.user = nil
expect(described_class.list(@wizard).total).to eq(@count + 2) expect(described_class.list(@wizard).total).to eq(@count + 2)
end end
it "list submissions by wizard and user" do it "list submissions by wizard and user" do
expect(described_class.list(@wizard, user_id: user.id).total).to eq(@count + 1) @wizard.user = user
expect(described_class.list(@wizard).total).to eq(@count + 1)
end end
it "paginates submission lists" do it "paginates submission lists" do
@wizard.user = nil
expect(described_class.list(@wizard, page: 1).submissions.size).to eq((@count + 2) - CustomWizard::Submission::PAGE_LIMIT) expect(described_class.list(@wizard, page: 1).submissions.size).to eq((@count + 2) - CustomWizard::Submission::PAGE_LIMIT)
end end
@ -59,7 +73,7 @@ describe CustomWizard::Submission do
described_class.new(@wizard, step_1_field_1: "I am the second submission").save described_class.new(@wizard, step_1_field_1: "I am the second submission").save
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user) builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
builder.build builder.build
submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions submissions = described_class.list(@wizard).submissions
expect(submissions.length).to eq(1) expect(submissions.length).to eq(1)
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the second submission") expect(submissions.first.fields["step_1_field_1"]).to eq("I am the second submission")
@ -75,7 +89,7 @@ describe CustomWizard::Submission do
PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, sub_data) PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, sub_data)
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user) builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
builder.build builder.build
submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions submissions = described_class.list(@wizard).submissions
expect(submissions.length).to eq(1) expect(submissions.length).to eq(1)
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the second submission") expect(submissions.first.fields["step_1_field_1"]).to eq("I am the second submission")
@ -92,7 +106,7 @@ describe CustomWizard::Submission do
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user) builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
builder.build builder.build
submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions submissions = described_class.list(@wizard).submissions
expect(submissions.length).to eq(1) expect(submissions.length).to eq(1)
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the third submission") expect(submissions.first.fields["step_1_field_1"]).to eq("I am the third submission")

Datei anzeigen

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
describe CustomWizard::Subscription do describe CustomWizard::Subscription do
let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
def undefine_client_classes def undefine_client_classes
Object.send(:remove_const, :SubscriptionClient) if Object.constants.include?(:SubscriptionClient) Object.send(:remove_const, :SubscriptionClient) if Object.constants.include?(:SubscriptionClient)
Object.send(:remove_const, :SubscriptionClientSubscription) if Object.constants.include?(:SubscriptionClientSubscription) Object.send(:remove_const, :SubscriptionClientSubscription) if Object.constants.include?(:SubscriptionClientSubscription)
@ -40,7 +42,7 @@ describe CustomWizard::Subscription do
expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true) expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true)
end end
it "ubscriber features are not included" do it "subscriber features are not included" do
expect(described_class.includes?(:wizard, :permitted, {})).to eq(false) expect(described_class.includes?(:wizard, :permitted, {})).to eq(false)
end end
end end
@ -69,6 +71,16 @@ describe CustomWizard::Subscription do
end end
end end
context "with a subscription" do
it "handles mapped values" do
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID)
expect(described_class.includes?(:wizard, :permitted, guests_permitted["permitted"])).to eq(true)
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::COMMUNITY_PRODUCT_ID)
expect(described_class.includes?(:wizard, :permitted, guests_permitted["permitted"])).to eq(false)
end
end
context "with standard subscription" do context "with standard subscription" do
before do before do
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID) SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID)

Datei anzeigen

@ -7,6 +7,8 @@ describe CustomWizard::TemplateValidator do
let(:user_condition) { get_wizard_fixture("condition/user_condition") } let(:user_condition) { get_wizard_fixture("condition/user_condition") }
let(:permitted_json) { get_wizard_fixture("wizard/permitted") } let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
let(:composer_preview) { get_wizard_fixture("field/composer_preview") } let(:composer_preview) { get_wizard_fixture("field/composer_preview") }
let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
let(:upload_field) { get_wizard_fixture("field/upload") }
let(:valid_liquid_template) { let(:valid_liquid_template) {
<<-LIQUID.strip <<-LIQUID.strip
@ -146,6 +148,20 @@ describe CustomWizard::TemplateValidator do
).to eq(true) ).to eq(true)
end end
it "validates user-only features" do
template[:permitted] = guests_permitted['permitted']
template[:steps][0][:fields] << upload_field
validator = CustomWizard::TemplateValidator.new(template)
expect(validator.perform).to eq(false)
errors = validator.errors.to_a
expect(errors).to include(
I18n.t("wizard.validation.not_permitted_for_guests", object_id: "action_1")
)
expect(errors).to include(
I18n.t("wizard.validation.not_permitted_for_guests", object_id: "step_2_field_7")
)
end
it "validates step attributes" do it "validates step attributes" do
template[:steps][0][:condition] = user_condition['condition'] template[:steps][0][:condition] = user_condition['condition']
expect( expect(

Datei anzeigen

@ -21,10 +21,10 @@ describe CustomWizard::Wizard do
@wizard.update! @wizard.update!
end end
def progress_step(step_id, acting_user: user, wizard: @wizard) def progress_step(step_id, actor_id: user.id, wizard: @wizard)
UserHistory.create( CustomWizard::UserHistory.create(
action: UserHistory.actions[:custom_wizard_step], action: CustomWizard::UserHistory.actions[:step],
acting_user_id: acting_user.id, actor_id: actor_id,
context: wizard.id, context: wizard.id,
subject: step_id subject: step_id
) )
@ -158,9 +158,9 @@ describe CustomWizard::Wizard do
it "lets a permitted user access a complete wizard with multiple submissions" do it "lets a permitted user access a complete wizard with multiple submissions" do
append_steps append_steps
progress_step("step_1", acting_user: trusted_user) progress_step("step_1", actor_id: trusted_user.id)
progress_step("step_2", acting_user: trusted_user) progress_step("step_2", actor_id: trusted_user.id)
progress_step("step_3", acting_user: trusted_user) progress_step("step_3", actor_id: trusted_user.id)
@permitted_template["multiple_submissions"] = true @permitted_template["multiple_submissions"] = true
@ -172,9 +172,9 @@ describe CustomWizard::Wizard do
it "does not let an unpermitted user access a complete wizard without multiple submissions" do it "does not let an unpermitted user access a complete wizard without multiple submissions" do
append_steps append_steps
progress_step("step_1", acting_user: trusted_user) progress_step("step_1", actor_id: trusted_user.id)
progress_step("step_2", acting_user: trusted_user) progress_step("step_2", actor_id: trusted_user.id)
progress_step("step_3", acting_user: trusted_user) progress_step("step_3", actor_id: trusted_user.id)
@permitted_template['multiple_submissions'] = false @permitted_template['multiple_submissions'] = false

12
spec/fixtures/actions/route_to.json gevendort Normale Datei
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", "id": "step_2_field_5",
"label": "Checkbox", "label": "Checkbox",
"type": "checkbox" "type": "checkbox"
},
{
"id": "step_2_field_7",
"label": "Upload",
"type": "upload",
"file_types": ".jpg,.jpeg,.png"
} }
], ],
"description": "Because I couldn't think of another name for this step :)" "description": "Because I couldn't think of another name for this step :)"

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

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

Datei anzeigen

@ -7,9 +7,111 @@ describe CustomWizard::StepsController do
let(:wizard_field_condition_template) { get_wizard_fixture("condition/wizard_field_condition") } let(:wizard_field_condition_template) { get_wizard_fixture("condition/wizard_field_condition") }
let(:user_condition_template) { get_wizard_fixture("condition/user_condition") } let(:user_condition_template) { get_wizard_fixture("condition/user_condition") }
let(:permitted_json) { get_wizard_fixture("wizard/permitted") } let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
let(:route_to_template) { get_wizard_fixture("actions/route_to") }
let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
before do before do
CustomWizard::Template.save(wizard_template, skip_jobs: true) CustomWizard::Template.save(wizard_template, skip_jobs: true)
end
def guest_template
temp = wizard_template.dup
temp["permitted"] = guests_permitted["permitted"]
temp.delete("actions")
temp["actions"] = [route_to_template]
temp
end
context "with guest" do
it "does not perform a step update" do
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Text input"
}
}
expect(response.status).to eq(403)
end
context "with guests permitted" do
before do
enable_subscription("standard")
result = CustomWizard::Template.save(guest_template, skip_jobs: true)
end
it "performs a step update" do
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Text input"
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_2")
wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, nil, cookies[:custom_wizard_guest_id])
expect(wizard.current_submission.fields['step_1_field_1']).to eq("Text input")
end
context "raises an error" do
it "when the wizard doesnt exist" do
put '/w/not-super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(400)
end
it "when the user cant access the wizard" do
enable_subscription("standard")
new_template = guest_template.dup
new_template["permitted"] = permitted_json["permitted"]
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(403)
end
it "when the step doesnt exist" do
put '/w/super-mega-fun-wizard/steps/step_10.json'
expect(response.status).to eq(400)
end
end
it "works if the step has no fields" do
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_2")
end
it "returns an updated wizard when condition passes" do
new_template = guest_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will pass"
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_2")
end
it "runs completion actions if guest has completed wizard" do
new_template = guest_template.dup
## route_to action
new_template['actions'].last['run_after'] = 'wizard_completion'
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json'
put '/w/super-mega-fun-wizard/steps/step_2.json'
put '/w/super-mega-fun-wizard/steps/step_3.json'
expect(response.status).to eq(200)
expect(response.parsed_body['redirect_on_complete']).to eq("https://google.com")
end
end
end
context "with user" do
before do
sign_in(user) sign_in(user)
end end
@ -267,3 +369,4 @@ describe CustomWizard::StepsController do
end end
end end
end end
end

Datei anzeigen

@ -8,7 +8,6 @@ describe CustomWizard::WizardController do
before do before do
CustomWizard::Template.save(wizard_template, skip_jobs: true) CustomWizard::Template.save(wizard_template, skip_jobs: true)
@template = CustomWizard::Template.find("super_mega_fun_wizard") @template = CustomWizard::Template.find("super_mega_fun_wizard")
sign_in(user)
end end
context 'plugin disabled' do context 'plugin disabled' do
@ -32,8 +31,12 @@ describe CustomWizard::WizardController do
expect(response.parsed_body["error"]).to eq("We couldn't find a wizard at that address.") expect(response.parsed_body["error"]).to eq("We couldn't find a wizard at that address.")
end end
context 'when user skips the wizard' do context "with user" do
before do
sign_in(user)
end
context 'when user skips' do
it 'skips a wizard if user is allowed to skip' do it 'skips a wizard if user is allowed to skip' do
put '/w/super-mega-fun-wizard/skip.json' put '/w/super-mega-fun-wizard/skip.json'
expect(response.status).to eq(200) expect(response.status).to eq(200)
@ -94,3 +97,4 @@ describe CustomWizard::WizardController do
end end
end end
end end
end

Datei anzeigen

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

Datei anzeigen

@ -9,6 +9,7 @@ import {
import { import {
wizard, wizard,
wizardCompleted, wizardCompleted,
wizardGuest,
wizardNoUser, wizardNoUser,
wizardNotPermitted, wizardNotPermitted,
} from "../helpers/wizard"; } from "../helpers/wizard";
@ -106,3 +107,59 @@ acceptance("Wizard | Wizard", function (needs) {
assert.strictEqual($("body.custom-wizard").length, 0); assert.strictEqual($("body.custom-wizard").length, 0);
}); });
}); });
acceptance("Wizard | Guest access", function (needs) {
needs.pretender((server, helper) => {
server.get("/w/wizard.json", () => helper.response(wizardGuest));
});
test("Does not require login", async function (assert) {
await visit("/w/wizard");
assert.ok(!exists(".wizard-no-access.requires-login"));
});
test("Starts", async function (assert) {
await visit("/w/wizard");
assert.ok(query(".wizard-column"), true);
});
test("Applies the wizard body class", async function (assert) {
await visit("/w/wizard");
assert.ok($("body.custom-wizard").length);
});
test("Applies the body background color", async function (assert) {
await visit("/w/wizard");
assert.ok($("body")[0].style.background);
});
test("Renders the wizard form", async function (assert) {
await visit("/w/wizard");
assert.ok(exists(".wizard-column-contents .wizard-step"), true);
assert.ok(exists(".wizard-footer img"), true);
});
test("Renders the first step", async function (assert) {
await visit("/w/wizard");
assert.strictEqual(
query(".wizard-step-title p").textContent.trim(),
"Text"
);
assert.strictEqual(
query(".wizard-step-description p").textContent.trim(),
"Text inputs!"
);
assert.strictEqual(
query(".wizard-step-description p").textContent.trim(),
"Text inputs!"
);
assert.strictEqual(count(".wizard-step-form .wizard-field"), 6);
assert.ok(exists(".wizard-step-footer .wizard-progress"), true);
assert.ok(exists(".wizard-step-footer .wizard-buttons"), true);
});
test("Removes the wizard body class when navigating away", async function (assert) {
await visit("/");
assert.strictEqual($("body.custom-wizard").length, 0);
});
});

Datei anzeigen

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

Datei anzeigen

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