0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-11-09 20:02:54 +01:00
Dieser Commit ist enthalten in:
jumagura 2023-03-15 21:43:03 -04:00
Commit 9b8a3589bd
64 geänderte Dateien mit 1123 neuen und 746 gelöschten Zeilen

11
.github/workflows/discourse-plugin.yml gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,11 @@
name: Discourse Plugin
on:
push:
branches:
- main
pull_request:
jobs:
ci:
uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1

Datei anzeigen

@ -1,54 +0,0 @@
name: Linting
on:
push:
branches:
- main
- stable
pull_request:
concurrency:
group: plugin-linting-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 16
cache: yarn
- name: Yarn install
run: yarn install
- name: Set up ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
bundler-cache: true
- name: ESLint
if: ${{ always() }}
run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,assets}/javascripts
- name: Prettier
if: ${{ always() }}
shell: bash
run: |
yarn prettier -v
if [ 0 -lt $(find assets -type f \( -name "*.scss" -or -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
yarn prettier --list-different "assets/**/*.{scss,js,es6}"
fi
if [ 0 -lt $(find test -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
yarn prettier --list-different "test/**/*.{js,es6}"
fi
- name: Rubocop
if: ${{ always() }}
run: bundle exec rubocop .

Datei anzeigen

@ -1,136 +0,0 @@
name: Plugin Tests
on:
push:
branches:
- main
- stable
pull_request:
concurrency:
group: tests-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
cancel-in-progress: true
jobs:
build:
name: ${{ matrix.build_type }}
runs-on: ubuntu-latest
container: discourse/discourse_test:slim${{ startsWith(matrix.build_type, 'frontend') && '-browsers' || '' }}
timeout-minutes: 30
env:
DISCOURSE_HOSTNAME: www.example.com
RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072
RAILS_ENV: test
PGUSER: discourse
PGPASSWORD: discourse
strategy:
fail-fast: false
matrix:
build_type: ["backend", "frontend"]
steps:
- uses: actions/checkout@v3
with:
repository: discourse/discourse
fetch-depth: 1
- name: Install plugin
uses: actions/checkout@v3
with:
path: plugins/${{ github.event.repository.name }}
fetch-depth: 1
- name: Setup Git
run: |
git config --global user.email "ci@ci.invalid"
git config --global user.name "Discourse CI"
- name: Start redis
run: |
redis-server /etc/redis/redis.conf &
- name: Start Postgres
run: |
chown -R postgres /var/run/postgresql
sudo -E -u postgres script/start_test_db.rb
sudo -u postgres psql -c "CREATE ROLE $PGUSER LOGIN SUPERUSER PASSWORD '$PGPASSWORD';"
- name: Bundler cache
uses: actions/cache@v3
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-
- name: Setup gems
run: |
gem install bundler --conservative -v $(awk '/BUNDLED WITH/ { getline; gsub(/ /,""); print $0 }' Gemfile.lock)
bundle config --local path vendor/bundle
bundle config --local deployment true
bundle config --local without development
bundle install --jobs 4
bundle clean
- name: Lint English locale
if: matrix.build_type == 'backend'
run: bundle exec ruby script/i18n_lint.rb "plugins/${{ github.event.repository.name }}/locales/{client,server}.en.yml"
- name: Get yarn cache directory
id: yarn-cache-dir
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn cache
uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Yarn install
run: yarn install
- name: Fetch app state cache
uses: actions/cache@v3
id: app-cache
with:
path: tmp/app-cache
key: >-
${{ hashFiles('.github/workflows/tests.yml') }}-
${{ hashFiles('db/**/*', 'plugins/**/db/**/*') }}-
- name: Restore database from cache
if: steps.app-cache.outputs.cache-hit == 'true'
run: psql -f tmp/app-cache/cache.sql postgres
- name: Restore uploads from cache
if: steps.app-cache.outputs.cache-hit == 'true'
run: rm -rf public/uploads && cp -r tmp/app-cache/uploads public/uploads
- name: Create and migrate database
if: steps.app-cache.outputs.cache-hit != 'true'
run: |
bin/rake db:create
bin/rake db:migrate
- name: Dump database for cache
if: steps.app-cache.outputs.cache-hit != 'true'
run: mkdir -p tmp/app-cache && pg_dumpall > tmp/app-cache/cache.sql
- name: Dump uploads for cache
if: steps.app-cache.outputs.cache-hit != 'true'
run: rm -rf tmp/app-cache/uploads && cp -r public/uploads tmp/app-cache/uploads
- name: Plugin RSpec
if: matrix.build_type == 'backend'
run: bin/rake plugin:spec[${{ github.event.repository.name }}]
- name: Plugin QUnit
if: matrix.build_type == 'frontend'
run: QUNIT_EMBER_CLI=1 bundle exec rake plugin:qunit['${{ github.event.repository.name }}','1200000']
timeout-minutes: 10

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

@ -12,6 +12,7 @@ import { alias } from "@ember/object/computed";
import Site from "discourse/models/site"; import Site from "discourse/models/site";
import { uploadIcon } from "discourse/lib/uploads"; import { uploadIcon } from "discourse/lib/uploads";
import { dasherize } from "@ember/string"; import { dasherize } from "@ember/string";
import showModal from "discourse/lib/show-modal";
const IMAGE_MARKDOWN_REGEX = /!\[(.*?)\|(\d{1,4}x\d{1,4})(,\s*\d{1,3}%)?(.*?)\]\((upload:\/\/.*?)\)(?!(.*`))/g; const IMAGE_MARKDOWN_REGEX = /!\[(.*?)\|(\d{1,4}x\d{1,4})(,\s*\d{1,3}%)?(.*?)\]\((upload:\/\/.*?)\)(?!(.*`))/g;
@ -19,7 +20,6 @@ export default ComposerEditor.extend({
classNameBindings: ["fieldClass"], classNameBindings: ["fieldClass"],
allowUpload: true, allowUpload: true,
showLink: false, showLink: false,
showHyperlinkBox: false,
topic: null, topic: null,
showToolbar: true, showToolbar: true,
focusTarget: "reply", focusTarget: "reply",
@ -29,6 +29,7 @@ export default ComposerEditor.extend({
draftStatus: "null", draftStatus: "null",
replyPlaceholder: alias("field.translatedPlaceholder"), replyPlaceholder: alias("field.translatedPlaceholder"),
wizardEventFieldId: null, wizardEventFieldId: null,
composerEventPrefix: "wizard-editor",
@on("didInsertElement") @on("didInsertElement")
_composerEditorInit() { _composerEditorInit() {
@ -77,24 +78,13 @@ export default ComposerEditor.extend({
$input.on("scroll", this._throttledSyncEditorAndPreviewScroll); $input.on("scroll", this._throttledSyncEditorAndPreviewScroll);
this._bindUploadTarget(); this._bindUploadTarget();
const wizardEventNames = ["insert-text", "replace-text"]; const field = this.field;
const eventPrefix = this.eventPrefix; this.editorInputClass = `.${dasherize(field.type)}-${dasherize(
this.appEvents.reopen({ field.id
trigger(name, ...args) { )} .d-editor-input`;
let eventParts = name.split(":");
let currentEventPrefix = eventParts[0];
let currentEventName = eventParts[1];
if ( this._uppyInstance.on("file-added", () => {
currentEventPrefix !== "wizard-editor" && this.session.set("wizardEventFieldId", field.id);
wizardEventNames.some((wen) => wen === currentEventName)
) {
let wizardEventName = name.replace(eventPrefix, "wizard-editor");
return this._super(wizardEventName, ...args);
} else {
return this._super(name, ...args);
}
},
}); });
}, },
@ -116,12 +106,6 @@ export default ComposerEditor.extend({
return uploadIcon(false, this.siteSettings); return uploadIcon(false, this.siteSettings);
}, },
click(e) {
if ($(e.target).hasClass("wizard-composer-hyperlink")) {
this.set("showHyperlinkBox", false);
}
},
@bind @bind
_handleImageDeleteButtonClick(event) { _handleImageDeleteButtonClick(event) {
if (!event.target.classList.contains("delete-image-button")) { if (!event.target.classList.contains("delete-image-button")) {
@ -165,7 +149,7 @@ export default ComposerEditor.extend({
shortcut: "K", shortcut: "K",
trimLeading: true, trimLeading: true,
unshift: true, unshift: true,
sendAction: () => component.set("showHyperlinkBox", true), sendAction: (event) => component.send("showLinkModal", event),
}); });
if (this.siteSettings.mentionables_enabled) { if (this.siteSettings.mentionables_enabled) {
@ -206,17 +190,18 @@ export default ComposerEditor.extend({
this._super(...arguments); this._super(...arguments);
}, },
addLink(linkName, linkUrl) { showLinkModal(toolbarEvent) {
let link = `[${linkName}](${linkUrl})`; let linkText = "";
this.appEvents.trigger("wizard-editor:insert-text", { this._lastSel = toolbarEvent.selected;
fieldId: this.field.id,
text: link,
});
this.set("showHyperlinkBox", false);
},
hideBox() { if (this._lastSel) {
this.set("showHyperlinkBox", false); linkText = this._lastSel.value;
}
showModal("insert-hyperlink").setProperties({
linkText,
toolbarEvent,
});
}, },
showUploadModal() { showUploadModal() {

Datei anzeigen

@ -1,15 +0,0 @@
import Component from "@ember/component";
export default Component.extend({
classNames: ["wizard-composer-hyperlink"],
actions: {
addLink() {
this.addLink(this.linkName, this.linkUrl);
},
hideBox() {
this.hideBox();
},
},
});

Datei anzeigen

@ -4,7 +4,10 @@ export default TagChooser.extend({
searchTags(url, data, callback) { searchTags(url, data, callback) {
if (this.tagGroups) { if (this.tagGroups) {
let tagGroupsString = this.tagGroups.join(","); let tagGroupsString = this.tagGroups.join(",");
data.tag_groups = tagGroupsString; data.filterForInput = {
name: "custom-wizard-tag-chooser",
groups: tagGroupsString,
};
} }
return this._super(url, data, callback); return this._super(url, data, callback);

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

@ -72,6 +72,7 @@ const field = {
required: null, required: null,
type: null, type: null,
condition: null, condition: null,
tag_groups: null,
}, },
types: {}, types: {},
mapped: ["prefill", "content", "condition", "index"], mapped: ["prefill", "content", "condition", "index"],
@ -210,11 +211,41 @@ 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"],
}; };
export function buildFieldTypes(types) {
wizardSchema.field.types = types;
}
field.type = Object.keys(field.types); field.type = Object.keys(field.types);
action.type = Object.keys(action.types); action.type = Object.keys(action.types);
@ -224,16 +255,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 && 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

@ -4,6 +4,8 @@ import { get, set } from "@ember/object";
import Mixin from "@ember/object/mixin"; import Mixin from "@ember/object/mixin";
import { deepEqual } from "discourse-common/lib/object"; import { deepEqual } from "discourse-common/lib/object";
const observedCache = [];
export default Mixin.create({ export default Mixin.create({
didInsertElement() { didInsertElement() {
this._super(...arguments); this._super(...arguments);
@ -32,7 +34,13 @@ export default Mixin.create({
}; };
listProperties(componentType, opts).forEach((property) => { listProperties(componentType, opts).forEach((property) => {
obj.removeObserver(property, this, this.toggleUndo); if (observedCache.includes(property)) {
obj.removeObserver(property, this, this.toggleUndo);
let index = observedCache.indexOf(property);
if (index !== -1) {
observedCache.splice(index, 1);
}
}
}); });
}, },
@ -45,6 +53,9 @@ export default Mixin.create({
}; };
listProperties(componentType, opts).forEach((property) => { listProperties(componentType, opts).forEach((property) => {
if (observedCache.indexOf(property) === -1) {
observedCache.push(property);
}
obj.addObserver(property, this, this.toggleUndo); obj.addObserver(property, this, this.toggleUndo);
}); });
}, },

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

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

@ -1,21 +0,0 @@
<div class="wizard-composer-hyperlink-contents">
<h3>{{i18n "composer.link_dialog_title"}}</h3>
{{input
class="composer-link-name"
placeholder=(i18n "composer.link_optional_text")
type="text"
value=linkName}}
{{input
class="composer-link-url"
placeholder=(i18n "composer.link_url_placeholder")
type="text"
value=linkUrl}}
{{d-button
label="wizard_composer.modal_ok"
class="add-link btn-primary"
click=(action "addLink")}}
{{d-button
label="wizard_composer.modal_cancel"
class="hide-hyperlink-box btn-danger"
click=(action "hideBox")}}
</div>

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

@ -1,10 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'request_store'
module CustomWizardDiscourseTagging module CustomWizardDiscourseTagging
def filter_allowed_tags(guardian, opts = {}) def filter_allowed_tags(guardian, opts = {})
if tag_groups = ::RequestStore.store[:tag_groups] if opts[:for_input].respond_to?(:dig) && (groups = opts.dig(:for_input, :groups)).present?
tag_group_array = tag_groups.split(",") tag_group_array = groups.split(",")
filtered_tags = TagGroup.includes(:tags).where(name: tag_group_array).map do |tag_group| filtered_tags = TagGroup.includes(:tags).where(name: tag_group_array).map do |tag_group|
tag_group.tags.pluck(:name) tag_group.tags.pluck(:name)
end.flatten end.flatten

Datei anzeigen

@ -1,9 +0,0 @@
# frozen_string_literal: true
require 'request_store'
module CustomWizardTagsController
def search
::RequestStore.store[:tag_groups] = params[:tag_groups] if params[:tag_groups].present?
super
end
end

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
@ -253,7 +255,7 @@ class CustomWizard::Mapper
end end
end end
if opts[:template] && CustomWizard::Subscription.subscribed? if opts[:template] #&& CustomWizard::Subscription.subscribed?
template = Liquid::Template.parse(string) template = Liquid::Template.parse(string)
string = template.render(data) string = template.render(data)
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"
@user = user 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 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],
context: id,
subject: all_step_ids
).order("created_at").last
last_completed_step.subject last_completed_step = CustomWizard::UserHistory.where(
else actor_id: actor_id,
nil action: CustomWizard::UserHistory.actions[:step],
end context: id,
subject: all_step_ids
).order("created_at").last
last_completed_step&.subject
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,27 +226,32 @@ class CustomWizard::Wizard
return true if mapper.blank? return true if mapper.blank?
mapper.all? do |m| mapper.all? do |m|
if m[:type] === 'assignment' if !user
[*m[:result]].include?(Group::AUTO_GROUPS[:everyone]) || m[:type] === 'assignment' && [*m[:result]].include?(GUEST_GROUP_ID)
GroupUser.exists?(group_id: m[:result], user_id: user.id)
elsif m[:type] === 'validation'
m[:result]
else else
true if m[:type] === 'assignment'
[*m[:result]].include?(GUEST_GROUP_ID) ||
[*m[:result]].include?(Group::AUTO_GROUPS[:everyone]) ||
GroupUser.exists?(group_id: m[:result], user_id: user.id)
elsif m[:type] === 'validation'
m[:result]
else
true
end
end end
end 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 +279,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 +315,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 +336,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 +397,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,11 @@
# 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.
<<<<<<< HEAD
# version: 2.1.5 # version: 2.1.5
=======
# version: 2.2.9
>>>>>>> main
# 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 +45,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 +70,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
@ -88,7 +94,6 @@ after_initialize do
../lib/custom_wizard/extensions/extra_locales_controller.rb ../lib/custom_wizard/extensions/extra_locales_controller.rb
../lib/custom_wizard/extensions/invites_controller.rb ../lib/custom_wizard/extensions/invites_controller.rb
../lib/custom_wizard/extensions/users_controller.rb ../lib/custom_wizard/extensions/users_controller.rb
../lib/custom_wizard/extensions/tags_controller.rb
../lib/custom_wizard/extensions/guardian.rb ../lib/custom_wizard/extensions/guardian.rb
../lib/custom_wizard/extensions/custom_field/preloader.rb ../lib/custom_wizard/extensions/custom_field/preloader.rb
../lib/custom_wizard/extensions/custom_field/serializer.rb ../lib/custom_wizard/extensions/custom_field/serializer.rb
@ -230,7 +235,6 @@ after_initialize do
end end
reloadable_patch do |plugin| reloadable_patch do |plugin|
::TagsController.prepend CustomWizardTagsController
::DiscourseTagging.singleton_class.prepend CustomWizardDiscourseTagging ::DiscourseTagging.singleton_class.prepend CustomWizardDiscourseTagging
end end

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,14 +80,11 @@ 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] subject: step[:id]
}.merge(
subject: step[:id]
)
) )
end end

Datei anzeigen

@ -373,7 +373,7 @@ describe CustomWizard::Mapper do
expect(result).to eq(template_params["step_1_field_1"]) expect(result).to eq(template_params["step_1_field_1"])
end end
it "requires a subscription" do it "does not require a subscription" do
template = '{{ "w{step_1_field_1}" | size }}' template = '{{ "w{step_1_field_1}" | size }}'
mapper = create_template_mapper(template_params, user1) mapper = create_template_mapper(template_params, user1)
result = mapper.interpolate( result = mapper.interpolate(
@ -383,7 +383,7 @@ describe CustomWizard::Mapper do
wizard: true, wizard: true,
value: true value: true
) )
expect(result).to eq("{{ \"#{template_params["step_1_field_1"]}\" | size }}") expect(result).to eq("5")
end end
context "with a subscription" do context "with a subscription" do

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

@ -6,11 +6,14 @@ describe CustomWizard::Wizard do
fab!(:admin_user) { Fabricate(:user, admin: true) } fab!(:admin_user) { Fabricate(:user, admin: true) }
let(:template_json) { get_wizard_fixture("wizard") } let(:template_json) { get_wizard_fixture("wizard") }
let(:permitted_json) { get_wizard_fixture("wizard/permitted") } let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
let(:guests_permitted_json) { get_wizard_fixture("wizard/guests_permitted") }
before do before do
Group.refresh_automatic_group!(:trust_level_3) Group.refresh_automatic_group!(:trust_level_3)
@permitted_template = template_json.dup @permitted_template = template_json.dup
@permitted_template["permitted"] = permitted_json["permitted"] @permitted_template["permitted"] = permitted_json["permitted"]
@guests_permitted_template = template_json.dup
@guests_permitted_template["permitted"] = guests_permitted_json["permitted"]
@wizard = CustomWizard::Wizard.new(template_json, user) @wizard = CustomWizard::Wizard.new(template_json, user)
end end
@ -21,10 +24,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 +161,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 +175,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
@ -200,6 +203,30 @@ describe CustomWizard::Wizard do
end end
end end
context "with subscription and guest wizard" do
before do
enable_subscription("standard")
end
it "permits admins" do
expect(
CustomWizard::Wizard.new(@guests_permitted_template, admin_user).permitted?
).to eq(true)
end
it "permits regular users" do
expect(
CustomWizard::Wizard.new(@guests_permitted_template, user).permitted?
).to eq(true)
end
it "permits guests" do
expect(
CustomWizard::Wizard.new(@guests_permitted_template, nil, "guest123").permitted?
).to eq(true)
end
end
context "submissions" do context "submissions" do
before do before do
CustomWizard::Submission.new(@wizard, step_1_field_1: "I am a user submission").save CustomWizard::Submission.new(@wizard, step_1_field_1: "I am a user submission").save

Datei anzeigen

@ -0,0 +1,67 @@
# frozen_string_literal: true
describe ::DiscourseTagging, type: :request do
fab!(:user) { Fabricate(:user) }
fab!(:tag_1) { Fabricate(:tag, name: "Angus") }
fab!(:tag_2) { Fabricate(:tag, name: "Faizaan") }
fab!(:tag_3) { Fabricate(:tag, name: "Robert") }
fab!(:tag_4) { Fabricate(:tag, name: "Eli") }
fab!(:tag_5) { Fabricate(:tag, name: "Jeff") }
fab!(:tag_group_1) { Fabricate(:tag_group, tags: [tag_1, tag_2]) }
fab!(:tag_group_2) { Fabricate(:tag_group, tags: [tag_3, tag_4]) }
describe "#filter_allowed_tags" do
let(:guardian) { Guardian.new(user) }
context "for_input is a boolean" do
it "works normally" do
filter_params = {
q: '',
for_input: true
}
tags = DiscourseTagging.filter_allowed_tags(guardian, filter_params)
names = tags.map(&:name)
all_tag_names = Tag.all.pluck(:name)
expect(names).to contain_exactly(*all_tag_names)
end
end
context "for_input is an object including a tag group" do
it "returns tags only in the tag group" do
filter_params = {
q: "",
for_input: {
name: "custom-wizard-tag-chooser",
groups: tag_group_1.name
}
}
tags = DiscourseTagging.filter_allowed_tags(guardian, filter_params)
names = tags.map(&:name)
expected_tag_names = TagGroup
.includes(:tags)
.where(id: tag_group_1.id)
.map { |tag_group| tag_group.tags.pluck(:name) }.flatten
expect(names).to contain_exactly(*expected_tag_names)
end
end
context "for_input is an object including an empty tag group string" do
it "returns all tags" do
filter_params = {
q: "",
for_input: {
name: "custom-wizard-tag-chooser",
groups: ""
}
}
tags = DiscourseTagging.filter_allowed_tags(guardian, filter_params)
names = tags.map(&:name)
all_tag_names = Tag.all.pluck(:name)
expect(names).to contain_exactly(*all_tag_names)
end
end
end
end

Datei anzeigen

@ -1,46 +0,0 @@
# frozen_string_literal: true
describe ::TagsController, type: :request do
fab!(:tag_1) { Fabricate(:tag, name: "Angus") }
fab!(:tag_2) { Fabricate(:tag, name: "Faizaan") }
fab!(:tag_3) { Fabricate(:tag, name: "Robert") }
fab!(:tag_4) { Fabricate(:tag, name: "Eli") }
fab!(:tag_5) { Fabricate(:tag, name: "Jeff") }
fab!(:tag_group_1) { Fabricate(:tag_group, tags: [tag_1, tag_2]) }
fab!(:tag_group_2) { Fabricate(:tag_group, tags: [tag_3, tag_4]) }
before do
::RequestStore.store[:tag_groups] = nil
end
describe "#search" do
context "tag group param present" do
it "returns tags only in the tag group" do
get "/tags/filter/search.json", params: { q: '', tag_groups: [tag_group_1.name, tag_group_2.name] }
expect(response.status).to eq(200)
results = response.parsed_body['results']
names = results.map { |result| result['name'] }
expected_tag_names = TagGroup
.includes(:tags)
.where(id: [tag_group_1.id, tag_group_2.id])
.map { |tag_group| tag_group.tags.pluck(:name) }.flatten
expect(names).to contain_exactly(*expected_tag_names)
end
end
context "tag group param not present" do
it "returns all tags" do
get "/tags/filter/search.json", params: { q: '' }
expect(response.status).to eq(200)
results = response.parsed_body['results']
names = results.map { |result| result['name'] }
all_tag_names = Tag.all.pluck(:name)
expect(names).to contain_exactly(*all_tag_names)
end
end
end
end

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

@ -13,7 +13,7 @@ describe CustomWizard::AdminManagerController do
template_3["id"] = 'super_mega_fun_wizard_3' template_3["id"] = 'super_mega_fun_wizard_3'
@template_array = [template, template_2, template_3] @template_array = [template, template_2, template_3]
FileUtils.mkdir_p(file_from_fixtures_tmp_folder) unless Dir.exists?(file_from_fixtures_tmp_folder) FileUtils.mkdir_p(file_from_fixtures_tmp_folder) unless Dir.exist?(file_from_fixtures_tmp_folder)
@tmp_file_path = File.join(file_from_fixtures_tmp_folder, SecureRandom.hex << 'wizards.json') @tmp_file_path = File.join(file_from_fixtures_tmp_folder, SecureRandom.hex << 'wizards.json')
File.write(@tmp_file_path, @template_array.to_json) File.write(@tmp_file_path, @template_array.to_json)
end end

Datei anzeigen

@ -7,192 +7,209 @@ 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)
sign_in(user)
end end
it 'performs a step update' do def guest_template
put '/w/super-mega-fun-wizard/steps/step_1.json', params: { temp = wizard_template.dup
fields: { temp["permitted"] = guests_permitted["permitted"]
step_1_field_1: "Text input" temp.delete("actions")
temp["actions"] = [route_to_template]
temp
end
context "with guest" do
it "does not perform a step update" do
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Text input"
}
} }
}
expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_2")
wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user)
expect(wizard.current_submission.fields['step_1_field_1']).to eq("Text input")
end
context "raises an error" do
it "when the wizard doesnt exist" do
put '/w/not-super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(400)
end
it "when the user cant access the wizard" do
enable_subscription("standard")
new_template = wizard_template.dup
new_template["permitted"] = permitted_json["permitted"]
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(403) expect(response.status).to eq(403)
end end
it "when the step doesnt exist" do context "with guests permitted" do
put '/w/super-mega-fun-wizard/steps/step_10.json' before do
expect(response.status).to eq(400) 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
end end
it "works if the step has no fields" do context "with user" do
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_2")
end
it "returns an updated wizard when condition passes" do
new_template = wizard_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will pass"
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_2")
end
it "runs completion actions if user has completed wizard" do
new_template = wizard_template.dup
## route_to action
new_template['actions'].last['run_after'] = 'wizard_completion'
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json'
put '/w/super-mega-fun-wizard/steps/step_2.json'
put '/w/super-mega-fun-wizard/steps/step_3.json'
expect(response.status).to eq(200)
expect(response.parsed_body['redirect_on_complete']).to eq("https://google.com")
end
it "saves results of completion actions if user has completed wizard" do
new_template = wizard_template.dup
new_template['actions'].first['run_after'] = 'wizard_completion'
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Topic title",
step_1_field_2: "Topic post"
}
}
put '/w/super-mega-fun-wizard/steps/step_2.json'
put '/w/super-mega-fun-wizard/steps/step_3.json'
wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user)
topic_id = wizard.submissions.first.fields[new_template['actions'].first['id']]
topic = Topic.find(topic_id)
expect(topic.present?).to eq(true)
end
it "returns a final step without conditions" do
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_2.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_3.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
context "subscription" do
before do before do
enable_subscription("standard") sign_in(user)
end end
it "raises an error when user cant see the step due to conditions" do it 'performs a step update' do
sign_in(user2)
new_wizard_template = wizard_template.dup
new_wizard_template['steps'][0]['condition'] = user_condition_template['condition']
CustomWizard::Template.save(new_wizard_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(403)
end
it "returns an updated wizard when condition doesnt pass" do
new_template = wizard_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: { put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: { fields: {
step_1_field_1: "Condition wont pass" step_1_field_1: "Text input"
} }
} }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_3") expect(response.parsed_body['wizard']['start']).to eq("step_2")
wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user)
expect(wizard.current_submission.fields['step_1_field_1']).to eq("Text input")
end end
it "returns the correct final step when the conditional final step and last step are the same" do context "raises an error" do
new_template = wizard_template.dup it "when the wizard doesnt exist" do
new_template['steps'][0]['condition'] = user_condition_template['condition'] put '/w/not-super-mega-fun-wizard/steps/step_1.json'
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition'] expect(response.status).to eq(400)
CustomWizard::Template.save(new_template, skip_jobs: true) end
it "when the user cant access the wizard" do
enable_subscription("standard")
new_template = wizard_template.dup
new_template["permitted"] = permitted_json["permitted"]
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(403)
end
it "when the step doesnt exist" do
put '/w/super-mega-fun-wizard/steps/step_10.json'
expect(response.status).to eq(400)
end
end end
it "raises an error when user cant see the step due to conditions" do it "works if the step has no fields" do
sign_in(user2)
new_wizard_template = wizard_template.dup
new_wizard_template['steps'][0]['condition'] = user_condition_template['condition']
CustomWizard::Template.save(new_wizard_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json' put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(403) expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_2")
end end
it "returns an updated wizard when condition doesnt pass" do it "returns an updated wizard when condition passes" do
new_template = wizard_template.dup new_template = wizard_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition'] new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true) CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition wont pass"
}
}
expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_3")
end
it "returns the correct final step when the conditional final step and last step are the same" do
new_template = wizard_template.dup
new_template['steps'][0]['condition'] = user_condition_template['condition']
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: { put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: { fields: {
step_1_field_1: "Condition will pass" step_1_field_1: "Condition will pass"
} }
} }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.parsed_body['wizard']['start']).to eq("step_2")
end
it "runs completion actions if user has completed wizard" do
new_template = wizard_template.dup
## route_to action
new_template['actions'].last['run_after'] = 'wizard_completion'
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json'
put '/w/super-mega-fun-wizard/steps/step_2.json'
put '/w/super-mega-fun-wizard/steps/step_3.json'
expect(response.status).to eq(200)
expect(response.parsed_body['redirect_on_complete']).to eq("https://google.com")
end
it "saves results of completion actions if user has completed wizard" do
new_template = wizard_template.dup
new_template['actions'].first['run_after'] = 'wizard_completion'
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Topic title",
step_1_field_2: "Topic post"
}
}
put '/w/super-mega-fun-wizard/steps/step_2.json'
put '/w/super-mega-fun-wizard/steps/step_3.json'
wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user)
topic_id = wizard.submissions.first.fields[new_template['actions'].first['id']]
topic = Topic.find(topic_id)
expect(topic.present?).to eq(true)
end
it "returns a final step without conditions" do
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false) expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_2.json' put '/w/super-mega-fun-wizard/steps/step_2.json'
@ -204,66 +221,152 @@ describe CustomWizard::StepsController do
expect(response.parsed_body['final']).to eq(true) expect(response.parsed_body['final']).to eq(true)
end end
it "returns the correct final step when the conditional final step and last step are different" do context "subscription" do
new_template = wizard_template.dup before do
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition'] enable_subscription("standard")
CustomWizard::Template.save(new_template, skip_jobs: true) end
put '/w/super-mega-fun-wizard/steps/step_1.json', params: { it "raises an error when user cant see the step due to conditions" do
fields: { sign_in(user2)
step_1_field_1: "Condition will not pass"
new_wizard_template = wizard_template.dup
new_wizard_template['steps'][0]['condition'] = user_condition_template['condition']
CustomWizard::Template.save(new_wizard_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(403)
end
it "returns an updated wizard when condition doesnt pass" do
new_template = wizard_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition wont pass"
}
} }
} expect(response.status).to eq(200)
expect(response.status).to eq(200) expect(response.parsed_body['wizard']['start']).to eq("step_3")
expect(response.parsed_body['final']).to eq(false) end
put '/w/super-mega-fun-wizard/steps/step_2.json' it "returns the correct final step when the conditional final step and last step are the same" do
expect(response.status).to eq(200) new_template = wizard_template.dup
expect(response.parsed_body['final']).to eq(true) new_template['steps'][0]['condition'] = user_condition_template['condition']
end new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
end
it "returns the correct final step when the conditional final step is determined in the same action" do it "raises an error when user cant see the step due to conditions" do
new_template = wizard_template.dup sign_in(user2)
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: { new_wizard_template = wizard_template.dup
fields: { new_wizard_template['steps'][0]['condition'] = user_condition_template['condition']
step_1_field_1: "Condition will not pass" CustomWizard::Template.save(new_wizard_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(403)
end
it "returns an updated wizard when condition doesnt pass" do
new_template = wizard_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition wont pass"
}
} }
} expect(response.status).to eq(200)
expect(response.status).to eq(200) expect(response.parsed_body['wizard']['start']).to eq("step_3")
expect(response.parsed_body['final']).to eq(true) end
end
it "excludes the non-included conditional fields from the submissions" do it "returns the correct final step when the conditional final step and last step are the same" do
new_template = wizard_template.dup new_template = wizard_template.dup
new_template['steps'][1]['fields'][0]['condition'] = wizard_field_condition_template['condition'] new_template['steps'][0]['condition'] = user_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true) new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: { put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: { fields: {
step_1_field_1: "Condition will pass" step_1_field_1: "Condition will pass"
}
} }
} expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_2.json', params: { put '/w/super-mega-fun-wizard/steps/step_2.json'
fields: { expect(response.status).to eq(200)
step_2_field_1: "1995-04-23" expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_3.json'
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
it "returns the correct final step when the conditional final step and last step are different" do
new_template = wizard_template.dup
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will not pass"
}
} }
} expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: { put '/w/super-mega-fun-wizard/steps/step_2.json'
fields: { expect(response.status).to eq(200)
step_1_field_1: "Condition will not pass" expect(response.parsed_body['final']).to eq(true)
end
it "returns the correct final step when the conditional final step is determined in the same action" do
new_template = wizard_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will not pass"
}
} }
} expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
wizard_id = response.parsed_body['wizard']['id'] it "excludes the non-included conditional fields from the submissions" do
wizard = CustomWizard::Wizard.create(wizard_id, user) new_template = wizard_template.dup
submission = wizard.current_submission new_template['steps'][1]['fields'][0]['condition'] = wizard_field_condition_template['condition']
expect(submission.fields.keys).not_to include("step_2_field_1") CustomWizard::Template.save(new_template, skip_jobs: true)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will pass"
}
}
put '/w/super-mega-fun-wizard/steps/step_2.json', params: {
fields: {
step_2_field_1: "1995-04-23"
}
}
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will not pass"
}
}
wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user)
submission = wizard.current_submission
expect(submission.fields.keys).not_to include("step_2_field_1")
end
end end
end 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,65 +31,70 @@ 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
it 'skips a wizard if user is allowed to skip' do sign_in(user)
put '/w/super-mega-fun-wizard/skip.json'
expect(response.status).to eq(200)
end end
it 'lets user skip if user cant access wizard' do context 'when user skips' do
enable_subscription("standard") it 'skips a wizard if user is allowed to skip' do
@template["permitted"] = permitted_json["permitted"] put '/w/super-mega-fun-wizard/skip.json'
CustomWizard::Template.save(@template, skip_jobs: true) expect(response.status).to eq(200)
put '/w/super-mega-fun-wizard/skip.json' end
expect(response.status).to eq(200)
end
it 'returns a no skip message if user is not allowed to skip' do it 'lets user skip if user cant access wizard' do
enable_subscription("standard") enable_subscription("standard")
@template['required'] = 'true' @template["permitted"] = permitted_json["permitted"]
CustomWizard::Template.save(@template) CustomWizard::Template.save(@template, skip_jobs: true)
put '/w/super-mega-fun-wizard/skip.json' put '/w/super-mega-fun-wizard/skip.json'
expect(response.parsed_body['error']).to eq("Wizard can't be skipped") expect(response.status).to eq(200)
end end
it 'skip response contains a redirect_to if in users submissions' do it 'returns a no skip message if user is not allowed to skip' do
@wizard = CustomWizard::Wizard.create(@template["id"], user) enable_subscription("standard")
CustomWizard::Submission.new(@wizard, redirect_to: "/t/2").save @template['required'] = 'true'
put '/w/super-mega-fun-wizard/skip.json' CustomWizard::Template.save(@template)
expect(response.parsed_body['redirect_to']).to eq('/t/2') put '/w/super-mega-fun-wizard/skip.json'
end expect(response.parsed_body['error']).to eq("Wizard can't be skipped")
end
it 'deletes the users redirect_to_wizard if present' do it 'skip response contains a redirect_to if in users submissions' do
user.custom_fields['redirect_to_wizard'] = @template["id"] @wizard = CustomWizard::Wizard.create(@template["id"], user)
user.save_custom_fields(true) CustomWizard::Submission.new(@wizard, redirect_to: "/t/2").save
@wizard = CustomWizard::Wizard.create(@template["id"], user) put '/w/super-mega-fun-wizard/skip.json'
put '/w/super-mega-fun-wizard/skip.json' expect(response.parsed_body['redirect_to']).to eq('/t/2')
expect(response.status).to eq(200) end
expect(user.reload.redirect_to_wizard).to eq(nil)
end
it "deletes the submission if user has filled up some data" do it 'deletes the users redirect_to_wizard if present' do
@wizard = CustomWizard::Wizard.create(@template["id"], user) user.custom_fields['redirect_to_wizard'] = @template["id"]
CustomWizard::Submission.new(@wizard, step_1_field_1: "Hello World").save user.save_custom_fields(true)
current_submission = @wizard.current_submission @wizard = CustomWizard::Wizard.create(@template["id"], user)
put '/w/super-mega-fun-wizard/skip.json' put '/w/super-mega-fun-wizard/skip.json'
submissions = CustomWizard::Submission.list(@wizard).submissions expect(response.status).to eq(200)
expect(user.reload.redirect_to_wizard).to eq(nil)
end
expect(submissions.any? { |submission| submission.id == current_submission.id }).to eq(false) it "deletes the submission if user has filled up some data" do
end @wizard = CustomWizard::Wizard.create(@template["id"], user)
CustomWizard::Submission.new(@wizard, step_1_field_1: "Hello World").save
current_submission = @wizard.current_submission
put '/w/super-mega-fun-wizard/skip.json'
submissions = CustomWizard::Submission.list(@wizard).submissions
it "starts from the first step if user visits after skipping the wizard" do expect(submissions.any? { |submission| submission.id == current_submission.id }).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_1.json', params: { end
fields: {
step_1_field_1: "Text input" it "starts from the first step if user visits after skipping the wizard" do
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Text input"
}
} }
} put '/w/super-mega-fun-wizard/skip.json'
put '/w/super-mega-fun-wizard/skip.json' get '/w/super-mega-fun-wizard.json'
get '/w/super-mega-fun-wizard.json'
expect(response.parsed_body["start"]).to eq('step_1') expect(response.parsed_body["start"]).to eq('step_1')
end
end end
end 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

