diff --git a/.github/workflows/discourse-plugin.yml b/.github/workflows/discourse-plugin.yml
new file mode 100644
index 00000000..f5cf62e5
--- /dev/null
+++ b/.github/workflows/discourse-plugin.yml
@@ -0,0 +1,11 @@
+name: Discourse Plugin
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
+jobs:
+ ci:
+ uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1
diff --git a/.github/workflows/plugin-linting.yml b/.github/workflows/plugin-linting.yml
deleted file mode 100644
index acb85230..00000000
--- a/.github/workflows/plugin-linting.yml
+++ /dev/null
@@ -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 .
diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml
deleted file mode 100644
index f58f1b64..00000000
--- a/.github/workflows/plugin-tests.yml
+++ /dev/null
@@ -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
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
index 48cea364..66b401ac 100644
--- a/COPYRIGHT.txt
+++ b/COPYRIGHT.txt
@@ -1,4 +1,4 @@
-All code in this repository is Copyright 2018 by Angus McLeod.
+All code in this repository is Copyright 2023 by Angus McLeod.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/app/controllers/custom_wizard/steps.rb b/app/controllers/custom_wizard/steps.rb
index df3c2cb3..2a4305c7 100644
--- a/app/controllers/custom_wizard/steps.rb
+++ b/app/controllers/custom_wizard/steps.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-class CustomWizard::StepsController < ::ApplicationController
- before_action :ensure_logged_in
+class CustomWizard::StepsController < ::CustomWizard::WizardClientController
before_action :ensure_can_update
def update
@@ -22,7 +21,7 @@ class CustomWizard::StepsController < ::ApplicationController
if updater.success?
wizard_id = update_params[:wizard_id]
- builder = CustomWizard::Builder.new(wizard_id, current_user)
+ builder = CustomWizard::Builder.new(wizard_id, current_user, guest_id)
@wizard = builder.build(force: true)
current_step = @wizard.find_step(update[:step_id])
@@ -85,7 +84,6 @@ class CustomWizard::StepsController < ::ApplicationController
private
def ensure_can_update
- @builder = CustomWizard::Builder.new(update_params[:wizard_id], current_user)
raise Discourse::InvalidParameters.new(:wizard_id) if @builder.template.nil?
raise Discourse::InvalidAccess.new if !@builder.wizard || !@builder.wizard.can_access?
diff --git a/app/controllers/custom_wizard/wizard.rb b/app/controllers/custom_wizard/wizard.rb
index 7aafdd3b..dd4ea4ca 100644
--- a/app/controllers/custom_wizard/wizard.rb
+++ b/app/controllers/custom_wizard/wizard.rb
@@ -1,8 +1,5 @@
# frozen_string_literal: true
-class CustomWizard::WizardController < ::ApplicationController
- before_action :ensure_plugin_enabled
- before_action :ensure_logged_in, only: [:skip]
-
+class CustomWizard::WizardController < ::CustomWizard::WizardClientController
def show
if wizard.present?
render json: CustomWizard::WizardSerializer.new(wizard, scope: guardian, root: false).as_json, status: 200
@@ -35,19 +32,8 @@ class CustomWizard::WizardController < ::ApplicationController
def wizard
@wizard ||= begin
- builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user)
- return nil unless builder.present?
- opts = {}
- opts[:reset] = params[:reset]
- builder.build(opts, params)
- end
- end
-
- private
-
- def ensure_plugin_enabled
- unless SiteSetting.custom_wizard_enabled
- redirect_to path("/")
+ return nil unless @builder.present?
+ @builder.build({ reset: params[:reset] }, params)
end
end
end
diff --git a/app/controllers/custom_wizard/wizard_client.rb b/app/controllers/custom_wizard/wizard_client.rb
new file mode 100644
index 00000000..e898852a
--- /dev/null
+++ b/app/controllers/custom_wizard/wizard_client.rb
@@ -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
diff --git a/app/serializers/custom_wizard/submission_serializer.rb b/app/serializers/custom_wizard/submission_serializer.rb
index 732d6743..48892c21 100644
--- a/app/serializers/custom_wizard/submission_serializer.rb
+++ b/app/serializers/custom_wizard/submission_serializer.rb
@@ -2,12 +2,15 @@
class CustomWizard::SubmissionSerializer < ApplicationSerializer
attributes :id,
:fields,
- :submitted_at
-
- has_one :user, serializer: ::BasicUserSerializer, embed: :objects
+ :submitted_at,
+ :user
def include_user?
- object.user.present?
+ object.wizard.user.present?
+ end
+
+ def user
+ ::BasicUserSerializer.new(object.wizard.user).as_json
end
def fields
diff --git a/assets/javascripts/discourse/components/custom-wizard-composer-editor.js.es6 b/assets/javascripts/discourse/components/custom-wizard-composer-editor.js.es6
index 5e2ef424..5335da81 100644
--- a/assets/javascripts/discourse/components/custom-wizard-composer-editor.js.es6
+++ b/assets/javascripts/discourse/components/custom-wizard-composer-editor.js.es6
@@ -12,6 +12,7 @@ import { alias } from "@ember/object/computed";
import Site from "discourse/models/site";
import { uploadIcon } from "discourse/lib/uploads";
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;
@@ -19,7 +20,6 @@ export default ComposerEditor.extend({
classNameBindings: ["fieldClass"],
allowUpload: true,
showLink: false,
- showHyperlinkBox: false,
topic: null,
showToolbar: true,
focusTarget: "reply",
@@ -29,6 +29,7 @@ export default ComposerEditor.extend({
draftStatus: "null",
replyPlaceholder: alias("field.translatedPlaceholder"),
wizardEventFieldId: null,
+ composerEventPrefix: "wizard-editor",
@on("didInsertElement")
_composerEditorInit() {
@@ -77,24 +78,13 @@ export default ComposerEditor.extend({
$input.on("scroll", this._throttledSyncEditorAndPreviewScroll);
this._bindUploadTarget();
- const wizardEventNames = ["insert-text", "replace-text"];
- const eventPrefix = this.eventPrefix;
- this.appEvents.reopen({
- trigger(name, ...args) {
- let eventParts = name.split(":");
- let currentEventPrefix = eventParts[0];
- let currentEventName = eventParts[1];
+ const field = this.field;
+ this.editorInputClass = `.${dasherize(field.type)}-${dasherize(
+ field.id
+ )} .d-editor-input`;
- if (
- currentEventPrefix !== "wizard-editor" &&
- wizardEventNames.some((wen) => wen === currentEventName)
- ) {
- let wizardEventName = name.replace(eventPrefix, "wizard-editor");
- return this._super(wizardEventName, ...args);
- } else {
- return this._super(name, ...args);
- }
- },
+ this._uppyInstance.on("file-added", () => {
+ this.session.set("wizardEventFieldId", field.id);
});
},
@@ -116,12 +106,6 @@ export default ComposerEditor.extend({
return uploadIcon(false, this.siteSettings);
},
- click(e) {
- if ($(e.target).hasClass("wizard-composer-hyperlink")) {
- this.set("showHyperlinkBox", false);
- }
- },
-
@bind
_handleImageDeleteButtonClick(event) {
if (!event.target.classList.contains("delete-image-button")) {
@@ -165,7 +149,7 @@ export default ComposerEditor.extend({
shortcut: "K",
trimLeading: true,
unshift: true,
- sendAction: () => component.set("showHyperlinkBox", true),
+ sendAction: (event) => component.send("showLinkModal", event),
});
if (this.siteSettings.mentionables_enabled) {
@@ -206,17 +190,18 @@ export default ComposerEditor.extend({
this._super(...arguments);
},
- addLink(linkName, linkUrl) {
- let link = `[${linkName}](${linkUrl})`;
- this.appEvents.trigger("wizard-editor:insert-text", {
- fieldId: this.field.id,
- text: link,
- });
- this.set("showHyperlinkBox", false);
- },
+ showLinkModal(toolbarEvent) {
+ let linkText = "";
+ this._lastSel = toolbarEvent.selected;
- hideBox() {
- this.set("showHyperlinkBox", false);
+ if (this._lastSel) {
+ linkText = this._lastSel.value;
+ }
+
+ showModal("insert-hyperlink").setProperties({
+ linkText,
+ toolbarEvent,
+ });
},
showUploadModal() {
diff --git a/assets/javascripts/discourse/components/custom-wizard-composer-hyperlink.js.es6 b/assets/javascripts/discourse/components/custom-wizard-composer-hyperlink.js.es6
deleted file mode 100644
index a56b7aff..00000000
--- a/assets/javascripts/discourse/components/custom-wizard-composer-hyperlink.js.es6
+++ /dev/null
@@ -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();
- },
- },
-});
diff --git a/assets/javascripts/discourse/components/custom-wizard-tag-chooser.js.es6 b/assets/javascripts/discourse/components/custom-wizard-tag-chooser.js.es6
index 32a1fd6a..8d439aa4 100644
--- a/assets/javascripts/discourse/components/custom-wizard-tag-chooser.js.es6
+++ b/assets/javascripts/discourse/components/custom-wizard-tag-chooser.js.es6
@@ -4,7 +4,10 @@ export default TagChooser.extend({
searchTags(url, data, callback) {
if (this.tagGroups) {
let tagGroupsString = this.tagGroups.join(",");
- data.tag_groups = tagGroupsString;
+ data.filterForInput = {
+ name: "custom-wizard-tag-chooser",
+ groups: tagGroupsString,
+ };
}
return this._super(url, data, callback);
diff --git a/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 b/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6
index a257ed12..e19e4917 100644
--- a/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6
+++ b/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6
@@ -15,6 +15,7 @@ import {
import Component from "@ember/component";
import { bind, later } from "@ember/runloop";
import I18n from "I18n";
+import Subscription from "../mixins/subscription";
const customFieldActionMap = {
topic: ["create_topic", "send_message"],
@@ -26,7 +27,7 @@ const customFieldActionMap = {
const values = ["present", "true", "false"];
-export default Component.extend({
+export default Component.extend(Subscription, {
classNameBindings: [":mapper-selector", "activeType"],
showText: computed("activeType", function () {
@@ -116,6 +117,9 @@ export default Component.extend({
groupEnabled: computed("options.groupSelection", "inputType", function () {
return this.optionEnabled("groupSelection");
}),
+ guestGroup: computed("options.guestGroup", "inputType", function () {
+ return this.optionEnabled("guestGroup");
+ }),
userEnabled: computed("options.userSelection", "inputType", function () {
return this.optionEnabled("userSelection");
}),
@@ -126,7 +130,29 @@ export default Component.extend({
return this.connector === "is";
}),
- groups: alias("site.groups"),
+ @discourseComputed("site.groups", "guestGroup", "subscriptionType")
+ groups(groups, guestGroup, subscriptionType) {
+ let result = groups;
+ if (!guestGroup) {
+ return result;
+ }
+
+ if (["standard", "business"].includes(subscriptionType)) {
+ let guestIndex;
+ result.forEach((r, index) => {
+ if (r.id === 0) {
+ r.name = I18n.t("admin.wizard.selector.label.users");
+ guestIndex = index;
+ }
+ });
+ result.splice(guestIndex, 0, {
+ id: -1,
+ name: I18n.t("admin.wizard.selector.label.guests"),
+ });
+ }
+
+ return result;
+ },
categories: alias("site.categories"),
showComboBox: or(
"showWizardField",
diff --git a/assets/javascripts/discourse/components/wizard-mapper.js.es6 b/assets/javascripts/discourse/components/wizard-mapper.js.es6
index 95aabb1c..ec58e3f2 100644
--- a/assets/javascripts/discourse/components/wizard-mapper.js.es6
+++ b/assets/javascripts/discourse/components/wizard-mapper.js.es6
@@ -32,6 +32,7 @@ export default Component.extend({
pairConnector: options.pairConnector || null,
outputConnector: options.outputConnector || null,
context: options.context || null,
+ guestGroup: options.guestGroup || false,
};
let inputTypes = ["key", "value", "output"];
diff --git a/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 b/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6
index 53f7d19c..351b5782 100644
--- a/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6
+++ b/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6
@@ -1,6 +1,6 @@
import SingleSelectComponent from "select-kit/components/single-select";
import Subscription from "../mixins/subscription";
-import wizardSchema from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
+import { filterValues } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n";
@@ -40,9 +40,9 @@ export default SingleSelectComponent.extend(Subscription, {
return allowedTypes;
},
- @discourseComputed("feature", "attribute")
+ @discourseComputed("feature", "attribute", "wizard.allowGuests")
content(feature, attribute) {
- return wizardSchema[feature][attribute]
+ return filterValues(this.wizard, feature, attribute)
.map((value) => {
let allowedSubscriptionTypes = this.allowedSubscriptionTypes(
feature,
diff --git a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6
index c9a80e0e..75ea0ff7 100644
--- a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6
+++ b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6
@@ -10,6 +10,7 @@ import { later, scheduleOnce } from "@ember/runloop";
import Controller from "@ember/controller";
import copyText from "discourse/lib/copy-text";
import I18n from "I18n";
+import { filterValues } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
export default Controller.extend({
hasName: notEmpty("wizard.name"),
@@ -59,6 +60,19 @@ export default Controller.extend({
}
return wizardFieldList(steps);
},
+
+ @discourseComputed("fieldTypes", "wizard.allowGuests")
+ filteredFieldTypes(fieldTypes) {
+ const fieldTypeIds = fieldTypes.map((f) => f.id);
+ const allowedTypeIds = filterValues(
+ this.wizard,
+ "field",
+ "type",
+ fieldTypeIds
+ );
+ return fieldTypes.filter((f) => allowedTypeIds.includes(f.id));
+ },
+
getErrorMessage(result) {
if (result.backend_validation_error) {
return result.backend_validation_error;
diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6
index 8c3323ff..24bda1d3 100644
--- a/assets/javascripts/discourse/lib/wizard-schema.js.es6
+++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6
@@ -72,6 +72,7 @@ const field = {
required: null,
type: null,
condition: null,
+ tag_groups: null,
},
types: {},
mapped: ["prefill", "content", "condition", "index"],
@@ -210,11 +211,41 @@ const action = {
objectArrays: {},
};
+const filters = {
+ allow_guests: {
+ field: {
+ type: [
+ "text",
+ "textarea",
+ "text_only",
+ "date",
+ "time",
+ "date_time",
+ "number",
+ "checkbox",
+ "url",
+ "dropdown",
+ "tag",
+ "category",
+ "group",
+ "user_selector",
+ ],
+ },
+ action: {
+ type: ["route_to", "send_message"],
+ },
+ },
+};
+
const custom_field = {
klass: ["topic", "post", "group", "category"],
type: ["string", "boolean", "integer", "json"],
};
+export function buildFieldTypes(types) {
+ wizardSchema.field.types = types;
+}
+
field.type = Object.keys(field.types);
action.type = Object.keys(action.types);
@@ -224,16 +255,29 @@ const wizardSchema = {
field,
custom_field,
action,
+ filters,
};
-export function buildFieldTypes(types) {
- wizardSchema.field.types = types;
-}
-
export function buildFieldValidations(validations) {
wizardSchema.field.validations = validations;
}
+export function filterValues(currentWizard, feature, attribute, values = null) {
+ values = values || wizardSchema[feature][attribute];
+
+ if (currentWizard && currentWizard.allowGuests) {
+ const filteredFeature = wizardSchema.filters.allow_guests[feature];
+ if (filteredFeature) {
+ const filtered = filteredFeature[attribute];
+ if (filtered) {
+ values = values.filter((v) => filtered.includes(v));
+ }
+ }
+ }
+
+ return values;
+}
+
const siteSettings = getOwner(this).lookup("service:site-settings");
if (siteSettings.wizard_apis_enabled) {
wizardSchema.action.types.send_to_api = {
diff --git a/assets/javascripts/discourse/mixins/undo-changes.js.es6 b/assets/javascripts/discourse/mixins/undo-changes.js.es6
index b2ab322d..e98cfb0e 100644
--- a/assets/javascripts/discourse/mixins/undo-changes.js.es6
+++ b/assets/javascripts/discourse/mixins/undo-changes.js.es6
@@ -4,6 +4,8 @@ import { get, set } from "@ember/object";
import Mixin from "@ember/object/mixin";
import { deepEqual } from "discourse-common/lib/object";
+const observedCache = [];
+
export default Mixin.create({
didInsertElement() {
this._super(...arguments);
@@ -32,7 +34,13 @@ export default Mixin.create({
};
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) => {
+ if (observedCache.indexOf(property) === -1) {
+ observedCache.push(property);
+ }
obj.addObserver(property, this, this.toggleUndo);
});
},
diff --git a/assets/javascripts/discourse/models/custom-wizard-admin.js.es6 b/assets/javascripts/discourse/models/custom-wizard-admin.js.es6
index 65c7aa7f..afca4833 100644
--- a/assets/javascripts/discourse/models/custom-wizard-admin.js.es6
+++ b/assets/javascripts/discourse/models/custom-wizard-admin.js.es6
@@ -5,8 +5,20 @@ import wizardSchema from "../lib/wizard-schema";
import { Promise } from "rsvp";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
+import discourseComputed from "discourse-common/utils/decorators";
+
+const GUEST_GROUP_ID = -1;
const CustomWizardAdmin = EmberObject.extend({
+ @discourseComputed("permitted.@each.output")
+ allowGuests(permitted) {
+ return (
+ permitted &&
+ permitted.filter((p) => p.output && p.output.includes(GUEST_GROUP_ID))
+ .length
+ );
+ },
+
save(opts) {
return new Promise((resolve, reject) => {
let wizard = this.buildJson(this, "wizard");
diff --git a/assets/javascripts/discourse/routes/custom-wizard-index.js.es6 b/assets/javascripts/discourse/routes/custom-wizard-index.js.es6
index 1d5a71c7..5ffe83c6 100644
--- a/assets/javascripts/discourse/routes/custom-wizard-index.js.es6
+++ b/assets/javascripts/discourse/routes/custom-wizard-index.js.es6
@@ -4,13 +4,7 @@ import Route from "@ember/routing/route";
export default Route.extend({
beforeModel() {
const wizard = getCachedWizard();
- if (
- wizard &&
- wizard.user &&
- wizard.permitted &&
- !wizard.completed &&
- wizard.start
- ) {
+ if (wizard && wizard.permitted && !wizard.completed && wizard.start) {
this.replaceWith("customWizardStep", wizard.start);
}
},
@@ -26,7 +20,7 @@ export default Route.extend({
const wizardId = model.get("id");
const user = model.get("user");
const name = model.get("name");
- const requiresLogin = !user;
+ const requiresLogin = !user && !permitted;
const notPermitted = !permitted;
const props = {
diff --git a/assets/javascripts/discourse/routes/custom-wizard-step.js.es6 b/assets/javascripts/discourse/routes/custom-wizard-step.js.es6
index 969df1eb..dd7b8be8 100644
--- a/assets/javascripts/discourse/routes/custom-wizard-step.js.es6
+++ b/assets/javascripts/discourse/routes/custom-wizard-step.js.es6
@@ -7,7 +7,7 @@ export default Route.extend({
const wizard = getCachedWizard();
this.set("wizard", wizard);
- if (!wizard || !wizard.user || !wizard.permitted || wizard.completed) {
+ if (!wizard || !wizard.permitted || wizard.completed) {
this.replaceWith("customWizard");
}
},
diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs
index 11a2b415..a81ec6db 100644
--- a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs
+++ b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs
@@ -140,6 +140,7 @@
context="wizard"
inputTypes="assignment,validation"
groupSelection="output"
+ guestGroup=true
userFieldSelection="key"
textSelection="value"
inputConnector="and"
@@ -160,7 +161,7 @@
wizard=wizard
currentField=currentField
wizardFields=wizardFields
- fieldTypes=fieldTypes
+ fieldTypes=filteredFieldTypes
subscribed=subscribed}}
{{/if}}
@@ -178,7 +179,7 @@
apis=apis
removeAction="removeAction"
wizardFields=wizardFields
- fieldTypes=fieldTypes}}
+ fieldTypes=filteredFieldTypes}}
{{/each}}
diff --git a/assets/javascripts/discourse/templates/components/custom-wizard-composer-hyperlink.hbs b/assets/javascripts/discourse/templates/components/custom-wizard-composer-hyperlink.hbs
deleted file mode 100644
index f430fb59..00000000
--- a/assets/javascripts/discourse/templates/components/custom-wizard-composer-hyperlink.hbs
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
{{i18n "composer.link_dialog_title"}}
- {{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")}}
-
diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs
index 8245f1b8..106c61ee 100644
--- a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs
+++ b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs
@@ -17,6 +17,7 @@
feature="action"
attribute="type"
onChange=(action "changeType")
+ wizard=wizard
options=(hash
none="admin.wizard.select_type"
)
diff --git a/assets/stylesheets/common/wizard/step.scss b/assets/stylesheets/common/wizard/step.scss
index 0b774224..2820cfcb 100644
--- a/assets/stylesheets/common/wizard/step.scss
+++ b/assets/stylesheets/common/wizard/step.scss
@@ -17,7 +17,7 @@ body.custom-wizard .wizard-column {
}
}
- img.emoji {
+ .emoji {
width: 20px;
height: 20px;
vertical-align: middle;
@@ -29,7 +29,7 @@ body.custom-wizard .wizard-column {
p {
img {
- @extend img.emoji;
+ @extend .emoji;
}
}
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 1ec2b3ce..8a856636 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -217,6 +217,8 @@ en:
list: "list"
custom_field: "custom field"
value: "value"
+ users: "users"
+ guests: "users and guests"
placeholder:
text: "Enter text"
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 08cf5336..e8ceb44b 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -53,7 +53,8 @@ en:
after_signup_after_time: "You can't use 'after time' and 'after signup' on the same wizard."
after_time: "After time setting is invalid."
liquid_syntax_error: "Liquid syntax error in %{attribute}: %{message}"
- subscription: "%{type} %{property} is subscription only"
+ subscription: "%{type} %{property} usage is not supported on your subscription"
+ not_permitted_for_guests: "%{object_id} is not permitted when guests can access the wizard"
site_settings:
custom_wizard_enabled: "Enable custom wizards."
diff --git a/lib/custom_wizard/action.rb b/lib/custom_wizard/action.rb
index edd3b23c..34f81455 100644
--- a/lib/custom_wizard/action.rb
+++ b/lib/custom_wizard/action.rb
@@ -6,6 +6,14 @@ class CustomWizard::Action
:guardian,
:result
+ REQUIRES_USER = %w[
+ create_topic
+ update_profile
+ open_composer
+ watch_categories
+ add_to_group
+ ]
+
def initialize(opts)
@wizard = opts[:wizard]
@action = opts[:action]
@@ -17,6 +25,12 @@ class CustomWizard::Action
end
def perform
+ if REQUIRES_USER.include?(action['id']) && !@user
+ log_error("action requires user", "id: #{action['id']};")
+ @result.success = false
+ return @result
+ end
+
ActiveRecord::Base.transaction do
self.send(action['type'].to_sym)
end
@@ -76,7 +90,6 @@ class CustomWizard::Action
end
def send_message
-
if action['required'].present?
required = CustomWizard::Mapper.new(
inputs: action['required'],
@@ -123,13 +136,14 @@ class CustomWizard::Action
params[:archetype] = Archetype.private_message
- creator = PostCreator.new(user, params)
+ poster = user || Discourse.system_user
+ creator = PostCreator.new(poster, params)
post = creator.create
if creator.errors.present?
messages = creator.errors.full_messages.join(" ")
log_error("failed to create message", messages)
- elsif action['skip_redirect'].blank?
+ elsif user && action['skip_redirect'].blank?
@submission.redirect_on_complete = post.topic.url
end
@@ -809,10 +823,12 @@ class CustomWizard::Action
end
def save_log
+ username = user ? user.username : @wizard.actor_id
+
CustomWizard::Log.create(
@wizard.id,
action['type'],
- user.username,
+ username,
@log.join('; ')
)
end
diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb
index 3b12ad27..0d0b689d 100644
--- a/lib/custom_wizard/builder.rb
+++ b/lib/custom_wizard/builder.rb
@@ -2,10 +2,10 @@
class CustomWizard::Builder
attr_accessor :wizard, :updater, :template
- def initialize(wizard_id, user = nil)
+ def initialize(wizard_id, user = nil, guest_id = nil)
@template = CustomWizard::Template.create(wizard_id)
return nil if @template.nil?
- @wizard = CustomWizard::Wizard.new(template.data, user)
+ @wizard = CustomWizard::Wizard.new(template.data, user, guest_id)
end
def self.sorted_handlers
@@ -182,7 +182,7 @@ class CustomWizard::Builder
if field_template['description'].present?
params[:description] = mapper.interpolate(
field_template['description'],
- user: true,
+ user: @wizard.user,
value: true,
wizard: true,
template: true
@@ -192,7 +192,7 @@ class CustomWizard::Builder
if field_template['preview_template'].present?
preview_template = mapper.interpolate(
field_template['preview_template'],
- user: true,
+ user: @wizard.user,
value: true,
wizard: true,
template: true
@@ -204,7 +204,7 @@ class CustomWizard::Builder
if field_template['placeholder'].present?
params[:placeholder] = mapper.interpolate(
field_template['placeholder'],
- user: true,
+ user: @wizard.user,
value: true,
wizard: true,
template: true
@@ -248,7 +248,7 @@ class CustomWizard::Builder
if step_template['description']
step.description = mapper.interpolate(
step_template['description'],
- user: true,
+ user: @wizard.user,
value: true,
wizard: true,
template: true
diff --git a/lib/custom_wizard/extensions/discourse_tagging.rb b/lib/custom_wizard/extensions/discourse_tagging.rb
index 3c81bbb3..701158bf 100644
--- a/lib/custom_wizard/extensions/discourse_tagging.rb
+++ b/lib/custom_wizard/extensions/discourse_tagging.rb
@@ -1,10 +1,9 @@
# frozen_string_literal: true
-require 'request_store'
module CustomWizardDiscourseTagging
def filter_allowed_tags(guardian, opts = {})
- if tag_groups = ::RequestStore.store[:tag_groups]
- tag_group_array = tag_groups.split(",")
+ if opts[:for_input].respond_to?(:dig) && (groups = opts.dig(:for_input, :groups)).present?
+ tag_group_array = groups.split(",")
filtered_tags = TagGroup.includes(:tags).where(name: tag_group_array).map do |tag_group|
tag_group.tags.pluck(:name)
end.flatten
diff --git a/lib/custom_wizard/extensions/tags_controller.rb b/lib/custom_wizard/extensions/tags_controller.rb
deleted file mode 100644
index 0ddacb5f..00000000
--- a/lib/custom_wizard/extensions/tags_controller.rb
+++ /dev/null
@@ -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
diff --git a/lib/custom_wizard/field.rb b/lib/custom_wizard/field.rb
index 6215fc8c..ec85ff3a 100644
--- a/lib/custom_wizard/field.rb
+++ b/lib/custom_wizard/field.rb
@@ -29,6 +29,11 @@ class CustomWizard::Field
attr_accessor :index,
:step
+ REQUIRES_USER = %w[
+ composer
+ upload
+ ]
+
def initialize(attrs)
@raw = attrs || {}
@id = attrs[:id]
diff --git a/lib/custom_wizard/mapper.rb b/lib/custom_wizard/mapper.rb
index b677a710..9d26c82e 100644
--- a/lib/custom_wizard/mapper.rb
+++ b/lib/custom_wizard/mapper.rb
@@ -203,6 +203,8 @@ class CustomWizard::Mapper
end
def map_user_field(value)
+ return nil unless user
+
if value.include?(User::USER_FIELD_PREFIX)
user.custom_fields[value]
elsif PROFILE_FIELDS.include?(value)
@@ -229,7 +231,7 @@ class CustomWizard::Mapper
def interpolate(string, opts = { user: true, wizard: true, value: true, template: false })
return string if string.blank? || string.frozen?
- if opts[:user]
+ if opts[:user] && @user.present?
string.gsub!(/u\{(.*?)\}/) { |match| map_user_field($1) || '' }
end
@@ -253,7 +255,7 @@ class CustomWizard::Mapper
end
end
- if opts[:template] && CustomWizard::Subscription.subscribed?
+ if opts[:template] #&& CustomWizard::Subscription.subscribed?
template = Liquid::Template.parse(string)
string = template.render(data)
end
@@ -282,4 +284,8 @@ class CustomWizard::Mapper
user.avatar_template_url.gsub("{size}", parts.last)
end
end
+
+ def self.mapped_value?(value)
+ value.is_a?(Array) && value.all? { |v| v.is_a?(Hash) && v.key?("type") }
+ end
end
diff --git a/lib/custom_wizard/step_updater.rb b/lib/custom_wizard/step_updater.rb
index ab86f3fa..511001c2 100644
--- a/lib/custom_wizard/step_updater.rb
+++ b/lib/custom_wizard/step_updater.rb
@@ -5,8 +5,7 @@ class CustomWizard::StepUpdater
attr_accessor :refresh_required, :result
attr_reader :step, :submission
- def initialize(current_user, wizard, step, submission)
- @current_user = current_user
+ def initialize(wizard, step, submission)
@wizard = wizard
@step = step
@refresh_required = false
@@ -22,9 +21,9 @@ class CustomWizard::StepUpdater
@step.updater.call(self)
- UserHistory.create(
- action: UserHistory.actions[:custom_wizard_step],
- acting_user_id: @current_user.id,
+ CustomWizard::UserHistory.create(
+ action: CustomWizard::UserHistory.actions[:step],
+ actor_id: @wizard.actor_id,
context: @wizard.id,
subject: @step.id
)
diff --git a/lib/custom_wizard/submission.rb b/lib/custom_wizard/submission.rb
index a52172e3..fc10cf31 100644
--- a/lib/custom_wizard/submission.rb
+++ b/lib/custom_wizard/submission.rb
@@ -7,8 +7,6 @@ class CustomWizard::Submission
META ||= %w(updated_at submitted_at route_to redirect_on_complete redirect_to)
attr_reader :id,
- :user,
- :user_id,
:wizard
attr_accessor :fields,
@@ -18,15 +16,8 @@ class CustomWizard::Submission
class_eval { attr_accessor attr }
end
- def initialize(wizard, data = {}, user_id = nil)
+ def initialize(wizard, data = {})
@wizard = wizard
- @user_id = user_id
-
- if user_id
- @user = User.find_by(id: user_id)
- else
- @user = wizard.user
- end
data = (data || {}).with_indifferent_access
@id = data['id'] || SecureRandom.hex(12)
@@ -44,13 +35,13 @@ class CustomWizard::Submission
return nil unless wizard.save_submissions
validate
- submission_list = self.class.list(wizard, user_id: user.id)
+ submission_list = self.class.list(wizard)
submissions = submission_list.submissions.select { |submission| submission.id != self.id }
self.updated_at = Time.now.iso8601
submissions.push(self)
submission_data = submissions.map { |submission| data_to_save(submission) }
- PluginStore.set("#{wizard.id}_#{KEY}", user.id, submission_data)
+ PluginStore.set("#{wizard.id}_#{KEY}", wizard.actor_id, submission_data)
end
def validate
@@ -93,25 +84,21 @@ class CustomWizard::Submission
data
end
- def self.get(wizard, user_id)
- data = PluginStore.get("#{wizard.id}_#{KEY}", user_id).last
- new(wizard, data, user_id)
+ def self.get(wizard)
+ data = PluginStore.get("#{wizard.id}_#{KEY}", wizard.actor_id).last
+ new(wizard, data)
end
def remove
if present?
- user_id = @user.id
- wizard_id = @wizard.id
- submission_id = @id
- data = PluginStore.get("#{wizard_id}_#{KEY}", user_id)
- data.delete_if { |sub| sub["id"] == submission_id }
- PluginStore.set("#{wizard_id}_#{KEY}", user_id, data)
+ data = PluginStore.get("#{@wizard.id}_#{KEY}", wizard.actor_id)
+ data.delete_if { |sub| sub["id"] == @id }
+ PluginStore.set("#{@wizard.id}_#{KEY}", wizard.actor_id, data)
end
end
def self.cleanup_incomplete_submissions(wizard)
- user_id = wizard.user.id
- all_submissions = list(wizard, user_id: user_id)
+ all_submissions = list(wizard)
sorted_submissions = all_submissions.submissions.sort_by do |submission|
zero_epoch_time = DateTime.strptime("0", '%s')
[
@@ -129,12 +116,12 @@ class CustomWizard::Submission
end
valid_data = valid_submissions.map { |submission| submission.data_to_save(submission) }
- PluginStore.set("#{wizard.id}_#{KEY}", user_id, valid_data)
+ PluginStore.set("#{wizard.id}_#{KEY}", wizard.actor_id, valid_data)
end
- def self.list(wizard, user_id: nil, order_by: nil, page: nil)
+ def self.list(wizard, order_by: nil, page: nil)
params = { plugin_name: "#{wizard.id}_#{KEY}" }
- params[:key] = user_id if user_id.present?
+ params[:key] = wizard.actor_id if wizard.actor_id
query = PluginStoreRow.where(params)
result = OpenStruct.new(submissions: [], total: nil)
@@ -142,7 +129,7 @@ class CustomWizard::Submission
query.each do |record|
if (submission_data = ::JSON.parse(record.value)).any?
submission_data.each do |data|
- result.submissions.push(new(wizard, data, record.key))
+ result.submissions.push(new(wizard, data))
end
end
end
diff --git a/lib/custom_wizard/subscription.rb b/lib/custom_wizard/subscription.rb
index c3c9803d..dfb75324 100644
--- a/lib/custom_wizard/subscription.rb
+++ b/lib/custom_wizard/subscription.rb
@@ -17,7 +17,7 @@ class CustomWizard::Subscription
none: [],
standard: ['*'],
business: ['*'],
- community: ['*']
+ community: ['*', "!#{CustomWizard::Wizard::GUEST_GROUP_ID}"]
},
restart_on_revisit: {
none: [],
@@ -114,8 +114,15 @@ class CustomWizard::Subscription
## Subscription type does not support the attribute.
return false if values.blank?
+ ## Value is an exception for the subscription type
+ if (exceptions = get_exceptions(values)).any?
+ value = mapped_output(value) if CustomWizard::Mapper.mapped_value?(value)
+ value = [*value].map(&:to_s)
+ return false if (exceptions & value).length > 0
+ end
+
## Subscription type supports all values of the attribute.
- return true if values.first === "*"
+ return true if values.include?("*")
## Subscription type supports some values of the attributes.
values.include?(value)
@@ -192,4 +199,21 @@ class CustomWizard::Subscription
def self.includes?(feature, attribute, value)
new.includes?(feature, attribute, value)
end
+
+ protected
+
+ def get_exceptions(values)
+ values.reduce([]) do |result, value|
+ result << value.split("!").last if value.start_with?("!")
+ result
+ end
+ end
+
+ def mapped_output(value)
+ value.reduce([]) do |result, v|
+ ## We can only validate mapped assignment values at the moment
+ result << v["output"] if v.is_a?(Hash) && v["type"] === "assignment"
+ result
+ end.flatten
+ end
end
diff --git a/lib/custom_wizard/template.rb b/lib/custom_wizard/template.rb
index 4163a1f7..12a86bf6 100644
--- a/lib/custom_wizard/template.rb
+++ b/lib/custom_wizard/template.rb
@@ -23,7 +23,6 @@ class CustomWizard::Template
normalize_data
validate_data
prepare_data
-
return false if errors.any?
ActiveRecord::Base.transaction do
diff --git a/lib/custom_wizard/user_history.rb b/lib/custom_wizard/user_history.rb
new file mode 100644
index 00000000..1d5ee3e1
--- /dev/null
+++ b/lib/custom_wizard/user_history.rb
@@ -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
diff --git a/lib/custom_wizard/validators/template.rb b/lib/custom_wizard/validators/template.rb
index 079f9884..60652322 100644
--- a/lib/custom_wizard/validators/template.rb
+++ b/lib/custom_wizard/validators/template.rb
@@ -30,6 +30,7 @@ class CustomWizard::TemplateValidator
validate_subscription(field, :field)
check_required(field, :field)
validate_liquid_template(field, :field)
+ validate_guests(field, :field)
end
end
end
@@ -39,6 +40,7 @@ class CustomWizard::TemplateValidator
validate_subscription(action, :action)
check_required(action, :action)
validate_liquid_template(action, :action)
+ validate_guests(action, :action)
end
end
@@ -80,6 +82,21 @@ class CustomWizard::TemplateValidator
end
end
+ def validate_guests(object, type)
+ guests_permitted = @data[:permitted] && @data[:permitted].any? do |m|
+ m["output"].include?(CustomWizard::Wizard::GUEST_GROUP_ID)
+ end
+ return unless guests_permitted
+
+ if type === :action && CustomWizard::Action::REQUIRES_USER.include?(object[:type])
+ errors.add :base, I18n.t("wizard.validation.not_permitted_for_guests", object_id: object[:id])
+ end
+
+ if type === :field && CustomWizard::Field::REQUIRES_USER.include?(object[:type])
+ errors.add :base, I18n.t("wizard.validation.not_permitted_for_guests", object_id: object[:id])
+ end
+ end
+
def validate_after_signup
return unless ActiveRecord::Type::Boolean.new.cast(@data[:after_signup])
diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb
index 223aeaa5..4ed4037d 100644
--- a/lib/custom_wizard/wizard.rb
+++ b/lib/custom_wizard/wizard.rb
@@ -4,8 +4,6 @@ require_dependency 'wizard/field'
require_dependency 'wizard/step_updater'
require_dependency 'wizard/builder'
-UserHistory.actions[:custom_wizard_step] = 1000
-
class CustomWizard::Wizard
include ActiveModel::SerializerSupport
@@ -31,13 +29,22 @@ class CustomWizard::Wizard
:actions,
:action_ids,
:user,
+ :guest_id,
:submissions,
:template
attr_reader :all_step_ids
- def initialize(attrs = {}, user = nil)
- @user = user
+ GUEST_ID_PREFIX ||= "guest"
+ GUEST_GROUP_ID = -1
+
+ def initialize(attrs = {}, user = nil, guest_id = nil)
+ if user
+ @user = user
+ elsif guest_id
+ @guest_id = guest_id
+ end
+
attrs = attrs.with_indifferent_access
@id = attrs['id']
@@ -81,6 +88,10 @@ class CustomWizard::Wizard
@template = attrs
end
+ def actor_id
+ user ? user.id : guest_id
+ end
+
def cast_bool(val)
val.nil? ? false : ActiveRecord::Type::Boolean.new.cast(val)
end
@@ -141,17 +152,16 @@ class CustomWizard::Wizard
end
def last_completed_step_id
- if user && unfinished? && last_completed_step = ::UserHistory.where(
- acting_user_id: user.id,
- action: ::UserHistory.actions[:custom_wizard_step],
- context: id,
- subject: all_step_ids
- ).order("created_at").last
+ return nil unless actor_id && unfinished?
- last_completed_step.subject
- else
- nil
- end
+ last_completed_step = CustomWizard::UserHistory.where(
+ actor_id: actor_id,
+ action: CustomWizard::UserHistory.actions[:step],
+ context: id,
+ subject: all_step_ids
+ ).order("created_at").last
+
+ last_completed_step&.subject
end
def find_step(step_id)
@@ -161,15 +171,15 @@ class CustomWizard::Wizard
def create_updater(step_id, submission)
step = @steps.find { |s| s.id == step_id }
wizard = self
- CustomWizard::StepUpdater.new(user, wizard, step, submission)
+ CustomWizard::StepUpdater.new(wizard, step, submission)
end
def unfinished?
- return nil if !user
+ return nil unless actor_id
- most_recent = ::UserHistory.where(
- acting_user_id: user.id,
- action: ::UserHistory.actions[:custom_wizard_step],
+ most_recent = CustomWizard::UserHistory.where(
+ actor_id: actor_id,
+ action: CustomWizard::UserHistory.actions[:step],
context: id,
).distinct.order('updated_at DESC').first
@@ -183,11 +193,11 @@ class CustomWizard::Wizard
end
def completed?
- return nil if !user
+ return nil unless actor_id
- history = ::UserHistory.where(
- acting_user_id: user.id,
- action: ::UserHistory.actions[:custom_wizard_step],
+ history = CustomWizard::UserHistory.where(
+ actor_id: actor_id,
+ action: CustomWizard::UserHistory.actions[:step],
context: id
)
@@ -200,8 +210,9 @@ class CustomWizard::Wizard
end
def permitted?
- return false unless user
- return true if user.admin? || permitted.blank?
+ return nil unless actor_id
+ return true if user && (user.admin? || permitted.blank?)
+ return false if !user && permitted.blank?
mapper = CustomWizard::Mapper.new(
inputs: permitted,
@@ -215,27 +226,32 @@ class CustomWizard::Wizard
return true if mapper.blank?
mapper.all? do |m|
- if m[:type] === 'assignment'
- [*m[:result]].include?(Group::AUTO_GROUPS[:everyone]) ||
- GroupUser.exists?(group_id: m[:result], user_id: user.id)
- elsif m[:type] === 'validation'
- m[:result]
+ if !user
+ m[:type] === 'assignment' && [*m[:result]].include?(GUEST_GROUP_ID)
else
- true
+ if m[:type] === 'assignment'
+ [*m[:result]].include?(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
def can_access?
- return false unless user
- return true if user.admin
- permitted? && (multiple_submissions || !completed?)
+ permitted? && (user&.admin? || (multiple_submissions || !completed?))
end
def reset
- ::UserHistory.create(
- action: ::UserHistory.actions[:custom_wizard_step],
- acting_user_id: user.id,
+ return nil unless actor_id
+
+ CustomWizard::UserHistory.create(
+ action: CustomWizard::UserHistory.actions[:step],
+ actor_id: actor_id,
context: id,
subject: "reset"
)
@@ -263,8 +279,7 @@ class CustomWizard::Wizard
end
def submissions
- return nil unless user.present?
- @submissions ||= CustomWizard::Submission.list(self, user_id: user.id).submissions
+ @submissions ||= CustomWizard::Submission.list(self).submissions
end
def current_submission
@@ -300,15 +315,17 @@ class CustomWizard::Wizard
end
def remove_user_redirect
+ return unless user.present?
+
if id == user.redirect_to_wizard
user.custom_fields.delete('redirect_to_wizard')
user.save_custom_fields(true)
end
end
- def self.create(wizard_id, user = nil)
+ def self.create(wizard_id, user = nil, guest_id = nil)
if template = CustomWizard::Template.find(wizard_id)
- new(template.to_h, user)
+ new(template.to_h, user, guest_id)
else
false
end
@@ -319,7 +336,7 @@ class CustomWizard::Wizard
CustomWizard::Template.list(**template_opts).reduce([]) do |result, template|
wizard = new(template, user)
- result.push(wizard) if wizard.can_access? && (
+ result.push(wizard) if wizard.permitted? && (
!not_completed || !wizard.completed?
)
result
@@ -380,4 +397,8 @@ class CustomWizard::Wizard
false
end
end
+
+ def self.generate_guest_id
+ "#{self::GUEST_ID_PREFIX}_#{SecureRandom.hex(12)}"
+ end
end
diff --git a/plugin.rb b/plugin.rb
index 4c0ce987..41d9270d 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -1,7 +1,11 @@
# frozen_string_literal: true
# name: discourse-custom-wizard
# about: Forms for Discourse. Better onboarding, structured posting, data enrichment, automated actions and much more.
+<<<<<<< HEAD
# version: 2.1.5
+=======
+# version: 2.2.9
+>>>>>>> main
# authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George, Kaitlin Maddever
# url: https://github.com/paviliondev/discourse-custom-wizard
# contact_emails: development@pavilion.tech
@@ -41,6 +45,7 @@ after_initialize do
../app/controllers/custom_wizard/admin/logs.rb
../app/controllers/custom_wizard/admin/manager.rb
../app/controllers/custom_wizard/admin/custom_fields.rb
+ ../app/controllers/custom_wizard/wizard_client.rb
../app/controllers/custom_wizard/wizard.rb
../app/controllers/custom_wizard/steps.rb
../app/controllers/custom_wizard/realtime_validations.rb
@@ -65,6 +70,7 @@ after_initialize do
../lib/custom_wizard/subscription.rb
../lib/custom_wizard/template.rb
../lib/custom_wizard/wizard.rb
+ ../lib/custom_wizard/user_history.rb
../lib/custom_wizard/api/api.rb
../lib/custom_wizard/api/authorization.rb
../lib/custom_wizard/api/endpoint.rb
@@ -88,7 +94,6 @@ after_initialize do
../lib/custom_wizard/extensions/extra_locales_controller.rb
../lib/custom_wizard/extensions/invites_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/custom_field/preloader.rb
../lib/custom_wizard/extensions/custom_field/serializer.rb
@@ -230,7 +235,6 @@ after_initialize do
end
reloadable_patch do |plugin|
- ::TagsController.prepend CustomWizardTagsController
::DiscourseTagging.singleton_class.prepend CustomWizardDiscourseTagging
end
diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb
index 07c1084f..79c64520 100644
--- a/spec/components/custom_wizard/action_spec.rb
+++ b/spec/components/custom_wizard/action_spec.rb
@@ -18,6 +18,7 @@ describe CustomWizard::Action do
let(:api_test_endpoint) { get_wizard_fixture("endpoints/test_endpoint") }
let(:api_test_endpoint_body) { get_wizard_fixture("endpoints/test_endpoint_body") }
let(:api_test_no_authorization) { get_wizard_fixture("api/no_authorization") }
+ let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
def update_template(template)
CustomWizard::Template.save(template, skip_jobs: true)
@@ -78,8 +79,8 @@ describe CustomWizard::Action do
updater.update
expect(updater.success?).to eq(true)
- expect(UserHistory.where(
- acting_user_id: user.id,
+ expect(CustomWizard::UserHistory.where(
+ actor_id: user.id,
context: "super_mega_fun_wizard",
subject: "step_3"
).exists?).to eq(true)
@@ -301,6 +302,28 @@ describe CustomWizard::Action do
expect(topic.first.allowed_groups.map(&:name)).to include('cool_group', 'cool_group_1')
expect(post.exists?).to eq(true)
end
+
+ it "send_message works with guests are permitted" do
+ wizard_template["permitted"] = guests_permitted["permitted"]
+ wizard_template.delete("actions")
+ wizard_template['actions'] = [send_message]
+ update_template(wizard_template)
+
+ User.create(username: 'angus1', email: "angus1@email.com")
+
+ wizard = CustomWizard::Builder.new(wizard_template["id"], nil, CustomWizard::Wizard.generate_guest_id).build
+ wizard.create_updater(wizard.steps[0].id, {}).update
+ updater = wizard.create_updater(wizard.steps[1].id, {})
+ updater.update
+
+ topic = Topic.where(archetype: Archetype.private_message, title: "Message title")
+ post = Post.where(topic_id: topic.pluck(:id))
+
+ expect(topic.exists?).to eq(true)
+ expect(topic.first.topic_allowed_users.first.user.username).to eq('angus1')
+ expect(topic.first.topic_allowed_users.second.user.username).to eq(Discourse.system_user.username)
+ expect(post.exists?).to eq(true)
+ end
end
context "business subscription actions" do
diff --git a/spec/components/custom_wizard/builder_spec.rb b/spec/components/custom_wizard/builder_spec.rb
index ebcc355b..1e55b203 100644
--- a/spec/components/custom_wizard/builder_spec.rb
+++ b/spec/components/custom_wizard/builder_spec.rb
@@ -80,14 +80,11 @@ describe CustomWizard::Builder do
it 'returns no steps if user has completed it' do
@template[:steps].each do |step|
- UserHistory.create!(
- {
- action: UserHistory.actions[:custom_wizard_step],
- acting_user_id: user.id,
- context: @template[:id]
- }.merge(
- subject: step[:id]
- )
+ CustomWizard::UserHistory.create!(
+ action: CustomWizard::UserHistory.actions[:step],
+ actor_id: user.id,
+ context: @template[:id],
+ subject: step[:id]
)
end
diff --git a/spec/components/custom_wizard/mapper_spec.rb b/spec/components/custom_wizard/mapper_spec.rb
index 56778a07..f460cc01 100644
--- a/spec/components/custom_wizard/mapper_spec.rb
+++ b/spec/components/custom_wizard/mapper_spec.rb
@@ -373,7 +373,7 @@ describe CustomWizard::Mapper do
expect(result).to eq(template_params["step_1_field_1"])
end
- it "requires a subscription" do
+ it "does not require a subscription" do
template = '{{ "w{step_1_field_1}" | size }}'
mapper = create_template_mapper(template_params, user1)
result = mapper.interpolate(
@@ -383,7 +383,7 @@ describe CustomWizard::Mapper do
wizard: true,
value: true
)
- expect(result).to eq("{{ \"#{template_params["step_1_field_1"]}\" | size }}")
+ expect(result).to eq("5")
end
context "with a subscription" do
diff --git a/spec/components/custom_wizard/submission_spec.rb b/spec/components/custom_wizard/submission_spec.rb
index ff9df88a..d0e0c986 100644
--- a/spec/components/custom_wizard/submission_spec.rb
+++ b/spec/components/custom_wizard/submission_spec.rb
@@ -4,6 +4,7 @@ describe CustomWizard::Submission do
fab!(:user) { Fabricate(:user) }
fab!(:user2) { Fabricate(:user) }
let(:template_json) { get_wizard_fixture("wizard") }
+ let(:guest_id) { CustomWizard::Wizard.generate_guest_id }
before do
CustomWizard::Template.save(template_json, skip_jobs: true)
@@ -13,10 +14,20 @@ describe CustomWizard::Submission do
it "saves a user's submission" do
expect(
- described_class.get(@wizard, user.id).fields["step_1_field_1"]
+ described_class.get(@wizard).fields["step_1_field_1"]
).to eq("I am user submission")
end
+ it "saves a guest's submission" do
+ CustomWizard::Template.save(template_json, skip_jobs: true)
+ @wizard = CustomWizard::Wizard.create(template_json["id"], nil, guest_id)
+ described_class.new(@wizard, step_1_field_1: "I am guest submission").save
+
+ expect(
+ described_class.get(@wizard).fields["step_1_field_1"]
+ ).to eq("I am guest submission")
+ end
+
describe "#list" do
before do
freeze_time Time.now
@@ -37,14 +48,17 @@ describe CustomWizard::Submission do
end
it "list submissions by wizard" do
+ @wizard.user = nil
expect(described_class.list(@wizard).total).to eq(@count + 2)
end
it "list submissions by wizard and user" do
- expect(described_class.list(@wizard, user_id: user.id).total).to eq(@count + 1)
+ @wizard.user = user
+ expect(described_class.list(@wizard).total).to eq(@count + 1)
end
it "paginates submission lists" do
+ @wizard.user = nil
expect(described_class.list(@wizard, page: 1).submissions.size).to eq((@count + 2) - CustomWizard::Submission::PAGE_LIMIT)
end
@@ -59,7 +73,7 @@ describe CustomWizard::Submission do
described_class.new(@wizard, step_1_field_1: "I am the second submission").save
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
builder.build
- submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions
+ submissions = described_class.list(@wizard).submissions
expect(submissions.length).to eq(1)
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the second submission")
@@ -75,7 +89,7 @@ describe CustomWizard::Submission do
PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, sub_data)
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
builder.build
- submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions
+ submissions = described_class.list(@wizard).submissions
expect(submissions.length).to eq(1)
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the second submission")
@@ -92,7 +106,7 @@ describe CustomWizard::Submission do
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
builder.build
- submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions
+ submissions = described_class.list(@wizard).submissions
expect(submissions.length).to eq(1)
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the third submission")
diff --git a/spec/components/custom_wizard/subscription_spec.rb b/spec/components/custom_wizard/subscription_spec.rb
index 5f06397b..2ac191e1 100644
--- a/spec/components/custom_wizard/subscription_spec.rb
+++ b/spec/components/custom_wizard/subscription_spec.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
describe CustomWizard::Subscription do
+ let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
+
def undefine_client_classes
Object.send(:remove_const, :SubscriptionClient) if Object.constants.include?(:SubscriptionClient)
Object.send(:remove_const, :SubscriptionClientSubscription) if Object.constants.include?(:SubscriptionClientSubscription)
@@ -40,7 +42,7 @@ describe CustomWizard::Subscription do
expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true)
end
- it "ubscriber features are not included" do
+ it "subscriber features are not included" do
expect(described_class.includes?(:wizard, :permitted, {})).to eq(false)
end
end
@@ -69,6 +71,16 @@ describe CustomWizard::Subscription do
end
end
+ context "with a subscription" do
+ it "handles mapped values" do
+ SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID)
+ expect(described_class.includes?(:wizard, :permitted, guests_permitted["permitted"])).to eq(true)
+
+ SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::COMMUNITY_PRODUCT_ID)
+ expect(described_class.includes?(:wizard, :permitted, guests_permitted["permitted"])).to eq(false)
+ end
+ end
+
context "with standard subscription" do
before do
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID)
diff --git a/spec/components/custom_wizard/template_validator_spec.rb b/spec/components/custom_wizard/template_validator_spec.rb
index b149706f..b9b257e3 100644
--- a/spec/components/custom_wizard/template_validator_spec.rb
+++ b/spec/components/custom_wizard/template_validator_spec.rb
@@ -7,6 +7,8 @@ describe CustomWizard::TemplateValidator do
let(:user_condition) { get_wizard_fixture("condition/user_condition") }
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
let(:composer_preview) { get_wizard_fixture("field/composer_preview") }
+ let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
+ let(:upload_field) { get_wizard_fixture("field/upload") }
let(:valid_liquid_template) {
<<-LIQUID.strip
@@ -146,6 +148,20 @@ describe CustomWizard::TemplateValidator do
).to eq(true)
end
+ it "validates user-only features" do
+ template[:permitted] = guests_permitted['permitted']
+ template[:steps][0][:fields] << upload_field
+ validator = CustomWizard::TemplateValidator.new(template)
+ expect(validator.perform).to eq(false)
+ errors = validator.errors.to_a
+ expect(errors).to include(
+ I18n.t("wizard.validation.not_permitted_for_guests", object_id: "action_1")
+ )
+ expect(errors).to include(
+ I18n.t("wizard.validation.not_permitted_for_guests", object_id: "step_2_field_7")
+ )
+ end
+
it "validates step attributes" do
template[:steps][0][:condition] = user_condition['condition']
expect(
diff --git a/spec/components/custom_wizard/wizard_spec.rb b/spec/components/custom_wizard/wizard_spec.rb
index 8268849c..591eee8c 100644
--- a/spec/components/custom_wizard/wizard_spec.rb
+++ b/spec/components/custom_wizard/wizard_spec.rb
@@ -6,11 +6,14 @@ describe CustomWizard::Wizard do
fab!(:admin_user) { Fabricate(:user, admin: true) }
let(:template_json) { get_wizard_fixture("wizard") }
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
+ let(:guests_permitted_json) { get_wizard_fixture("wizard/guests_permitted") }
before do
Group.refresh_automatic_group!(:trust_level_3)
@permitted_template = template_json.dup
@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)
end
@@ -21,10 +24,10 @@ describe CustomWizard::Wizard do
@wizard.update!
end
- def progress_step(step_id, acting_user: user, wizard: @wizard)
- UserHistory.create(
- action: UserHistory.actions[:custom_wizard_step],
- acting_user_id: acting_user.id,
+ def progress_step(step_id, actor_id: user.id, wizard: @wizard)
+ CustomWizard::UserHistory.create(
+ action: CustomWizard::UserHistory.actions[:step],
+ actor_id: actor_id,
context: wizard.id,
subject: step_id
)
@@ -158,9 +161,9 @@ describe CustomWizard::Wizard do
it "lets a permitted user access a complete wizard with multiple submissions" do
append_steps
- progress_step("step_1", acting_user: trusted_user)
- progress_step("step_2", acting_user: trusted_user)
- progress_step("step_3", acting_user: trusted_user)
+ progress_step("step_1", actor_id: trusted_user.id)
+ progress_step("step_2", actor_id: trusted_user.id)
+ progress_step("step_3", actor_id: trusted_user.id)
@permitted_template["multiple_submissions"] = true
@@ -172,9 +175,9 @@ describe CustomWizard::Wizard do
it "does not let an unpermitted user access a complete wizard without multiple submissions" do
append_steps
- progress_step("step_1", acting_user: trusted_user)
- progress_step("step_2", acting_user: trusted_user)
- progress_step("step_3", acting_user: trusted_user)
+ progress_step("step_1", actor_id: trusted_user.id)
+ progress_step("step_2", actor_id: trusted_user.id)
+ progress_step("step_3", actor_id: trusted_user.id)
@permitted_template['multiple_submissions'] = false
@@ -200,6 +203,30 @@ describe CustomWizard::Wizard do
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
before do
CustomWizard::Submission.new(@wizard, step_1_field_1: "I am a user submission").save
diff --git a/spec/extensions/discourse_tagging_spec.rb b/spec/extensions/discourse_tagging_spec.rb
new file mode 100644
index 00000000..14adaf5b
--- /dev/null
+++ b/spec/extensions/discourse_tagging_spec.rb
@@ -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
diff --git a/spec/extensions/tags_controller_spec.rb b/spec/extensions/tags_controller_spec.rb
deleted file mode 100644
index b3c1ccc8..00000000
--- a/spec/extensions/tags_controller_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/fixtures/actions/route_to.json b/spec/fixtures/actions/route_to.json
new file mode 100644
index 00000000..442b1556
--- /dev/null
+++ b/spec/fixtures/actions/route_to.json
@@ -0,0 +1,12 @@
+{
+ "id": "route_to",
+ "type": "route_to",
+ "url": [
+ {
+ "type": "assignment",
+ "output": "https://google.com",
+ "output_type": "text",
+ "output_connector": "set"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/spec/fixtures/field/upload.json b/spec/fixtures/field/upload.json
new file mode 100644
index 00000000..ebf6c21d
--- /dev/null
+++ b/spec/fixtures/field/upload.json
@@ -0,0 +1,6 @@
+{
+ "id": "step_2_field_7",
+ "label": "Upload",
+ "type": "upload",
+ "file_types": ".jpg,.jpeg,.png"
+}
\ No newline at end of file
diff --git a/spec/fixtures/sprockets/require_tree_discourse_empty.js b/spec/fixtures/sprockets/require_tree_discourse_empty.js
index 953c5ec4..4f034d13 100644
--- a/spec/fixtures/sprockets/require_tree_discourse_empty.js
+++ b/spec/fixtures/sprockets/require_tree_discourse_empty.js
@@ -1 +1 @@
-//= require_tree_discourse
\ No newline at end of file
+//= require_tree_discourse
diff --git a/spec/fixtures/sprockets/require_tree_discourse_non_existant.js b/spec/fixtures/sprockets/require_tree_discourse_non_existant.js
index d9b2be76..fc4752e5 100644
--- a/spec/fixtures/sprockets/require_tree_discourse_non_existant.js
+++ b/spec/fixtures/sprockets/require_tree_discourse_non_existant.js
@@ -1 +1 @@
-//= require_tree_discourse dummy_path
\ No newline at end of file
+//= require_tree_discourse dummy_path
diff --git a/spec/fixtures/sprockets/require_tree_discourse_test.js b/spec/fixtures/sprockets/require_tree_discourse_test.js
index a86aa0d7..56451213 100644
--- a/spec/fixtures/sprockets/require_tree_discourse_test.js
+++ b/spec/fixtures/sprockets/require_tree_discourse_test.js
@@ -1 +1 @@
-//= require_tree_discourse sptest
\ No newline at end of file
+//= require_tree_discourse sptest
diff --git a/spec/fixtures/wizard.json b/spec/fixtures/wizard.json
index de5e636e..5868001e 100644
--- a/spec/fixtures/wizard.json
+++ b/spec/fixtures/wizard.json
@@ -74,12 +74,6 @@
"id": "step_2_field_5",
"label": "Checkbox",
"type": "checkbox"
- },
- {
- "id": "step_2_field_7",
- "label": "Upload",
- "type": "upload",
- "file_types": ".jpg,.jpeg,.png"
}
],
"description": "Because I couldn't think of another name for this step :)"
diff --git a/spec/fixtures/wizard/guests_permitted.json b/spec/fixtures/wizard/guests_permitted.json
new file mode 100644
index 00000000..3a332f31
--- /dev/null
+++ b/spec/fixtures/wizard/guests_permitted.json
@@ -0,0 +1,12 @@
+{
+ "permitted": [
+ {
+ "type": "assignment",
+ "output_type": "group",
+ "output_connector": "set",
+ "output": [
+ -1
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/spec/requests/custom_wizard/admin/manager_controller_spec.rb b/spec/requests/custom_wizard/admin/manager_controller_spec.rb
index 30c1aa3a..608b61fb 100644
--- a/spec/requests/custom_wizard/admin/manager_controller_spec.rb
+++ b/spec/requests/custom_wizard/admin/manager_controller_spec.rb
@@ -13,7 +13,7 @@ describe CustomWizard::AdminManagerController do
template_3["id"] = 'super_mega_fun_wizard_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')
File.write(@tmp_file_path, @template_array.to_json)
end
diff --git a/spec/requests/custom_wizard/steps_controller_spec.rb b/spec/requests/custom_wizard/steps_controller_spec.rb
index e05ba917..4d8b96eb 100644
--- a/spec/requests/custom_wizard/steps_controller_spec.rb
+++ b/spec/requests/custom_wizard/steps_controller_spec.rb
@@ -7,192 +7,209 @@ describe CustomWizard::StepsController do
let(:wizard_field_condition_template) { get_wizard_fixture("condition/wizard_field_condition") }
let(:user_condition_template) { get_wizard_fixture("condition/user_condition") }
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
+ let(:route_to_template) { get_wizard_fixture("actions/route_to") }
+ let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
before do
CustomWizard::Template.save(wizard_template, skip_jobs: true)
- sign_in(user)
end
- it 'performs a step update' do
- put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
- fields: {
- step_1_field_1: "Text input"
+ def guest_template
+ temp = wizard_template.dup
+ temp["permitted"] = guests_permitted["permitted"]
+ temp.delete("actions")
+ temp["actions"] = [route_to_template]
+ temp
+ end
+
+ context "with guest" do
+ it "does not perform a step update" do
+ put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
+ fields: {
+ step_1_field_1: "Text input"
+ }
}
- }
- expect(response.status).to eq(200)
- expect(response.parsed_body['wizard']['start']).to eq("step_2")
-
- wizard_id = response.parsed_body['wizard']['id']
- wizard = CustomWizard::Wizard.create(wizard_id, user)
- expect(wizard.current_submission.fields['step_1_field_1']).to eq("Text input")
- end
-
- context "raises an error" do
- it "when the wizard doesnt exist" do
- put '/w/not-super-mega-fun-wizard/steps/step_1.json'
- expect(response.status).to eq(400)
- end
-
- it "when the user cant access the wizard" do
- enable_subscription("standard")
- new_template = wizard_template.dup
- new_template["permitted"] = permitted_json["permitted"]
- CustomWizard::Template.save(new_template, skip_jobs: true)
-
- put '/w/super-mega-fun-wizard/steps/step_1.json'
expect(response.status).to eq(403)
end
- it "when the step doesnt exist" do
- put '/w/super-mega-fun-wizard/steps/step_10.json'
- expect(response.status).to eq(400)
+ context "with guests permitted" do
+ before do
+ enable_subscription("standard")
+ result = CustomWizard::Template.save(guest_template, skip_jobs: true)
+ end
+
+ it "performs a step update" do
+ put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
+ fields: {
+ step_1_field_1: "Text input"
+ }
+ }
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['wizard']['start']).to eq("step_2")
+
+ wizard_id = response.parsed_body['wizard']['id']
+ wizard = CustomWizard::Wizard.create(wizard_id, nil, cookies[:custom_wizard_guest_id])
+ expect(wizard.current_submission.fields['step_1_field_1']).to eq("Text input")
+ end
+
+ context "raises an error" do
+ it "when the wizard doesnt exist" do
+ put '/w/not-super-mega-fun-wizard/steps/step_1.json'
+ expect(response.status).to eq(400)
+ end
+
+ it "when the user cant access the wizard" do
+ enable_subscription("standard")
+ new_template = guest_template.dup
+ new_template["permitted"] = permitted_json["permitted"]
+ CustomWizard::Template.save(new_template, skip_jobs: true)
+
+ put '/w/super-mega-fun-wizard/steps/step_1.json'
+ expect(response.status).to eq(403)
+ end
+
+ it "when the step doesnt exist" do
+ put '/w/super-mega-fun-wizard/steps/step_10.json'
+ expect(response.status).to eq(400)
+ end
+ end
+
+ it "works if the step has no fields" do
+ put '/w/super-mega-fun-wizard/steps/step_1.json'
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['wizard']['start']).to eq("step_2")
+ end
+
+ it "returns an updated wizard when condition passes" do
+ new_template = guest_template.dup
+ new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
+ CustomWizard::Template.save(new_template, skip_jobs: true)
+
+ put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
+ fields: {
+ step_1_field_1: "Condition will pass"
+ }
+ }
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['wizard']['start']).to eq("step_2")
+ end
+
+ it "runs completion actions if guest has completed wizard" do
+ new_template = guest_template.dup
+
+ ## route_to action
+ new_template['actions'].last['run_after'] = 'wizard_completion'
+ CustomWizard::Template.save(new_template, skip_jobs: true)
+
+ put '/w/super-mega-fun-wizard/steps/step_1.json'
+ put '/w/super-mega-fun-wizard/steps/step_2.json'
+ put '/w/super-mega-fun-wizard/steps/step_3.json'
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['redirect_on_complete']).to eq("https://google.com")
+ end
end
end
- it "works if the step has no fields" do
- put '/w/super-mega-fun-wizard/steps/step_1.json'
- expect(response.status).to eq(200)
- expect(response.parsed_body['wizard']['start']).to eq("step_2")
- end
-
- it "returns an updated wizard when condition passes" do
- new_template = wizard_template.dup
- new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
- CustomWizard::Template.save(new_template, skip_jobs: true)
-
- put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
- fields: {
- step_1_field_1: "Condition will pass"
- }
- }
- expect(response.status).to eq(200)
- expect(response.parsed_body['wizard']['start']).to eq("step_2")
- end
-
- it "runs completion actions if user has completed wizard" do
- new_template = wizard_template.dup
-
- ## route_to action
- new_template['actions'].last['run_after'] = 'wizard_completion'
- CustomWizard::Template.save(new_template, skip_jobs: true)
-
- put '/w/super-mega-fun-wizard/steps/step_1.json'
- put '/w/super-mega-fun-wizard/steps/step_2.json'
- put '/w/super-mega-fun-wizard/steps/step_3.json'
- expect(response.status).to eq(200)
- expect(response.parsed_body['redirect_on_complete']).to eq("https://google.com")
- end
-
- it "saves results of completion actions if user has completed wizard" do
- new_template = wizard_template.dup
- new_template['actions'].first['run_after'] = 'wizard_completion'
- CustomWizard::Template.save(new_template, skip_jobs: true)
-
- put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
- fields: {
- step_1_field_1: "Topic title",
- step_1_field_2: "Topic post"
- }
- }
- put '/w/super-mega-fun-wizard/steps/step_2.json'
- put '/w/super-mega-fun-wizard/steps/step_3.json'
-
- wizard_id = response.parsed_body['wizard']['id']
- wizard = CustomWizard::Wizard.create(wizard_id, user)
-
- topic_id = wizard.submissions.first.fields[new_template['actions'].first['id']]
- topic = Topic.find(topic_id)
- expect(topic.present?).to eq(true)
- end
-
- it "returns a final step without conditions" do
- put '/w/super-mega-fun-wizard/steps/step_1.json'
- expect(response.status).to eq(200)
- expect(response.parsed_body['final']).to eq(false)
-
- put '/w/super-mega-fun-wizard/steps/step_2.json'
- expect(response.status).to eq(200)
- expect(response.parsed_body['final']).to eq(false)
-
- put '/w/super-mega-fun-wizard/steps/step_3.json'
- expect(response.status).to eq(200)
- expect(response.parsed_body['final']).to eq(true)
- end
-
- context "subscription" do
+ context "with user" do
before do
- enable_subscription("standard")
+ sign_in(user)
end
- it "raises an error when user cant see the step due to conditions" do
- sign_in(user2)
-
- new_wizard_template = wizard_template.dup
- new_wizard_template['steps'][0]['condition'] = user_condition_template['condition']
- CustomWizard::Template.save(new_wizard_template, skip_jobs: true)
-
- put '/w/super-mega-fun-wizard/steps/step_1.json'
- expect(response.status).to eq(403)
- end
-
- it "returns an updated wizard when condition doesnt pass" do
- new_template = wizard_template.dup
- new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
- CustomWizard::Template.save(new_template, skip_jobs: true)
-
+ it 'performs a step update' do
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
- step_1_field_1: "Condition wont pass"
+ step_1_field_1: "Text input"
}
}
expect(response.status).to eq(200)
- expect(response.parsed_body['wizard']['start']).to eq("step_3")
+ expect(response.parsed_body['wizard']['start']).to eq("step_2")
+
+ wizard_id = response.parsed_body['wizard']['id']
+ wizard = CustomWizard::Wizard.create(wizard_id, user)
+ expect(wizard.current_submission.fields['step_1_field_1']).to eq("Text input")
end
- it "returns the correct final step when the conditional final step and last step are the same" do
- new_template = wizard_template.dup
- new_template['steps'][0]['condition'] = user_condition_template['condition']
- new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
- CustomWizard::Template.save(new_template, skip_jobs: true)
+ context "raises an error" do
+ it "when the wizard doesnt exist" do
+ put '/w/not-super-mega-fun-wizard/steps/step_1.json'
+ expect(response.status).to eq(400)
+ end
+
+ it "when the user cant access the wizard" do
+ enable_subscription("standard")
+ new_template = wizard_template.dup
+ new_template["permitted"] = permitted_json["permitted"]
+ CustomWizard::Template.save(new_template, skip_jobs: true)
+
+ put '/w/super-mega-fun-wizard/steps/step_1.json'
+ expect(response.status).to eq(403)
+ end
+
+ it "when the step doesnt exist" do
+ put '/w/super-mega-fun-wizard/steps/step_10.json'
+ expect(response.status).to eq(400)
+ end
end
- it "raises an error when user cant see the step due to conditions" do
- sign_in(user2)
-
- new_wizard_template = wizard_template.dup
- new_wizard_template['steps'][0]['condition'] = user_condition_template['condition']
- CustomWizard::Template.save(new_wizard_template, skip_jobs: true)
-
+ it "works if the step has no fields" do
put '/w/super-mega-fun-wizard/steps/step_1.json'
- expect(response.status).to eq(403)
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['wizard']['start']).to eq("step_2")
end
- it "returns an updated wizard when condition doesnt pass" do
+ it "returns an updated wizard when condition passes" do
new_template = wizard_template.dup
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
CustomWizard::Template.save(new_template, skip_jobs: true)
- put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
- fields: {
- step_1_field_1: "Condition wont pass"
- }
- }
- expect(response.status).to eq(200)
- expect(response.parsed_body['wizard']['start']).to eq("step_3")
- end
-
- it "returns the correct final step when the conditional final step and last step are the same" do
- new_template = wizard_template.dup
- new_template['steps'][0]['condition'] = user_condition_template['condition']
- new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
- CustomWizard::Template.save(new_template, skip_jobs: true)
-
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
fields: {
step_1_field_1: "Condition will pass"
}
}
expect(response.status).to eq(200)
+ expect(response.parsed_body['wizard']['start']).to eq("step_2")
+ end
+
+ it "runs completion actions if user has completed wizard" do
+ new_template = wizard_template.dup
+
+ ## route_to action
+ new_template['actions'].last['run_after'] = 'wizard_completion'
+ CustomWizard::Template.save(new_template, skip_jobs: true)
+
+ put '/w/super-mega-fun-wizard/steps/step_1.json'
+ put '/w/super-mega-fun-wizard/steps/step_2.json'
+ put '/w/super-mega-fun-wizard/steps/step_3.json'
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['redirect_on_complete']).to eq("https://google.com")
+ end
+
+ it "saves results of completion actions if user has completed wizard" do
+ new_template = wizard_template.dup
+ new_template['actions'].first['run_after'] = 'wizard_completion'
+ CustomWizard::Template.save(new_template, skip_jobs: true)
+
+ put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
+ fields: {
+ step_1_field_1: "Topic title",
+ step_1_field_2: "Topic post"
+ }
+ }
+ put '/w/super-mega-fun-wizard/steps/step_2.json'
+ put '/w/super-mega-fun-wizard/steps/step_3.json'
+
+ wizard_id = response.parsed_body['wizard']['id']
+ wizard = CustomWizard::Wizard.create(wizard_id, user)
+
+ topic_id = wizard.submissions.first.fields[new_template['actions'].first['id']]
+ topic = Topic.find(topic_id)
+ expect(topic.present?).to eq(true)
+ end
+
+ it "returns a final step without conditions" do
+ put '/w/super-mega-fun-wizard/steps/step_1.json'
+ expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(false)
put '/w/super-mega-fun-wizard/steps/step_2.json'
@@ -204,66 +221,152 @@ describe CustomWizard::StepsController do
expect(response.parsed_body['final']).to eq(true)
end
- it "returns the correct final step when the conditional final step and last step are different" do
- new_template = wizard_template.dup
- new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
- CustomWizard::Template.save(new_template, skip_jobs: true)
+ context "subscription" do
+ before do
+ enable_subscription("standard")
+ end
- put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
- fields: {
- step_1_field_1: "Condition will not pass"
+ it "raises an error when user cant see the step due to conditions" do
+ sign_in(user2)
+
+ new_wizard_template = wizard_template.dup
+ new_wizard_template['steps'][0]['condition'] = user_condition_template['condition']
+ CustomWizard::Template.save(new_wizard_template, skip_jobs: true)
+
+ put '/w/super-mega-fun-wizard/steps/step_1.json'
+ expect(response.status).to eq(403)
+ end
+
+ it "returns an updated wizard when condition doesnt pass" do
+ new_template = wizard_template.dup
+ new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
+ CustomWizard::Template.save(new_template, skip_jobs: true)
+
+ put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
+ fields: {
+ step_1_field_1: "Condition wont pass"
+ }
}
- }
- expect(response.status).to eq(200)
- expect(response.parsed_body['final']).to eq(false)
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['wizard']['start']).to eq("step_3")
+ end
- put '/w/super-mega-fun-wizard/steps/step_2.json'
- expect(response.status).to eq(200)
- expect(response.parsed_body['final']).to eq(true)
- end
+ it "returns the correct final step when the conditional final step and last step are the same" do
+ new_template = wizard_template.dup
+ new_template['steps'][0]['condition'] = user_condition_template['condition']
+ new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
+ CustomWizard::Template.save(new_template, skip_jobs: true)
+ end
- it "returns the correct final step when the conditional final step is determined in the same action" do
- new_template = wizard_template.dup
- new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
- new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
- CustomWizard::Template.save(new_template, skip_jobs: true)
+ it "raises an error when user cant see the step due to conditions" do
+ sign_in(user2)
- put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
- fields: {
- step_1_field_1: "Condition will not pass"
+ new_wizard_template = wizard_template.dup
+ new_wizard_template['steps'][0]['condition'] = user_condition_template['condition']
+ CustomWizard::Template.save(new_wizard_template, skip_jobs: true)
+
+ put '/w/super-mega-fun-wizard/steps/step_1.json'
+ expect(response.status).to eq(403)
+ end
+
+ it "returns an updated wizard when condition doesnt pass" do
+ new_template = wizard_template.dup
+ new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
+ CustomWizard::Template.save(new_template, skip_jobs: true)
+
+ put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
+ fields: {
+ step_1_field_1: "Condition wont pass"
+ }
}
- }
- expect(response.status).to eq(200)
- expect(response.parsed_body['final']).to eq(true)
- end
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['wizard']['start']).to eq("step_3")
+ end
- it "excludes the non-included conditional fields from the submissions" do
- new_template = wizard_template.dup
- new_template['steps'][1]['fields'][0]['condition'] = wizard_field_condition_template['condition']
- CustomWizard::Template.save(new_template, skip_jobs: true)
+ it "returns the correct final step when the conditional final step and last step are the same" do
+ new_template = wizard_template.dup
+ new_template['steps'][0]['condition'] = user_condition_template['condition']
+ new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
+ CustomWizard::Template.save(new_template, skip_jobs: true)
- put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
- fields: {
- step_1_field_1: "Condition will pass"
+ put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
+ fields: {
+ step_1_field_1: "Condition will pass"
+ }
}
- }
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['final']).to eq(false)
- put '/w/super-mega-fun-wizard/steps/step_2.json', params: {
- fields: {
- step_2_field_1: "1995-04-23"
+ put '/w/super-mega-fun-wizard/steps/step_2.json'
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['final']).to eq(false)
+
+ put '/w/super-mega-fun-wizard/steps/step_3.json'
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['final']).to eq(true)
+ end
+
+ it "returns the correct final step when the conditional final step and last step are different" do
+ new_template = wizard_template.dup
+ new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
+ CustomWizard::Template.save(new_template, skip_jobs: true)
+
+ put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
+ fields: {
+ step_1_field_1: "Condition will not pass"
+ }
}
- }
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['final']).to eq(false)
- put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
- fields: {
- step_1_field_1: "Condition will not pass"
+ put '/w/super-mega-fun-wizard/steps/step_2.json'
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['final']).to eq(true)
+ end
+
+ it "returns the correct final step when the conditional final step is determined in the same action" do
+ new_template = wizard_template.dup
+ new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
+ new_template['steps'][2]['condition'] = wizard_field_condition_template['condition']
+ CustomWizard::Template.save(new_template, skip_jobs: true)
+
+ put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
+ fields: {
+ step_1_field_1: "Condition will not pass"
+ }
}
- }
+ expect(response.status).to eq(200)
+ expect(response.parsed_body['final']).to eq(true)
+ end
- wizard_id = response.parsed_body['wizard']['id']
- wizard = CustomWizard::Wizard.create(wizard_id, user)
- submission = wizard.current_submission
- expect(submission.fields.keys).not_to include("step_2_field_1")
+ it "excludes the non-included conditional fields from the submissions" do
+ new_template = wizard_template.dup
+ new_template['steps'][1]['fields'][0]['condition'] = wizard_field_condition_template['condition']
+ CustomWizard::Template.save(new_template, skip_jobs: true)
+
+ put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
+ fields: {
+ step_1_field_1: "Condition will pass"
+ }
+ }
+
+ put '/w/super-mega-fun-wizard/steps/step_2.json', params: {
+ fields: {
+ step_2_field_1: "1995-04-23"
+ }
+ }
+
+ put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
+ fields: {
+ step_1_field_1: "Condition will not pass"
+ }
+ }
+
+ wizard_id = response.parsed_body['wizard']['id']
+ wizard = CustomWizard::Wizard.create(wizard_id, user)
+ submission = wizard.current_submission
+ expect(submission.fields.keys).not_to include("step_2_field_1")
+ end
end
end
end
diff --git a/spec/requests/custom_wizard/wizard_controller_spec.rb b/spec/requests/custom_wizard/wizard_controller_spec.rb
index aa1f479b..93ec196b 100644
--- a/spec/requests/custom_wizard/wizard_controller_spec.rb
+++ b/spec/requests/custom_wizard/wizard_controller_spec.rb
@@ -8,7 +8,6 @@ describe CustomWizard::WizardController do
before do
CustomWizard::Template.save(wizard_template, skip_jobs: true)
@template = CustomWizard::Template.find("super_mega_fun_wizard")
- sign_in(user)
end
context 'plugin disabled' do
@@ -32,65 +31,70 @@ describe CustomWizard::WizardController do
expect(response.parsed_body["error"]).to eq("We couldn't find a wizard at that address.")
end
- context 'when user skips the wizard' do
-
- it 'skips a wizard if user is allowed to skip' do
- put '/w/super-mega-fun-wizard/skip.json'
- expect(response.status).to eq(200)
+ context "with user" do
+ before do
+ sign_in(user)
end
- it 'lets user skip if user cant access wizard' do
- enable_subscription("standard")
- @template["permitted"] = permitted_json["permitted"]
- CustomWizard::Template.save(@template, skip_jobs: true)
- put '/w/super-mega-fun-wizard/skip.json'
- expect(response.status).to eq(200)
- end
+ context 'when user skips' do
+ it 'skips a wizard if user is allowed to skip' do
+ put '/w/super-mega-fun-wizard/skip.json'
+ expect(response.status).to eq(200)
+ end
- it 'returns a no skip message if user is not allowed to skip' do
- enable_subscription("standard")
- @template['required'] = 'true'
- CustomWizard::Template.save(@template)
- put '/w/super-mega-fun-wizard/skip.json'
- expect(response.parsed_body['error']).to eq("Wizard can't be skipped")
- end
+ it 'lets user skip if user cant access wizard' do
+ enable_subscription("standard")
+ @template["permitted"] = permitted_json["permitted"]
+ CustomWizard::Template.save(@template, skip_jobs: true)
+ put '/w/super-mega-fun-wizard/skip.json'
+ expect(response.status).to eq(200)
+ end
- it 'skip response contains a redirect_to if in users submissions' do
- @wizard = CustomWizard::Wizard.create(@template["id"], user)
- CustomWizard::Submission.new(@wizard, redirect_to: "/t/2").save
- put '/w/super-mega-fun-wizard/skip.json'
- expect(response.parsed_body['redirect_to']).to eq('/t/2')
- end
+ it 'returns a no skip message if user is not allowed to skip' do
+ enable_subscription("standard")
+ @template['required'] = 'true'
+ CustomWizard::Template.save(@template)
+ put '/w/super-mega-fun-wizard/skip.json'
+ expect(response.parsed_body['error']).to eq("Wizard can't be skipped")
+ end
- it 'deletes the users redirect_to_wizard if present' do
- user.custom_fields['redirect_to_wizard'] = @template["id"]
- user.save_custom_fields(true)
- @wizard = CustomWizard::Wizard.create(@template["id"], user)
- put '/w/super-mega-fun-wizard/skip.json'
- expect(response.status).to eq(200)
- expect(user.reload.redirect_to_wizard).to eq(nil)
- end
+ it 'skip response contains a redirect_to if in users submissions' do
+ @wizard = CustomWizard::Wizard.create(@template["id"], user)
+ CustomWizard::Submission.new(@wizard, redirect_to: "/t/2").save
+ put '/w/super-mega-fun-wizard/skip.json'
+ expect(response.parsed_body['redirect_to']).to eq('/t/2')
+ end
- it "deletes the submission if user has filled up some data" do
- @wizard = CustomWizard::Wizard.create(@template["id"], user)
- CustomWizard::Submission.new(@wizard, step_1_field_1: "Hello World").save
- current_submission = @wizard.current_submission
- put '/w/super-mega-fun-wizard/skip.json'
- submissions = CustomWizard::Submission.list(@wizard).submissions
+ it 'deletes the users redirect_to_wizard if present' do
+ user.custom_fields['redirect_to_wizard'] = @template["id"]
+ user.save_custom_fields(true)
+ @wizard = CustomWizard::Wizard.create(@template["id"], user)
+ put '/w/super-mega-fun-wizard/skip.json'
+ expect(response.status).to eq(200)
+ expect(user.reload.redirect_to_wizard).to eq(nil)
+ end
- expect(submissions.any? { |submission| submission.id == current_submission.id }).to eq(false)
- end
+ it "deletes the submission if user has filled up some data" do
+ @wizard = CustomWizard::Wizard.create(@template["id"], user)
+ CustomWizard::Submission.new(@wizard, step_1_field_1: "Hello World").save
+ current_submission = @wizard.current_submission
+ put '/w/super-mega-fun-wizard/skip.json'
+ submissions = CustomWizard::Submission.list(@wizard).submissions
- it "starts from the first step if user visits after skipping the wizard" do
- put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
- fields: {
- step_1_field_1: "Text input"
+ expect(submissions.any? { |submission| submission.id == current_submission.id }).to eq(false)
+ end
+
+ it "starts from the first step if user visits after skipping the wizard" do
+ put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
+ fields: {
+ step_1_field_1: "Text input"
+ }
}
- }
- put '/w/super-mega-fun-wizard/skip.json'
- get '/w/super-mega-fun-wizard.json'
+ put '/w/super-mega-fun-wizard/skip.json'
+ get '/w/super-mega-fun-wizard.json'
- expect(response.parsed_body["start"]).to eq('step_1')
+ expect(response.parsed_body["start"]).to eq('step_1')
+ end
end
end
end
diff --git a/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb
index 1ac2579e..0568f898 100644
--- a/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb
+++ b/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb
@@ -29,6 +29,5 @@ describe CustomWizard::FieldSerializer do
scope: Guardian.new(user)
).as_json
expect(json_array[0][:format]).to eq("YYYY-MM-DD")
- expect(json_array[5][:file_types]).to eq(".jpg,.jpeg,.png")
end
end
diff --git a/test/javascripts/acceptance/field-test.js b/test/javascripts/acceptance/field-test.js
index 5eecce4f..f1d97130 100644
--- a/test/javascripts/acceptance/field-test.js
+++ b/test/javascripts/acceptance/field-test.js
@@ -54,6 +54,70 @@ acceptance("Field | Fields", function (needs) {
"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) {
await visit("/w/wizard");
diff --git a/test/javascripts/acceptance/wizard-test.js b/test/javascripts/acceptance/wizard-test.js
index 063c80fb..e2e6ce04 100644
--- a/test/javascripts/acceptance/wizard-test.js
+++ b/test/javascripts/acceptance/wizard-test.js
@@ -9,6 +9,7 @@ import {
import {
wizard,
wizardCompleted,
+ wizardGuest,
wizardNoUser,
wizardNotPermitted,
} from "../helpers/wizard";
@@ -106,3 +107,59 @@ acceptance("Wizard | Wizard", function (needs) {
assert.strictEqual($("body.custom-wizard").length, 0);
});
});
+
+acceptance("Wizard | Guest access", function (needs) {
+ needs.pretender((server, helper) => {
+ server.get("/w/wizard.json", () => helper.response(wizardGuest));
+ });
+
+ test("Does not require login", async function (assert) {
+ await visit("/w/wizard");
+ assert.ok(!exists(".wizard-no-access.requires-login"));
+ });
+
+ test("Starts", async function (assert) {
+ await visit("/w/wizard");
+ assert.ok(query(".wizard-column"), true);
+ });
+
+ test("Applies the wizard body class", async function (assert) {
+ await visit("/w/wizard");
+ assert.ok($("body.custom-wizard").length);
+ });
+
+ test("Applies the body background color", async function (assert) {
+ await visit("/w/wizard");
+ assert.ok($("body")[0].style.background);
+ });
+
+ test("Renders the wizard form", async function (assert) {
+ await visit("/w/wizard");
+ assert.ok(exists(".wizard-column-contents .wizard-step"), true);
+ assert.ok(exists(".wizard-footer img"), true);
+ });
+
+ test("Renders the first step", async function (assert) {
+ await visit("/w/wizard");
+ assert.strictEqual(
+ query(".wizard-step-title p").textContent.trim(),
+ "Text"
+ );
+ assert.strictEqual(
+ query(".wizard-step-description p").textContent.trim(),
+ "Text inputs!"
+ );
+ assert.strictEqual(
+ query(".wizard-step-description p").textContent.trim(),
+ "Text inputs!"
+ );
+ assert.strictEqual(count(".wizard-step-form .wizard-field"), 6);
+ assert.ok(exists(".wizard-step-footer .wizard-progress"), true);
+ assert.ok(exists(".wizard-step-footer .wizard-buttons"), true);
+ });
+
+ test("Removes the wizard body class when navigating away", async function (assert) {
+ await visit("/");
+ assert.strictEqual($("body.custom-wizard").length, 0);
+ });
+});
diff --git a/test/javascripts/fixtures/wizard.js.es6 b/test/javascripts/fixtures/wizard.js.es6
index 73fe45c1..a3b83063 100644
--- a/test/javascripts/fixtures/wizard.js.es6
+++ b/test/javascripts/fixtures/wizard.js.es6
@@ -6,7 +6,7 @@ export default {
submission_last_updated_at: "2022-03-15T21:11:01+01:00",
theme_id: 2,
required: false,
- permitted: true,
+ permitted: false,
uncategorized_category_id: 1,
categories: [],
subscribed: false,
diff --git a/test/javascripts/helpers/wizard.js b/test/javascripts/helpers/wizard.js
index 700cedc7..e02e2e99 100644
--- a/test/javascripts/helpers/wizard.js
+++ b/test/javascripts/helpers/wizard.js
@@ -6,8 +6,11 @@ import updateJson from "../fixtures/update";
import { cloneJSON } from "discourse-common/lib/object";
const wizardNoUser = cloneJSON(wizardJson);
+const wizardGuest = cloneJSON(wizardJson);
+wizardGuest.permitted = true;
const wizard = cloneJSON(wizardJson);
wizard.user = cloneJSON(userJson);
+wizard.permitted = true;
const wizardNotPermitted = cloneJSON(wizard);
wizardNotPermitted.permitted = false;
@@ -40,6 +43,7 @@ export {
wizardNoUser,
wizardNotPermitted,
wizardCompleted,
+ wizardGuest,
stepNotPermitted,
allFieldsWizard,
wizard,