@ -54,6 +54,70 @@ acceptance("Field | Fields", function (needs) {
"Input in composer" "Input in composer"
); );
}); });
test("Composer - Hyperlink", async function (assert) {
await visit("/w/wizard");
assert.ok(
visible(".wizard-field.composer-field .wizard-field-composer textarea")
);
assert.ok(
exists(".wizard-field.composer-field .d-editor-button-bar button")
);
assert.ok(visible(".wizard-btn.toggle-preview"));
await fillIn(
".wizard-field.composer-field .wizard-field-composer textarea",
"This is a link to "
);
assert.ok(
!exists(".insert-link.modal-body"),
"no hyperlink modal by default"
);
await click(
".wizard-field.composer-field .wizard-field-composer .d-editor button.link"
);
assert.ok(exists(".insert-link.modal-body"), "hyperlink modal visible");
await fillIn(".modal-body .link-url", "google.com");
await fillIn(".modal-body .link-text", "Google");
await click(".modal-footer button.btn-primary");
assert.strictEqual(
query(".wizard-field.composer-field .wizard-field-composer textarea")
.value,
"This is a link to [Google](https://google.com)",
"adds link with url and text, prepends 'https://'"
);
assert.ok(
!exists(
".wizard-field.composer-field .wizard-field-composer .insert-link.modal-body"
),
"modal dismissed after submitting link"
);
await fillIn(
".wizard-field.composer-field .wizard-field-composer textarea",
"Reset textarea contents."
);
await click(
".wizard-field.composer-field .wizard-field-composer .d-editor button.link"
);
await fillIn(".modal-body .link-url", "google.com");
await fillIn(".modal-body .link-text", "Google");
await click(".modal-footer button.btn-danger");
assert.strictEqual(
query(".wizard-field.composer-field .wizard-field-composer textarea")
.value,
"Reset textarea contents.",
"does not insert anything after cancelling"
);
assert.ok(
!exists(".insert-link.modal-body"),
"modal dismissed after cancelling"
);
});
test("Text Only", async function (assert) { test("Text Only", async function (assert) {
await visit("/w/wizard"); await visit("/w/wizard");

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,