merge main
Dieser Commit ist enthalten in:
Commit
9b8a3589bd
64 geänderte Dateien mit 1123 neuen und 746 gelöschten Zeilen
11
.github/workflows/discourse-plugin.yml
gevendort
Normale Datei
11
.github/workflows/discourse-plugin.yml
gevendort
Normale Datei
|
@ -0,0 +1,11 @@
|
||||||
|
name: Discourse Plugin
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1
|
54
.github/workflows/plugin-linting.yml
gevendort
54
.github/workflows/plugin-linting.yml
gevendort
|
@ -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 .
|
|
136
.github/workflows/plugin-tests.yml
gevendort
136
.github/workflows/plugin-tests.yml
gevendort
|
@ -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
|
|
|
@ -1,4 +1,4 @@
|
||||||
All code in this repository is Copyright 2018 by Angus McLeod.
|
All code in this repository is Copyright 2023 by Angus McLeod.
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
This program is free software; you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class CustomWizard::StepsController < ::ApplicationController
|
class CustomWizard::StepsController < ::CustomWizard::WizardClientController
|
||||||
before_action :ensure_logged_in
|
|
||||||
before_action :ensure_can_update
|
before_action :ensure_can_update
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
@ -22,7 +21,7 @@ class CustomWizard::StepsController < ::ApplicationController
|
||||||
|
|
||||||
if updater.success?
|
if updater.success?
|
||||||
wizard_id = update_params[:wizard_id]
|
wizard_id = update_params[:wizard_id]
|
||||||
builder = CustomWizard::Builder.new(wizard_id, current_user)
|
builder = CustomWizard::Builder.new(wizard_id, current_user, guest_id)
|
||||||
@wizard = builder.build(force: true)
|
@wizard = builder.build(force: true)
|
||||||
|
|
||||||
current_step = @wizard.find_step(update[:step_id])
|
current_step = @wizard.find_step(update[:step_id])
|
||||||
|
@ -85,7 +84,6 @@ class CustomWizard::StepsController < ::ApplicationController
|
||||||
private
|
private
|
||||||
|
|
||||||
def ensure_can_update
|
def ensure_can_update
|
||||||
@builder = CustomWizard::Builder.new(update_params[:wizard_id], current_user)
|
|
||||||
raise Discourse::InvalidParameters.new(:wizard_id) if @builder.template.nil?
|
raise Discourse::InvalidParameters.new(:wizard_id) if @builder.template.nil?
|
||||||
raise Discourse::InvalidAccess.new if !@builder.wizard || !@builder.wizard.can_access?
|
raise Discourse::InvalidAccess.new if !@builder.wizard || !@builder.wizard.can_access?
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
class CustomWizard::WizardController < ::ApplicationController
|
class CustomWizard::WizardController < ::CustomWizard::WizardClientController
|
||||||
before_action :ensure_plugin_enabled
|
|
||||||
before_action :ensure_logged_in, only: [:skip]
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
if wizard.present?
|
if wizard.present?
|
||||||
render json: CustomWizard::WizardSerializer.new(wizard, scope: guardian, root: false).as_json, status: 200
|
render json: CustomWizard::WizardSerializer.new(wizard, scope: guardian, root: false).as_json, status: 200
|
||||||
|
@ -35,19 +32,8 @@ class CustomWizard::WizardController < ::ApplicationController
|
||||||
|
|
||||||
def wizard
|
def wizard
|
||||||
@wizard ||= begin
|
@wizard ||= begin
|
||||||
builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user)
|
return nil unless @builder.present?
|
||||||
return nil unless builder.present?
|
@builder.build({ reset: params[:reset] }, params)
|
||||||
opts = {}
|
|
||||||
opts[:reset] = params[:reset]
|
|
||||||
builder.build(opts, params)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def ensure_plugin_enabled
|
|
||||||
unless SiteSetting.custom_wizard_enabled
|
|
||||||
redirect_to path("/")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
23
app/controllers/custom_wizard/wizard_client.rb
Normale Datei
23
app/controllers/custom_wizard/wizard_client.rb
Normale Datei
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class CustomWizard::WizardClientController < ::ApplicationController
|
||||||
|
before_action :ensure_plugin_enabled
|
||||||
|
before_action :set_builder
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def ensure_plugin_enabled
|
||||||
|
unless SiteSetting.custom_wizard_enabled
|
||||||
|
redirect_to path("/")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def guest_id
|
||||||
|
return nil if current_user.present?
|
||||||
|
cookies[:custom_wizard_guest_id] ||= CustomWizard::Wizard.generate_guest_id
|
||||||
|
cookies[:custom_wizard_guest_id]
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_builder
|
||||||
|
@builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user, guest_id)
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,12 +2,15 @@
|
||||||
class CustomWizard::SubmissionSerializer < ApplicationSerializer
|
class CustomWizard::SubmissionSerializer < ApplicationSerializer
|
||||||
attributes :id,
|
attributes :id,
|
||||||
:fields,
|
:fields,
|
||||||
:submitted_at
|
:submitted_at,
|
||||||
|
:user
|
||||||
has_one :user, serializer: ::BasicUserSerializer, embed: :objects
|
|
||||||
|
|
||||||
def include_user?
|
def include_user?
|
||||||
object.user.present?
|
object.wizard.user.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def user
|
||||||
|
::BasicUserSerializer.new(object.wizard.user).as_json
|
||||||
end
|
end
|
||||||
|
|
||||||
def fields
|
def fields
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { alias } from "@ember/object/computed";
|
||||||
import Site from "discourse/models/site";
|
import Site from "discourse/models/site";
|
||||||
import { uploadIcon } from "discourse/lib/uploads";
|
import { uploadIcon } from "discourse/lib/uploads";
|
||||||
import { dasherize } from "@ember/string";
|
import { dasherize } from "@ember/string";
|
||||||
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
|
||||||
const IMAGE_MARKDOWN_REGEX = /!\[(.*?)\|(\d{1,4}x\d{1,4})(,\s*\d{1,3}%)?(.*?)\]\((upload:\/\/.*?)\)(?!(.*`))/g;
|
const IMAGE_MARKDOWN_REGEX = /!\[(.*?)\|(\d{1,4}x\d{1,4})(,\s*\d{1,3}%)?(.*?)\]\((upload:\/\/.*?)\)(?!(.*`))/g;
|
||||||
|
|
||||||
|
@ -19,7 +20,6 @@ export default ComposerEditor.extend({
|
||||||
classNameBindings: ["fieldClass"],
|
classNameBindings: ["fieldClass"],
|
||||||
allowUpload: true,
|
allowUpload: true,
|
||||||
showLink: false,
|
showLink: false,
|
||||||
showHyperlinkBox: false,
|
|
||||||
topic: null,
|
topic: null,
|
||||||
showToolbar: true,
|
showToolbar: true,
|
||||||
focusTarget: "reply",
|
focusTarget: "reply",
|
||||||
|
@ -29,6 +29,7 @@ export default ComposerEditor.extend({
|
||||||
draftStatus: "null",
|
draftStatus: "null",
|
||||||
replyPlaceholder: alias("field.translatedPlaceholder"),
|
replyPlaceholder: alias("field.translatedPlaceholder"),
|
||||||
wizardEventFieldId: null,
|
wizardEventFieldId: null,
|
||||||
|
composerEventPrefix: "wizard-editor",
|
||||||
|
|
||||||
@on("didInsertElement")
|
@on("didInsertElement")
|
||||||
_composerEditorInit() {
|
_composerEditorInit() {
|
||||||
|
@ -77,24 +78,13 @@ export default ComposerEditor.extend({
|
||||||
$input.on("scroll", this._throttledSyncEditorAndPreviewScroll);
|
$input.on("scroll", this._throttledSyncEditorAndPreviewScroll);
|
||||||
this._bindUploadTarget();
|
this._bindUploadTarget();
|
||||||
|
|
||||||
const wizardEventNames = ["insert-text", "replace-text"];
|
const field = this.field;
|
||||||
const eventPrefix = this.eventPrefix;
|
this.editorInputClass = `.${dasherize(field.type)}-${dasherize(
|
||||||
this.appEvents.reopen({
|
field.id
|
||||||
trigger(name, ...args) {
|
)} .d-editor-input`;
|
||||||
let eventParts = name.split(":");
|
|
||||||
let currentEventPrefix = eventParts[0];
|
|
||||||
let currentEventName = eventParts[1];
|
|
||||||
|
|
||||||
if (
|
this._uppyInstance.on("file-added", () => {
|
||||||
currentEventPrefix !== "wizard-editor" &&
|
this.session.set("wizardEventFieldId", field.id);
|
||||||
wizardEventNames.some((wen) => wen === currentEventName)
|
|
||||||
) {
|
|
||||||
let wizardEventName = name.replace(eventPrefix, "wizard-editor");
|
|
||||||
return this._super(wizardEventName, ...args);
|
|
||||||
} else {
|
|
||||||
return this._super(name, ...args);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -116,12 +106,6 @@ export default ComposerEditor.extend({
|
||||||
return uploadIcon(false, this.siteSettings);
|
return uploadIcon(false, this.siteSettings);
|
||||||
},
|
},
|
||||||
|
|
||||||
click(e) {
|
|
||||||
if ($(e.target).hasClass("wizard-composer-hyperlink")) {
|
|
||||||
this.set("showHyperlinkBox", false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
_handleImageDeleteButtonClick(event) {
|
_handleImageDeleteButtonClick(event) {
|
||||||
if (!event.target.classList.contains("delete-image-button")) {
|
if (!event.target.classList.contains("delete-image-button")) {
|
||||||
|
@ -165,7 +149,7 @@ export default ComposerEditor.extend({
|
||||||
shortcut: "K",
|
shortcut: "K",
|
||||||
trimLeading: true,
|
trimLeading: true,
|
||||||
unshift: true,
|
unshift: true,
|
||||||
sendAction: () => component.set("showHyperlinkBox", true),
|
sendAction: (event) => component.send("showLinkModal", event),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.siteSettings.mentionables_enabled) {
|
if (this.siteSettings.mentionables_enabled) {
|
||||||
|
@ -206,17 +190,18 @@ export default ComposerEditor.extend({
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
},
|
},
|
||||||
|
|
||||||
addLink(linkName, linkUrl) {
|
showLinkModal(toolbarEvent) {
|
||||||
let link = `[${linkName}](${linkUrl})`;
|
let linkText = "";
|
||||||
this.appEvents.trigger("wizard-editor:insert-text", {
|
this._lastSel = toolbarEvent.selected;
|
||||||
fieldId: this.field.id,
|
|
||||||
text: link,
|
|
||||||
});
|
|
||||||
this.set("showHyperlinkBox", false);
|
|
||||||
},
|
|
||||||
|
|
||||||
hideBox() {
|
if (this._lastSel) {
|
||||||
this.set("showHyperlinkBox", false);
|
linkText = this._lastSel.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
showModal("insert-hyperlink").setProperties({
|
||||||
|
linkText,
|
||||||
|
toolbarEvent,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
showUploadModal() {
|
showUploadModal() {
|
||||||
|
|
|
@ -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();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -4,7 +4,10 @@ export default TagChooser.extend({
|
||||||
searchTags(url, data, callback) {
|
searchTags(url, data, callback) {
|
||||||
if (this.tagGroups) {
|
if (this.tagGroups) {
|
||||||
let tagGroupsString = this.tagGroups.join(",");
|
let tagGroupsString = this.tagGroups.join(",");
|
||||||
data.tag_groups = tagGroupsString;
|
data.filterForInput = {
|
||||||
|
name: "custom-wizard-tag-chooser",
|
||||||
|
groups: tagGroupsString,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._super(url, data, callback);
|
return this._super(url, data, callback);
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { bind, later } from "@ember/runloop";
|
import { bind, later } from "@ember/runloop";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
import Subscription from "../mixins/subscription";
|
||||||
|
|
||||||
const customFieldActionMap = {
|
const customFieldActionMap = {
|
||||||
topic: ["create_topic", "send_message"],
|
topic: ["create_topic", "send_message"],
|
||||||
|
@ -26,7 +27,7 @@ const customFieldActionMap = {
|
||||||
|
|
||||||
const values = ["present", "true", "false"];
|
const values = ["present", "true", "false"];
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend(Subscription, {
|
||||||
classNameBindings: [":mapper-selector", "activeType"],
|
classNameBindings: [":mapper-selector", "activeType"],
|
||||||
|
|
||||||
showText: computed("activeType", function () {
|
showText: computed("activeType", function () {
|
||||||
|
@ -116,6 +117,9 @@ export default Component.extend({
|
||||||
groupEnabled: computed("options.groupSelection", "inputType", function () {
|
groupEnabled: computed("options.groupSelection", "inputType", function () {
|
||||||
return this.optionEnabled("groupSelection");
|
return this.optionEnabled("groupSelection");
|
||||||
}),
|
}),
|
||||||
|
guestGroup: computed("options.guestGroup", "inputType", function () {
|
||||||
|
return this.optionEnabled("guestGroup");
|
||||||
|
}),
|
||||||
userEnabled: computed("options.userSelection", "inputType", function () {
|
userEnabled: computed("options.userSelection", "inputType", function () {
|
||||||
return this.optionEnabled("userSelection");
|
return this.optionEnabled("userSelection");
|
||||||
}),
|
}),
|
||||||
|
@ -126,7 +130,29 @@ export default Component.extend({
|
||||||
return this.connector === "is";
|
return this.connector === "is";
|
||||||
}),
|
}),
|
||||||
|
|
||||||
groups: alias("site.groups"),
|
@discourseComputed("site.groups", "guestGroup", "subscriptionType")
|
||||||
|
groups(groups, guestGroup, subscriptionType) {
|
||||||
|
let result = groups;
|
||||||
|
if (!guestGroup) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (["standard", "business"].includes(subscriptionType)) {
|
||||||
|
let guestIndex;
|
||||||
|
result.forEach((r, index) => {
|
||||||
|
if (r.id === 0) {
|
||||||
|
r.name = I18n.t("admin.wizard.selector.label.users");
|
||||||
|
guestIndex = index;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
result.splice(guestIndex, 0, {
|
||||||
|
id: -1,
|
||||||
|
name: I18n.t("admin.wizard.selector.label.guests"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
categories: alias("site.categories"),
|
categories: alias("site.categories"),
|
||||||
showComboBox: or(
|
showComboBox: or(
|
||||||
"showWizardField",
|
"showWizardField",
|
||||||
|
|
|
@ -32,6 +32,7 @@ export default Component.extend({
|
||||||
pairConnector: options.pairConnector || null,
|
pairConnector: options.pairConnector || null,
|
||||||
outputConnector: options.outputConnector || null,
|
outputConnector: options.outputConnector || null,
|
||||||
context: options.context || null,
|
context: options.context || null,
|
||||||
|
guestGroup: options.guestGroup || false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inputTypes = ["key", "value", "output"];
|
let inputTypes = ["key", "value", "output"];
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import SingleSelectComponent from "select-kit/components/single-select";
|
import SingleSelectComponent from "select-kit/components/single-select";
|
||||||
import Subscription from "../mixins/subscription";
|
import Subscription from "../mixins/subscription";
|
||||||
import wizardSchema from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
|
import { filterValues } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
@ -40,9 +40,9 @@ export default SingleSelectComponent.extend(Subscription, {
|
||||||
return allowedTypes;
|
return allowedTypes;
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed("feature", "attribute")
|
@discourseComputed("feature", "attribute", "wizard.allowGuests")
|
||||||
content(feature, attribute) {
|
content(feature, attribute) {
|
||||||
return wizardSchema[feature][attribute]
|
return filterValues(this.wizard, feature, attribute)
|
||||||
.map((value) => {
|
.map((value) => {
|
||||||
let allowedSubscriptionTypes = this.allowedSubscriptionTypes(
|
let allowedSubscriptionTypes = this.allowedSubscriptionTypes(
|
||||||
feature,
|
feature,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { later, scheduleOnce } from "@ember/runloop";
|
||||||
import Controller from "@ember/controller";
|
import Controller from "@ember/controller";
|
||||||
import copyText from "discourse/lib/copy-text";
|
import copyText from "discourse/lib/copy-text";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
import { filterValues } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
hasName: notEmpty("wizard.name"),
|
hasName: notEmpty("wizard.name"),
|
||||||
|
@ -59,6 +60,19 @@ export default Controller.extend({
|
||||||
}
|
}
|
||||||
return wizardFieldList(steps);
|
return wizardFieldList(steps);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@discourseComputed("fieldTypes", "wizard.allowGuests")
|
||||||
|
filteredFieldTypes(fieldTypes) {
|
||||||
|
const fieldTypeIds = fieldTypes.map((f) => f.id);
|
||||||
|
const allowedTypeIds = filterValues(
|
||||||
|
this.wizard,
|
||||||
|
"field",
|
||||||
|
"type",
|
||||||
|
fieldTypeIds
|
||||||
|
);
|
||||||
|
return fieldTypes.filter((f) => allowedTypeIds.includes(f.id));
|
||||||
|
},
|
||||||
|
|
||||||
getErrorMessage(result) {
|
getErrorMessage(result) {
|
||||||
if (result.backend_validation_error) {
|
if (result.backend_validation_error) {
|
||||||
return result.backend_validation_error;
|
return result.backend_validation_error;
|
||||||
|
|
|
@ -72,6 +72,7 @@ const field = {
|
||||||
required: null,
|
required: null,
|
||||||
type: null,
|
type: null,
|
||||||
condition: null,
|
condition: null,
|
||||||
|
tag_groups: null,
|
||||||
},
|
},
|
||||||
types: {},
|
types: {},
|
||||||
mapped: ["prefill", "content", "condition", "index"],
|
mapped: ["prefill", "content", "condition", "index"],
|
||||||
|
@ -210,11 +211,41 @@ const action = {
|
||||||
objectArrays: {},
|
objectArrays: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filters = {
|
||||||
|
allow_guests: {
|
||||||
|
field: {
|
||||||
|
type: [
|
||||||
|
"text",
|
||||||
|
"textarea",
|
||||||
|
"text_only",
|
||||||
|
"date",
|
||||||
|
"time",
|
||||||
|
"date_time",
|
||||||
|
"number",
|
||||||
|
"checkbox",
|
||||||
|
"url",
|
||||||
|
"dropdown",
|
||||||
|
"tag",
|
||||||
|
"category",
|
||||||
|
"group",
|
||||||
|
"user_selector",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: ["route_to", "send_message"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const custom_field = {
|
const custom_field = {
|
||||||
klass: ["topic", "post", "group", "category"],
|
klass: ["topic", "post", "group", "category"],
|
||||||
type: ["string", "boolean", "integer", "json"],
|
type: ["string", "boolean", "integer", "json"],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function buildFieldTypes(types) {
|
||||||
|
wizardSchema.field.types = types;
|
||||||
|
}
|
||||||
|
|
||||||
field.type = Object.keys(field.types);
|
field.type = Object.keys(field.types);
|
||||||
action.type = Object.keys(action.types);
|
action.type = Object.keys(action.types);
|
||||||
|
|
||||||
|
@ -224,16 +255,29 @@ const wizardSchema = {
|
||||||
field,
|
field,
|
||||||
custom_field,
|
custom_field,
|
||||||
action,
|
action,
|
||||||
|
filters,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function buildFieldTypes(types) {
|
|
||||||
wizardSchema.field.types = types;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildFieldValidations(validations) {
|
export function buildFieldValidations(validations) {
|
||||||
wizardSchema.field.validations = validations;
|
wizardSchema.field.validations = validations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filterValues(currentWizard, feature, attribute, values = null) {
|
||||||
|
values = values || wizardSchema[feature][attribute];
|
||||||
|
|
||||||
|
if (currentWizard && currentWizard.allowGuests) {
|
||||||
|
const filteredFeature = wizardSchema.filters.allow_guests[feature];
|
||||||
|
if (filteredFeature) {
|
||||||
|
const filtered = filteredFeature[attribute];
|
||||||
|
if (filtered) {
|
||||||
|
values = values.filter((v) => filtered.includes(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
const siteSettings = getOwner(this).lookup("service:site-settings");
|
const siteSettings = getOwner(this).lookup("service:site-settings");
|
||||||
if (siteSettings.wizard_apis_enabled) {
|
if (siteSettings.wizard_apis_enabled) {
|
||||||
wizardSchema.action.types.send_to_api = {
|
wizardSchema.action.types.send_to_api = {
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { get, set } from "@ember/object";
|
||||||
import Mixin from "@ember/object/mixin";
|
import Mixin from "@ember/object/mixin";
|
||||||
import { deepEqual } from "discourse-common/lib/object";
|
import { deepEqual } from "discourse-common/lib/object";
|
||||||
|
|
||||||
|
const observedCache = [];
|
||||||
|
|
||||||
export default Mixin.create({
|
export default Mixin.create({
|
||||||
didInsertElement() {
|
didInsertElement() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
@ -32,7 +34,13 @@ export default Mixin.create({
|
||||||
};
|
};
|
||||||
|
|
||||||
listProperties(componentType, opts).forEach((property) => {
|
listProperties(componentType, opts).forEach((property) => {
|
||||||
|
if (observedCache.includes(property)) {
|
||||||
obj.removeObserver(property, this, this.toggleUndo);
|
obj.removeObserver(property, this, this.toggleUndo);
|
||||||
|
let index = observedCache.indexOf(property);
|
||||||
|
if (index !== -1) {
|
||||||
|
observedCache.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -45,6 +53,9 @@ export default Mixin.create({
|
||||||
};
|
};
|
||||||
|
|
||||||
listProperties(componentType, opts).forEach((property) => {
|
listProperties(componentType, opts).forEach((property) => {
|
||||||
|
if (observedCache.indexOf(property) === -1) {
|
||||||
|
observedCache.push(property);
|
||||||
|
}
|
||||||
obj.addObserver(property, this, this.toggleUndo);
|
obj.addObserver(property, this, this.toggleUndo);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,8 +5,20 @@ import wizardSchema from "../lib/wizard-schema";
|
||||||
import { Promise } from "rsvp";
|
import { Promise } from "rsvp";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
const GUEST_GROUP_ID = -1;
|
||||||
|
|
||||||
const CustomWizardAdmin = EmberObject.extend({
|
const CustomWizardAdmin = EmberObject.extend({
|
||||||
|
@discourseComputed("permitted.@each.output")
|
||||||
|
allowGuests(permitted) {
|
||||||
|
return (
|
||||||
|
permitted &&
|
||||||
|
permitted.filter((p) => p.output && p.output.includes(GUEST_GROUP_ID))
|
||||||
|
.length
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
save(opts) {
|
save(opts) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let wizard = this.buildJson(this, "wizard");
|
let wizard = this.buildJson(this, "wizard");
|
||||||
|
|
|
@ -4,13 +4,7 @@ import Route from "@ember/routing/route";
|
||||||
export default Route.extend({
|
export default Route.extend({
|
||||||
beforeModel() {
|
beforeModel() {
|
||||||
const wizard = getCachedWizard();
|
const wizard = getCachedWizard();
|
||||||
if (
|
if (wizard && wizard.permitted && !wizard.completed && wizard.start) {
|
||||||
wizard &&
|
|
||||||
wizard.user &&
|
|
||||||
wizard.permitted &&
|
|
||||||
!wizard.completed &&
|
|
||||||
wizard.start
|
|
||||||
) {
|
|
||||||
this.replaceWith("customWizardStep", wizard.start);
|
this.replaceWith("customWizardStep", wizard.start);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -26,7 +20,7 @@ export default Route.extend({
|
||||||
const wizardId = model.get("id");
|
const wizardId = model.get("id");
|
||||||
const user = model.get("user");
|
const user = model.get("user");
|
||||||
const name = model.get("name");
|
const name = model.get("name");
|
||||||
const requiresLogin = !user;
|
const requiresLogin = !user && !permitted;
|
||||||
const notPermitted = !permitted;
|
const notPermitted = !permitted;
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
|
|
|
@ -7,7 +7,7 @@ export default Route.extend({
|
||||||
const wizard = getCachedWizard();
|
const wizard = getCachedWizard();
|
||||||
this.set("wizard", wizard);
|
this.set("wizard", wizard);
|
||||||
|
|
||||||
if (!wizard || !wizard.user || !wizard.permitted || wizard.completed) {
|
if (!wizard || !wizard.permitted || wizard.completed) {
|
||||||
this.replaceWith("customWizard");
|
this.replaceWith("customWizard");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -140,6 +140,7 @@
|
||||||
context="wizard"
|
context="wizard"
|
||||||
inputTypes="assignment,validation"
|
inputTypes="assignment,validation"
|
||||||
groupSelection="output"
|
groupSelection="output"
|
||||||
|
guestGroup=true
|
||||||
userFieldSelection="key"
|
userFieldSelection="key"
|
||||||
textSelection="value"
|
textSelection="value"
|
||||||
inputConnector="and"
|
inputConnector="and"
|
||||||
|
@ -160,7 +161,7 @@
|
||||||
wizard=wizard
|
wizard=wizard
|
||||||
currentField=currentField
|
currentField=currentField
|
||||||
wizardFields=wizardFields
|
wizardFields=wizardFields
|
||||||
fieldTypes=fieldTypes
|
fieldTypes=filteredFieldTypes
|
||||||
subscribed=subscribed}}
|
subscribed=subscribed}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
@ -178,7 +179,7 @@
|
||||||
apis=apis
|
apis=apis
|
||||||
removeAction="removeAction"
|
removeAction="removeAction"
|
||||||
wizardFields=wizardFields
|
wizardFields=wizardFields
|
||||||
fieldTypes=fieldTypes}}
|
fieldTypes=filteredFieldTypes}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
<div class="admin-wizard-buttons">
|
<div class="admin-wizard-buttons">
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
<div class="wizard-composer-hyperlink-contents">
|
|
||||||
<h3>{{i18n "composer.link_dialog_title"}}</h3>
|
|
||||||
{{input
|
|
||||||
class="composer-link-name"
|
|
||||||
placeholder=(i18n "composer.link_optional_text")
|
|
||||||
type="text"
|
|
||||||
value=linkName}}
|
|
||||||
{{input
|
|
||||||
class="composer-link-url"
|
|
||||||
placeholder=(i18n "composer.link_url_placeholder")
|
|
||||||
type="text"
|
|
||||||
value=linkUrl}}
|
|
||||||
{{d-button
|
|
||||||
label="wizard_composer.modal_ok"
|
|
||||||
class="add-link btn-primary"
|
|
||||||
click=(action "addLink")}}
|
|
||||||
{{d-button
|
|
||||||
label="wizard_composer.modal_cancel"
|
|
||||||
class="hide-hyperlink-box btn-danger"
|
|
||||||
click=(action "hideBox")}}
|
|
||||||
</div>
|
|
|
@ -17,6 +17,7 @@
|
||||||
feature="action"
|
feature="action"
|
||||||
attribute="type"
|
attribute="type"
|
||||||
onChange=(action "changeType")
|
onChange=(action "changeType")
|
||||||
|
wizard=wizard
|
||||||
options=(hash
|
options=(hash
|
||||||
none="admin.wizard.select_type"
|
none="admin.wizard.select_type"
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,7 +17,7 @@ body.custom-wizard .wizard-column {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img.emoji {
|
.emoji {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
@ -29,7 +29,7 @@ body.custom-wizard .wizard-column {
|
||||||
|
|
||||||
p {
|
p {
|
||||||
img {
|
img {
|
||||||
@extend img.emoji;
|
@extend .emoji;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -217,6 +217,8 @@ en:
|
||||||
list: "list"
|
list: "list"
|
||||||
custom_field: "custom field"
|
custom_field: "custom field"
|
||||||
value: "value"
|
value: "value"
|
||||||
|
users: "users"
|
||||||
|
guests: "users and guests"
|
||||||
|
|
||||||
placeholder:
|
placeholder:
|
||||||
text: "Enter text"
|
text: "Enter text"
|
||||||
|
|
|
@ -53,7 +53,8 @@ en:
|
||||||
after_signup_after_time: "You can't use 'after time' and 'after signup' on the same wizard."
|
after_signup_after_time: "You can't use 'after time' and 'after signup' on the same wizard."
|
||||||
after_time: "After time setting is invalid."
|
after_time: "After time setting is invalid."
|
||||||
liquid_syntax_error: "Liquid syntax error in %{attribute}: %{message}"
|
liquid_syntax_error: "Liquid syntax error in %{attribute}: %{message}"
|
||||||
subscription: "%{type} %{property} is subscription only"
|
subscription: "%{type} %{property} usage is not supported on your subscription"
|
||||||
|
not_permitted_for_guests: "%{object_id} is not permitted when guests can access the wizard"
|
||||||
|
|
||||||
site_settings:
|
site_settings:
|
||||||
custom_wizard_enabled: "Enable custom wizards."
|
custom_wizard_enabled: "Enable custom wizards."
|
||||||
|
|
|
@ -6,6 +6,14 @@ class CustomWizard::Action
|
||||||
:guardian,
|
:guardian,
|
||||||
:result
|
:result
|
||||||
|
|
||||||
|
REQUIRES_USER = %w[
|
||||||
|
create_topic
|
||||||
|
update_profile
|
||||||
|
open_composer
|
||||||
|
watch_categories
|
||||||
|
add_to_group
|
||||||
|
]
|
||||||
|
|
||||||
def initialize(opts)
|
def initialize(opts)
|
||||||
@wizard = opts[:wizard]
|
@wizard = opts[:wizard]
|
||||||
@action = opts[:action]
|
@action = opts[:action]
|
||||||
|
@ -17,6 +25,12 @@ class CustomWizard::Action
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
|
if REQUIRES_USER.include?(action['id']) && !@user
|
||||||
|
log_error("action requires user", "id: #{action['id']};")
|
||||||
|
@result.success = false
|
||||||
|
return @result
|
||||||
|
end
|
||||||
|
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
self.send(action['type'].to_sym)
|
self.send(action['type'].to_sym)
|
||||||
end
|
end
|
||||||
|
@ -76,7 +90,6 @@ class CustomWizard::Action
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_message
|
def send_message
|
||||||
|
|
||||||
if action['required'].present?
|
if action['required'].present?
|
||||||
required = CustomWizard::Mapper.new(
|
required = CustomWizard::Mapper.new(
|
||||||
inputs: action['required'],
|
inputs: action['required'],
|
||||||
|
@ -123,13 +136,14 @@ class CustomWizard::Action
|
||||||
|
|
||||||
params[:archetype] = Archetype.private_message
|
params[:archetype] = Archetype.private_message
|
||||||
|
|
||||||
creator = PostCreator.new(user, params)
|
poster = user || Discourse.system_user
|
||||||
|
creator = PostCreator.new(poster, params)
|
||||||
post = creator.create
|
post = creator.create
|
||||||
|
|
||||||
if creator.errors.present?
|
if creator.errors.present?
|
||||||
messages = creator.errors.full_messages.join(" ")
|
messages = creator.errors.full_messages.join(" ")
|
||||||
log_error("failed to create message", messages)
|
log_error("failed to create message", messages)
|
||||||
elsif action['skip_redirect'].blank?
|
elsif user && action['skip_redirect'].blank?
|
||||||
@submission.redirect_on_complete = post.topic.url
|
@submission.redirect_on_complete = post.topic.url
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -809,10 +823,12 @@ class CustomWizard::Action
|
||||||
end
|
end
|
||||||
|
|
||||||
def save_log
|
def save_log
|
||||||
|
username = user ? user.username : @wizard.actor_id
|
||||||
|
|
||||||
CustomWizard::Log.create(
|
CustomWizard::Log.create(
|
||||||
@wizard.id,
|
@wizard.id,
|
||||||
action['type'],
|
action['type'],
|
||||||
user.username,
|
username,
|
||||||
@log.join('; ')
|
@log.join('; ')
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
class CustomWizard::Builder
|
class CustomWizard::Builder
|
||||||
attr_accessor :wizard, :updater, :template
|
attr_accessor :wizard, :updater, :template
|
||||||
|
|
||||||
def initialize(wizard_id, user = nil)
|
def initialize(wizard_id, user = nil, guest_id = nil)
|
||||||
@template = CustomWizard::Template.create(wizard_id)
|
@template = CustomWizard::Template.create(wizard_id)
|
||||||
return nil if @template.nil?
|
return nil if @template.nil?
|
||||||
@wizard = CustomWizard::Wizard.new(template.data, user)
|
@wizard = CustomWizard::Wizard.new(template.data, user, guest_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.sorted_handlers
|
def self.sorted_handlers
|
||||||
|
@ -182,7 +182,7 @@ class CustomWizard::Builder
|
||||||
if field_template['description'].present?
|
if field_template['description'].present?
|
||||||
params[:description] = mapper.interpolate(
|
params[:description] = mapper.interpolate(
|
||||||
field_template['description'],
|
field_template['description'],
|
||||||
user: true,
|
user: @wizard.user,
|
||||||
value: true,
|
value: true,
|
||||||
wizard: true,
|
wizard: true,
|
||||||
template: true
|
template: true
|
||||||
|
@ -192,7 +192,7 @@ class CustomWizard::Builder
|
||||||
if field_template['preview_template'].present?
|
if field_template['preview_template'].present?
|
||||||
preview_template = mapper.interpolate(
|
preview_template = mapper.interpolate(
|
||||||
field_template['preview_template'],
|
field_template['preview_template'],
|
||||||
user: true,
|
user: @wizard.user,
|
||||||
value: true,
|
value: true,
|
||||||
wizard: true,
|
wizard: true,
|
||||||
template: true
|
template: true
|
||||||
|
@ -204,7 +204,7 @@ class CustomWizard::Builder
|
||||||
if field_template['placeholder'].present?
|
if field_template['placeholder'].present?
|
||||||
params[:placeholder] = mapper.interpolate(
|
params[:placeholder] = mapper.interpolate(
|
||||||
field_template['placeholder'],
|
field_template['placeholder'],
|
||||||
user: true,
|
user: @wizard.user,
|
||||||
value: true,
|
value: true,
|
||||||
wizard: true,
|
wizard: true,
|
||||||
template: true
|
template: true
|
||||||
|
@ -248,7 +248,7 @@ class CustomWizard::Builder
|
||||||
if step_template['description']
|
if step_template['description']
|
||||||
step.description = mapper.interpolate(
|
step.description = mapper.interpolate(
|
||||||
step_template['description'],
|
step_template['description'],
|
||||||
user: true,
|
user: @wizard.user,
|
||||||
value: true,
|
value: true,
|
||||||
wizard: true,
|
wizard: true,
|
||||||
template: true
|
template: true
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
require 'request_store'
|
|
||||||
|
|
||||||
module CustomWizardDiscourseTagging
|
module CustomWizardDiscourseTagging
|
||||||
def filter_allowed_tags(guardian, opts = {})
|
def filter_allowed_tags(guardian, opts = {})
|
||||||
if tag_groups = ::RequestStore.store[:tag_groups]
|
if opts[:for_input].respond_to?(:dig) && (groups = opts.dig(:for_input, :groups)).present?
|
||||||
tag_group_array = tag_groups.split(",")
|
tag_group_array = groups.split(",")
|
||||||
filtered_tags = TagGroup.includes(:tags).where(name: tag_group_array).map do |tag_group|
|
filtered_tags = TagGroup.includes(:tags).where(name: tag_group_array).map do |tag_group|
|
||||||
tag_group.tags.pluck(:name)
|
tag_group.tags.pluck(:name)
|
||||||
end.flatten
|
end.flatten
|
||||||
|
|
|
@ -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
|
|
|
@ -29,6 +29,11 @@ class CustomWizard::Field
|
||||||
attr_accessor :index,
|
attr_accessor :index,
|
||||||
:step
|
:step
|
||||||
|
|
||||||
|
REQUIRES_USER = %w[
|
||||||
|
composer
|
||||||
|
upload
|
||||||
|
]
|
||||||
|
|
||||||
def initialize(attrs)
|
def initialize(attrs)
|
||||||
@raw = attrs || {}
|
@raw = attrs || {}
|
||||||
@id = attrs[:id]
|
@id = attrs[:id]
|
||||||
|
|
|
@ -203,6 +203,8 @@ class CustomWizard::Mapper
|
||||||
end
|
end
|
||||||
|
|
||||||
def map_user_field(value)
|
def map_user_field(value)
|
||||||
|
return nil unless user
|
||||||
|
|
||||||
if value.include?(User::USER_FIELD_PREFIX)
|
if value.include?(User::USER_FIELD_PREFIX)
|
||||||
user.custom_fields[value]
|
user.custom_fields[value]
|
||||||
elsif PROFILE_FIELDS.include?(value)
|
elsif PROFILE_FIELDS.include?(value)
|
||||||
|
@ -229,7 +231,7 @@ class CustomWizard::Mapper
|
||||||
def interpolate(string, opts = { user: true, wizard: true, value: true, template: false })
|
def interpolate(string, opts = { user: true, wizard: true, value: true, template: false })
|
||||||
return string if string.blank? || string.frozen?
|
return string if string.blank? || string.frozen?
|
||||||
|
|
||||||
if opts[:user]
|
if opts[:user] && @user.present?
|
||||||
string.gsub!(/u\{(.*?)\}/) { |match| map_user_field($1) || '' }
|
string.gsub!(/u\{(.*?)\}/) { |match| map_user_field($1) || '' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -253,7 +255,7 @@ class CustomWizard::Mapper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if opts[:template] && CustomWizard::Subscription.subscribed?
|
if opts[:template] #&& CustomWizard::Subscription.subscribed?
|
||||||
template = Liquid::Template.parse(string)
|
template = Liquid::Template.parse(string)
|
||||||
string = template.render(data)
|
string = template.render(data)
|
||||||
end
|
end
|
||||||
|
@ -282,4 +284,8 @@ class CustomWizard::Mapper
|
||||||
user.avatar_template_url.gsub("{size}", parts.last)
|
user.avatar_template_url.gsub("{size}", parts.last)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.mapped_value?(value)
|
||||||
|
value.is_a?(Array) && value.all? { |v| v.is_a?(Hash) && v.key?("type") }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,8 +5,7 @@ class CustomWizard::StepUpdater
|
||||||
attr_accessor :refresh_required, :result
|
attr_accessor :refresh_required, :result
|
||||||
attr_reader :step, :submission
|
attr_reader :step, :submission
|
||||||
|
|
||||||
def initialize(current_user, wizard, step, submission)
|
def initialize(wizard, step, submission)
|
||||||
@current_user = current_user
|
|
||||||
@wizard = wizard
|
@wizard = wizard
|
||||||
@step = step
|
@step = step
|
||||||
@refresh_required = false
|
@refresh_required = false
|
||||||
|
@ -22,9 +21,9 @@ class CustomWizard::StepUpdater
|
||||||
|
|
||||||
@step.updater.call(self)
|
@step.updater.call(self)
|
||||||
|
|
||||||
UserHistory.create(
|
CustomWizard::UserHistory.create(
|
||||||
action: UserHistory.actions[:custom_wizard_step],
|
action: CustomWizard::UserHistory.actions[:step],
|
||||||
acting_user_id: @current_user.id,
|
actor_id: @wizard.actor_id,
|
||||||
context: @wizard.id,
|
context: @wizard.id,
|
||||||
subject: @step.id
|
subject: @step.id
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,8 +7,6 @@ class CustomWizard::Submission
|
||||||
META ||= %w(updated_at submitted_at route_to redirect_on_complete redirect_to)
|
META ||= %w(updated_at submitted_at route_to redirect_on_complete redirect_to)
|
||||||
|
|
||||||
attr_reader :id,
|
attr_reader :id,
|
||||||
:user,
|
|
||||||
:user_id,
|
|
||||||
:wizard
|
:wizard
|
||||||
|
|
||||||
attr_accessor :fields,
|
attr_accessor :fields,
|
||||||
|
@ -18,15 +16,8 @@ class CustomWizard::Submission
|
||||||
class_eval { attr_accessor attr }
|
class_eval { attr_accessor attr }
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(wizard, data = {}, user_id = nil)
|
def initialize(wizard, data = {})
|
||||||
@wizard = wizard
|
@wizard = wizard
|
||||||
@user_id = user_id
|
|
||||||
|
|
||||||
if user_id
|
|
||||||
@user = User.find_by(id: user_id)
|
|
||||||
else
|
|
||||||
@user = wizard.user
|
|
||||||
end
|
|
||||||
|
|
||||||
data = (data || {}).with_indifferent_access
|
data = (data || {}).with_indifferent_access
|
||||||
@id = data['id'] || SecureRandom.hex(12)
|
@id = data['id'] || SecureRandom.hex(12)
|
||||||
|
@ -44,13 +35,13 @@ class CustomWizard::Submission
|
||||||
return nil unless wizard.save_submissions
|
return nil unless wizard.save_submissions
|
||||||
validate
|
validate
|
||||||
|
|
||||||
submission_list = self.class.list(wizard, user_id: user.id)
|
submission_list = self.class.list(wizard)
|
||||||
submissions = submission_list.submissions.select { |submission| submission.id != self.id }
|
submissions = submission_list.submissions.select { |submission| submission.id != self.id }
|
||||||
self.updated_at = Time.now.iso8601
|
self.updated_at = Time.now.iso8601
|
||||||
submissions.push(self)
|
submissions.push(self)
|
||||||
|
|
||||||
submission_data = submissions.map { |submission| data_to_save(submission) }
|
submission_data = submissions.map { |submission| data_to_save(submission) }
|
||||||
PluginStore.set("#{wizard.id}_#{KEY}", user.id, submission_data)
|
PluginStore.set("#{wizard.id}_#{KEY}", wizard.actor_id, submission_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate
|
def validate
|
||||||
|
@ -93,25 +84,21 @@ class CustomWizard::Submission
|
||||||
data
|
data
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.get(wizard, user_id)
|
def self.get(wizard)
|
||||||
data = PluginStore.get("#{wizard.id}_#{KEY}", user_id).last
|
data = PluginStore.get("#{wizard.id}_#{KEY}", wizard.actor_id).last
|
||||||
new(wizard, data, user_id)
|
new(wizard, data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove
|
def remove
|
||||||
if present?
|
if present?
|
||||||
user_id = @user.id
|
data = PluginStore.get("#{@wizard.id}_#{KEY}", wizard.actor_id)
|
||||||
wizard_id = @wizard.id
|
data.delete_if { |sub| sub["id"] == @id }
|
||||||
submission_id = @id
|
PluginStore.set("#{@wizard.id}_#{KEY}", wizard.actor_id, data)
|
||||||
data = PluginStore.get("#{wizard_id}_#{KEY}", user_id)
|
|
||||||
data.delete_if { |sub| sub["id"] == submission_id }
|
|
||||||
PluginStore.set("#{wizard_id}_#{KEY}", user_id, data)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.cleanup_incomplete_submissions(wizard)
|
def self.cleanup_incomplete_submissions(wizard)
|
||||||
user_id = wizard.user.id
|
all_submissions = list(wizard)
|
||||||
all_submissions = list(wizard, user_id: user_id)
|
|
||||||
sorted_submissions = all_submissions.submissions.sort_by do |submission|
|
sorted_submissions = all_submissions.submissions.sort_by do |submission|
|
||||||
zero_epoch_time = DateTime.strptime("0", '%s')
|
zero_epoch_time = DateTime.strptime("0", '%s')
|
||||||
[
|
[
|
||||||
|
@ -129,12 +116,12 @@ class CustomWizard::Submission
|
||||||
end
|
end
|
||||||
|
|
||||||
valid_data = valid_submissions.map { |submission| submission.data_to_save(submission) }
|
valid_data = valid_submissions.map { |submission| submission.data_to_save(submission) }
|
||||||
PluginStore.set("#{wizard.id}_#{KEY}", user_id, valid_data)
|
PluginStore.set("#{wizard.id}_#{KEY}", wizard.actor_id, valid_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.list(wizard, user_id: nil, order_by: nil, page: nil)
|
def self.list(wizard, order_by: nil, page: nil)
|
||||||
params = { plugin_name: "#{wizard.id}_#{KEY}" }
|
params = { plugin_name: "#{wizard.id}_#{KEY}" }
|
||||||
params[:key] = user_id if user_id.present?
|
params[:key] = wizard.actor_id if wizard.actor_id
|
||||||
|
|
||||||
query = PluginStoreRow.where(params)
|
query = PluginStoreRow.where(params)
|
||||||
result = OpenStruct.new(submissions: [], total: nil)
|
result = OpenStruct.new(submissions: [], total: nil)
|
||||||
|
@ -142,7 +129,7 @@ class CustomWizard::Submission
|
||||||
query.each do |record|
|
query.each do |record|
|
||||||
if (submission_data = ::JSON.parse(record.value)).any?
|
if (submission_data = ::JSON.parse(record.value)).any?
|
||||||
submission_data.each do |data|
|
submission_data.each do |data|
|
||||||
result.submissions.push(new(wizard, data, record.key))
|
result.submissions.push(new(wizard, data))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,7 +17,7 @@ class CustomWizard::Subscription
|
||||||
none: [],
|
none: [],
|
||||||
standard: ['*'],
|
standard: ['*'],
|
||||||
business: ['*'],
|
business: ['*'],
|
||||||
community: ['*']
|
community: ['*', "!#{CustomWizard::Wizard::GUEST_GROUP_ID}"]
|
||||||
},
|
},
|
||||||
restart_on_revisit: {
|
restart_on_revisit: {
|
||||||
none: [],
|
none: [],
|
||||||
|
@ -114,8 +114,15 @@ class CustomWizard::Subscription
|
||||||
## Subscription type does not support the attribute.
|
## Subscription type does not support the attribute.
|
||||||
return false if values.blank?
|
return false if values.blank?
|
||||||
|
|
||||||
|
## Value is an exception for the subscription type
|
||||||
|
if (exceptions = get_exceptions(values)).any?
|
||||||
|
value = mapped_output(value) if CustomWizard::Mapper.mapped_value?(value)
|
||||||
|
value = [*value].map(&:to_s)
|
||||||
|
return false if (exceptions & value).length > 0
|
||||||
|
end
|
||||||
|
|
||||||
## Subscription type supports all values of the attribute.
|
## Subscription type supports all values of the attribute.
|
||||||
return true if values.first === "*"
|
return true if values.include?("*")
|
||||||
|
|
||||||
## Subscription type supports some values of the attributes.
|
## Subscription type supports some values of the attributes.
|
||||||
values.include?(value)
|
values.include?(value)
|
||||||
|
@ -192,4 +199,21 @@ class CustomWizard::Subscription
|
||||||
def self.includes?(feature, attribute, value)
|
def self.includes?(feature, attribute, value)
|
||||||
new.includes?(feature, attribute, value)
|
new.includes?(feature, attribute, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def get_exceptions(values)
|
||||||
|
values.reduce([]) do |result, value|
|
||||||
|
result << value.split("!").last if value.start_with?("!")
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def mapped_output(value)
|
||||||
|
value.reduce([]) do |result, v|
|
||||||
|
## We can only validate mapped assignment values at the moment
|
||||||
|
result << v["output"] if v.is_a?(Hash) && v["type"] === "assignment"
|
||||||
|
result
|
||||||
|
end.flatten
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,7 +23,6 @@ class CustomWizard::Template
|
||||||
normalize_data
|
normalize_data
|
||||||
validate_data
|
validate_data
|
||||||
prepare_data
|
prepare_data
|
||||||
|
|
||||||
return false if errors.any?
|
return false if errors.any?
|
||||||
|
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
|
|
54
lib/custom_wizard/user_history.rb
Normale Datei
54
lib/custom_wizard/user_history.rb
Normale Datei
|
@ -0,0 +1,54 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
UserHistory.actions[:custom_wizard_step] = 1000
|
||||||
|
|
||||||
|
class CustomWizard::UserHistory
|
||||||
|
def self.where(actor_id: nil, action: nil, context: nil, subject: nil)
|
||||||
|
::UserHistory.where(where_opts(actor_id, action, context, subject))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create(actor_id: nil, action: nil, context: nil, subject: nil)
|
||||||
|
::UserHistory.create(create_opts(actor_id, action, context, subject))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create!(actor_id: nil, action: nil, context: nil, subject: nil)
|
||||||
|
::UserHistory.create!(create_opts(actor_id, action, context, subject))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.actions
|
||||||
|
@actions ||=
|
||||||
|
Enum.new(
|
||||||
|
step: UserHistory.actions[:custom_wizard_step]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.where_opts(actor_id, action, context, subject)
|
||||||
|
opts = {
|
||||||
|
context: context
|
||||||
|
}
|
||||||
|
opts[:action] = action if action
|
||||||
|
opts[:subject] = subject if subject
|
||||||
|
add_actor(opts, actor_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create_opts(actor_id, action, context, subject)
|
||||||
|
opts = {
|
||||||
|
action: action,
|
||||||
|
context: context
|
||||||
|
}
|
||||||
|
opts[:subject] = subject if subject
|
||||||
|
add_actor(opts, actor_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.add_actor(opts, actor_id)
|
||||||
|
acting_user_id = actor_id
|
||||||
|
|
||||||
|
if actor_id.is_a?(String) && actor_id.include?(CustomWizard::Wizard::GUEST_ID_PREFIX)
|
||||||
|
opts[:acting_user_id] = Discourse.system_user.id
|
||||||
|
opts[:details] = actor_id
|
||||||
|
else
|
||||||
|
opts[:acting_user_id] = actor_id
|
||||||
|
end
|
||||||
|
|
||||||
|
opts
|
||||||
|
end
|
||||||
|
end
|
|
@ -30,6 +30,7 @@ class CustomWizard::TemplateValidator
|
||||||
validate_subscription(field, :field)
|
validate_subscription(field, :field)
|
||||||
check_required(field, :field)
|
check_required(field, :field)
|
||||||
validate_liquid_template(field, :field)
|
validate_liquid_template(field, :field)
|
||||||
|
validate_guests(field, :field)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -39,6 +40,7 @@ class CustomWizard::TemplateValidator
|
||||||
validate_subscription(action, :action)
|
validate_subscription(action, :action)
|
||||||
check_required(action, :action)
|
check_required(action, :action)
|
||||||
validate_liquid_template(action, :action)
|
validate_liquid_template(action, :action)
|
||||||
|
validate_guests(action, :action)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -80,6 +82,21 @@ class CustomWizard::TemplateValidator
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_guests(object, type)
|
||||||
|
guests_permitted = @data[:permitted] && @data[:permitted].any? do |m|
|
||||||
|
m["output"].include?(CustomWizard::Wizard::GUEST_GROUP_ID)
|
||||||
|
end
|
||||||
|
return unless guests_permitted
|
||||||
|
|
||||||
|
if type === :action && CustomWizard::Action::REQUIRES_USER.include?(object[:type])
|
||||||
|
errors.add :base, I18n.t("wizard.validation.not_permitted_for_guests", object_id: object[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
if type === :field && CustomWizard::Field::REQUIRES_USER.include?(object[:type])
|
||||||
|
errors.add :base, I18n.t("wizard.validation.not_permitted_for_guests", object_id: object[:id])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def validate_after_signup
|
def validate_after_signup
|
||||||
return unless ActiveRecord::Type::Boolean.new.cast(@data[:after_signup])
|
return unless ActiveRecord::Type::Boolean.new.cast(@data[:after_signup])
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,6 @@ require_dependency 'wizard/field'
|
||||||
require_dependency 'wizard/step_updater'
|
require_dependency 'wizard/step_updater'
|
||||||
require_dependency 'wizard/builder'
|
require_dependency 'wizard/builder'
|
||||||
|
|
||||||
UserHistory.actions[:custom_wizard_step] = 1000
|
|
||||||
|
|
||||||
class CustomWizard::Wizard
|
class CustomWizard::Wizard
|
||||||
include ActiveModel::SerializerSupport
|
include ActiveModel::SerializerSupport
|
||||||
|
|
||||||
|
@ -31,13 +29,22 @@ class CustomWizard::Wizard
|
||||||
:actions,
|
:actions,
|
||||||
:action_ids,
|
:action_ids,
|
||||||
:user,
|
:user,
|
||||||
|
:guest_id,
|
||||||
:submissions,
|
:submissions,
|
||||||
:template
|
:template
|
||||||
|
|
||||||
attr_reader :all_step_ids
|
attr_reader :all_step_ids
|
||||||
|
|
||||||
def initialize(attrs = {}, user = nil)
|
GUEST_ID_PREFIX ||= "guest"
|
||||||
|
GUEST_GROUP_ID = -1
|
||||||
|
|
||||||
|
def initialize(attrs = {}, user = nil, guest_id = nil)
|
||||||
|
if user
|
||||||
@user = user
|
@user = user
|
||||||
|
elsif guest_id
|
||||||
|
@guest_id = guest_id
|
||||||
|
end
|
||||||
|
|
||||||
attrs = attrs.with_indifferent_access
|
attrs = attrs.with_indifferent_access
|
||||||
|
|
||||||
@id = attrs['id']
|
@id = attrs['id']
|
||||||
|
@ -81,6 +88,10 @@ class CustomWizard::Wizard
|
||||||
@template = attrs
|
@template = attrs
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def actor_id
|
||||||
|
user ? user.id : guest_id
|
||||||
|
end
|
||||||
|
|
||||||
def cast_bool(val)
|
def cast_bool(val)
|
||||||
val.nil? ? false : ActiveRecord::Type::Boolean.new.cast(val)
|
val.nil? ? false : ActiveRecord::Type::Boolean.new.cast(val)
|
||||||
end
|
end
|
||||||
|
@ -141,17 +152,16 @@ class CustomWizard::Wizard
|
||||||
end
|
end
|
||||||
|
|
||||||
def last_completed_step_id
|
def last_completed_step_id
|
||||||
if user && unfinished? && last_completed_step = ::UserHistory.where(
|
return nil unless actor_id && unfinished?
|
||||||
acting_user_id: user.id,
|
|
||||||
action: ::UserHistory.actions[:custom_wizard_step],
|
last_completed_step = CustomWizard::UserHistory.where(
|
||||||
|
actor_id: actor_id,
|
||||||
|
action: CustomWizard::UserHistory.actions[:step],
|
||||||
context: id,
|
context: id,
|
||||||
subject: all_step_ids
|
subject: all_step_ids
|
||||||
).order("created_at").last
|
).order("created_at").last
|
||||||
|
|
||||||
last_completed_step.subject
|
last_completed_step&.subject
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_step(step_id)
|
def find_step(step_id)
|
||||||
|
@ -161,15 +171,15 @@ class CustomWizard::Wizard
|
||||||
def create_updater(step_id, submission)
|
def create_updater(step_id, submission)
|
||||||
step = @steps.find { |s| s.id == step_id }
|
step = @steps.find { |s| s.id == step_id }
|
||||||
wizard = self
|
wizard = self
|
||||||
CustomWizard::StepUpdater.new(user, wizard, step, submission)
|
CustomWizard::StepUpdater.new(wizard, step, submission)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unfinished?
|
def unfinished?
|
||||||
return nil if !user
|
return nil unless actor_id
|
||||||
|
|
||||||
most_recent = ::UserHistory.where(
|
most_recent = CustomWizard::UserHistory.where(
|
||||||
acting_user_id: user.id,
|
actor_id: actor_id,
|
||||||
action: ::UserHistory.actions[:custom_wizard_step],
|
action: CustomWizard::UserHistory.actions[:step],
|
||||||
context: id,
|
context: id,
|
||||||
).distinct.order('updated_at DESC').first
|
).distinct.order('updated_at DESC').first
|
||||||
|
|
||||||
|
@ -183,11 +193,11 @@ class CustomWizard::Wizard
|
||||||
end
|
end
|
||||||
|
|
||||||
def completed?
|
def completed?
|
||||||
return nil if !user
|
return nil unless actor_id
|
||||||
|
|
||||||
history = ::UserHistory.where(
|
history = CustomWizard::UserHistory.where(
|
||||||
acting_user_id: user.id,
|
actor_id: actor_id,
|
||||||
action: ::UserHistory.actions[:custom_wizard_step],
|
action: CustomWizard::UserHistory.actions[:step],
|
||||||
context: id
|
context: id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -200,8 +210,9 @@ class CustomWizard::Wizard
|
||||||
end
|
end
|
||||||
|
|
||||||
def permitted?
|
def permitted?
|
||||||
return false unless user
|
return nil unless actor_id
|
||||||
return true if user.admin? || permitted.blank?
|
return true if user && (user.admin? || permitted.blank?)
|
||||||
|
return false if !user && permitted.blank?
|
||||||
|
|
||||||
mapper = CustomWizard::Mapper.new(
|
mapper = CustomWizard::Mapper.new(
|
||||||
inputs: permitted,
|
inputs: permitted,
|
||||||
|
@ -215,7 +226,11 @@ class CustomWizard::Wizard
|
||||||
return true if mapper.blank?
|
return true if mapper.blank?
|
||||||
|
|
||||||
mapper.all? do |m|
|
mapper.all? do |m|
|
||||||
|
if !user
|
||||||
|
m[:type] === 'assignment' && [*m[:result]].include?(GUEST_GROUP_ID)
|
||||||
|
else
|
||||||
if m[:type] === 'assignment'
|
if m[:type] === 'assignment'
|
||||||
|
[*m[:result]].include?(GUEST_GROUP_ID) ||
|
||||||
[*m[:result]].include?(Group::AUTO_GROUPS[:everyone]) ||
|
[*m[:result]].include?(Group::AUTO_GROUPS[:everyone]) ||
|
||||||
GroupUser.exists?(group_id: m[:result], user_id: user.id)
|
GroupUser.exists?(group_id: m[:result], user_id: user.id)
|
||||||
elsif m[:type] === 'validation'
|
elsif m[:type] === 'validation'
|
||||||
|
@ -225,17 +240,18 @@ class CustomWizard::Wizard
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def can_access?
|
def can_access?
|
||||||
return false unless user
|
permitted? && (user&.admin? || (multiple_submissions || !completed?))
|
||||||
return true if user.admin
|
|
||||||
permitted? && (multiple_submissions || !completed?)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset
|
def reset
|
||||||
::UserHistory.create(
|
return nil unless actor_id
|
||||||
action: ::UserHistory.actions[:custom_wizard_step],
|
|
||||||
acting_user_id: user.id,
|
CustomWizard::UserHistory.create(
|
||||||
|
action: CustomWizard::UserHistory.actions[:step],
|
||||||
|
actor_id: actor_id,
|
||||||
context: id,
|
context: id,
|
||||||
subject: "reset"
|
subject: "reset"
|
||||||
)
|
)
|
||||||
|
@ -263,8 +279,7 @@ class CustomWizard::Wizard
|
||||||
end
|
end
|
||||||
|
|
||||||
def submissions
|
def submissions
|
||||||
return nil unless user.present?
|
@submissions ||= CustomWizard::Submission.list(self).submissions
|
||||||
@submissions ||= CustomWizard::Submission.list(self, user_id: user.id).submissions
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_submission
|
def current_submission
|
||||||
|
@ -300,15 +315,17 @@ class CustomWizard::Wizard
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_user_redirect
|
def remove_user_redirect
|
||||||
|
return unless user.present?
|
||||||
|
|
||||||
if id == user.redirect_to_wizard
|
if id == user.redirect_to_wizard
|
||||||
user.custom_fields.delete('redirect_to_wizard')
|
user.custom_fields.delete('redirect_to_wizard')
|
||||||
user.save_custom_fields(true)
|
user.save_custom_fields(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.create(wizard_id, user = nil)
|
def self.create(wizard_id, user = nil, guest_id = nil)
|
||||||
if template = CustomWizard::Template.find(wizard_id)
|
if template = CustomWizard::Template.find(wizard_id)
|
||||||
new(template.to_h, user)
|
new(template.to_h, user, guest_id)
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -319,7 +336,7 @@ class CustomWizard::Wizard
|
||||||
|
|
||||||
CustomWizard::Template.list(**template_opts).reduce([]) do |result, template|
|
CustomWizard::Template.list(**template_opts).reduce([]) do |result, template|
|
||||||
wizard = new(template, user)
|
wizard = new(template, user)
|
||||||
result.push(wizard) if wizard.can_access? && (
|
result.push(wizard) if wizard.permitted? && (
|
||||||
!not_completed || !wizard.completed?
|
!not_completed || !wizard.completed?
|
||||||
)
|
)
|
||||||
result
|
result
|
||||||
|
@ -380,4 +397,8 @@ class CustomWizard::Wizard
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.generate_guest_id
|
||||||
|
"#{self::GUEST_ID_PREFIX}_#{SecureRandom.hex(12)}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
# name: discourse-custom-wizard
|
# name: discourse-custom-wizard
|
||||||
# about: Forms for Discourse. Better onboarding, structured posting, data enrichment, automated actions and much more.
|
# about: Forms for Discourse. Better onboarding, structured posting, data enrichment, automated actions and much more.
|
||||||
|
<<<<<<< HEAD
|
||||||
# version: 2.1.5
|
# version: 2.1.5
|
||||||
|
=======
|
||||||
|
# version: 2.2.9
|
||||||
|
>>>>>>> main
|
||||||
# authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George, Kaitlin Maddever
|
# authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George, Kaitlin Maddever
|
||||||
# url: https://github.com/paviliondev/discourse-custom-wizard
|
# url: https://github.com/paviliondev/discourse-custom-wizard
|
||||||
# contact_emails: development@pavilion.tech
|
# contact_emails: development@pavilion.tech
|
||||||
|
@ -41,6 +45,7 @@ after_initialize do
|
||||||
../app/controllers/custom_wizard/admin/logs.rb
|
../app/controllers/custom_wizard/admin/logs.rb
|
||||||
../app/controllers/custom_wizard/admin/manager.rb
|
../app/controllers/custom_wizard/admin/manager.rb
|
||||||
../app/controllers/custom_wizard/admin/custom_fields.rb
|
../app/controllers/custom_wizard/admin/custom_fields.rb
|
||||||
|
../app/controllers/custom_wizard/wizard_client.rb
|
||||||
../app/controllers/custom_wizard/wizard.rb
|
../app/controllers/custom_wizard/wizard.rb
|
||||||
../app/controllers/custom_wizard/steps.rb
|
../app/controllers/custom_wizard/steps.rb
|
||||||
../app/controllers/custom_wizard/realtime_validations.rb
|
../app/controllers/custom_wizard/realtime_validations.rb
|
||||||
|
@ -65,6 +70,7 @@ after_initialize do
|
||||||
../lib/custom_wizard/subscription.rb
|
../lib/custom_wizard/subscription.rb
|
||||||
../lib/custom_wizard/template.rb
|
../lib/custom_wizard/template.rb
|
||||||
../lib/custom_wizard/wizard.rb
|
../lib/custom_wizard/wizard.rb
|
||||||
|
../lib/custom_wizard/user_history.rb
|
||||||
../lib/custom_wizard/api/api.rb
|
../lib/custom_wizard/api/api.rb
|
||||||
../lib/custom_wizard/api/authorization.rb
|
../lib/custom_wizard/api/authorization.rb
|
||||||
../lib/custom_wizard/api/endpoint.rb
|
../lib/custom_wizard/api/endpoint.rb
|
||||||
|
@ -88,7 +94,6 @@ after_initialize do
|
||||||
../lib/custom_wizard/extensions/extra_locales_controller.rb
|
../lib/custom_wizard/extensions/extra_locales_controller.rb
|
||||||
../lib/custom_wizard/extensions/invites_controller.rb
|
../lib/custom_wizard/extensions/invites_controller.rb
|
||||||
../lib/custom_wizard/extensions/users_controller.rb
|
../lib/custom_wizard/extensions/users_controller.rb
|
||||||
../lib/custom_wizard/extensions/tags_controller.rb
|
|
||||||
../lib/custom_wizard/extensions/guardian.rb
|
../lib/custom_wizard/extensions/guardian.rb
|
||||||
../lib/custom_wizard/extensions/custom_field/preloader.rb
|
../lib/custom_wizard/extensions/custom_field/preloader.rb
|
||||||
../lib/custom_wizard/extensions/custom_field/serializer.rb
|
../lib/custom_wizard/extensions/custom_field/serializer.rb
|
||||||
|
@ -230,7 +235,6 @@ after_initialize do
|
||||||
end
|
end
|
||||||
|
|
||||||
reloadable_patch do |plugin|
|
reloadable_patch do |plugin|
|
||||||
::TagsController.prepend CustomWizardTagsController
|
|
||||||
::DiscourseTagging.singleton_class.prepend CustomWizardDiscourseTagging
|
::DiscourseTagging.singleton_class.prepend CustomWizardDiscourseTagging
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ describe CustomWizard::Action do
|
||||||
let(:api_test_endpoint) { get_wizard_fixture("endpoints/test_endpoint") }
|
let(:api_test_endpoint) { get_wizard_fixture("endpoints/test_endpoint") }
|
||||||
let(:api_test_endpoint_body) { get_wizard_fixture("endpoints/test_endpoint_body") }
|
let(:api_test_endpoint_body) { get_wizard_fixture("endpoints/test_endpoint_body") }
|
||||||
let(:api_test_no_authorization) { get_wizard_fixture("api/no_authorization") }
|
let(:api_test_no_authorization) { get_wizard_fixture("api/no_authorization") }
|
||||||
|
let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
|
||||||
|
|
||||||
def update_template(template)
|
def update_template(template)
|
||||||
CustomWizard::Template.save(template, skip_jobs: true)
|
CustomWizard::Template.save(template, skip_jobs: true)
|
||||||
|
@ -78,8 +79,8 @@ describe CustomWizard::Action do
|
||||||
updater.update
|
updater.update
|
||||||
|
|
||||||
expect(updater.success?).to eq(true)
|
expect(updater.success?).to eq(true)
|
||||||
expect(UserHistory.where(
|
expect(CustomWizard::UserHistory.where(
|
||||||
acting_user_id: user.id,
|
actor_id: user.id,
|
||||||
context: "super_mega_fun_wizard",
|
context: "super_mega_fun_wizard",
|
||||||
subject: "step_3"
|
subject: "step_3"
|
||||||
).exists?).to eq(true)
|
).exists?).to eq(true)
|
||||||
|
@ -301,6 +302,28 @@ describe CustomWizard::Action do
|
||||||
expect(topic.first.allowed_groups.map(&:name)).to include('cool_group', 'cool_group_1')
|
expect(topic.first.allowed_groups.map(&:name)).to include('cool_group', 'cool_group_1')
|
||||||
expect(post.exists?).to eq(true)
|
expect(post.exists?).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "send_message works with guests are permitted" do
|
||||||
|
wizard_template["permitted"] = guests_permitted["permitted"]
|
||||||
|
wizard_template.delete("actions")
|
||||||
|
wizard_template['actions'] = [send_message]
|
||||||
|
update_template(wizard_template)
|
||||||
|
|
||||||
|
User.create(username: 'angus1', email: "angus1@email.com")
|
||||||
|
|
||||||
|
wizard = CustomWizard::Builder.new(wizard_template["id"], nil, CustomWizard::Wizard.generate_guest_id).build
|
||||||
|
wizard.create_updater(wizard.steps[0].id, {}).update
|
||||||
|
updater = wizard.create_updater(wizard.steps[1].id, {})
|
||||||
|
updater.update
|
||||||
|
|
||||||
|
topic = Topic.where(archetype: Archetype.private_message, title: "Message title")
|
||||||
|
post = Post.where(topic_id: topic.pluck(:id))
|
||||||
|
|
||||||
|
expect(topic.exists?).to eq(true)
|
||||||
|
expect(topic.first.topic_allowed_users.first.user.username).to eq('angus1')
|
||||||
|
expect(topic.first.topic_allowed_users.second.user.username).to eq(Discourse.system_user.username)
|
||||||
|
expect(post.exists?).to eq(true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "business subscription actions" do
|
context "business subscription actions" do
|
||||||
|
|
|
@ -80,15 +80,12 @@ describe CustomWizard::Builder do
|
||||||
|
|
||||||
it 'returns no steps if user has completed it' do
|
it 'returns no steps if user has completed it' do
|
||||||
@template[:steps].each do |step|
|
@template[:steps].each do |step|
|
||||||
UserHistory.create!(
|
CustomWizard::UserHistory.create!(
|
||||||
{
|
action: CustomWizard::UserHistory.actions[:step],
|
||||||
action: UserHistory.actions[:custom_wizard_step],
|
actor_id: user.id,
|
||||||
acting_user_id: user.id,
|
context: @template[:id],
|
||||||
context: @template[:id]
|
|
||||||
}.merge(
|
|
||||||
subject: step[:id]
|
subject: step[:id]
|
||||||
)
|
)
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
|
|
@ -373,7 +373,7 @@ describe CustomWizard::Mapper do
|
||||||
expect(result).to eq(template_params["step_1_field_1"])
|
expect(result).to eq(template_params["step_1_field_1"])
|
||||||
end
|
end
|
||||||
|
|
||||||
it "requires a subscription" do
|
it "does not require a subscription" do
|
||||||
template = '{{ "w{step_1_field_1}" | size }}'
|
template = '{{ "w{step_1_field_1}" | size }}'
|
||||||
mapper = create_template_mapper(template_params, user1)
|
mapper = create_template_mapper(template_params, user1)
|
||||||
result = mapper.interpolate(
|
result = mapper.interpolate(
|
||||||
|
@ -383,7 +383,7 @@ describe CustomWizard::Mapper do
|
||||||
wizard: true,
|
wizard: true,
|
||||||
value: true
|
value: true
|
||||||
)
|
)
|
||||||
expect(result).to eq("{{ \"#{template_params["step_1_field_1"]}\" | size }}")
|
expect(result).to eq("5")
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with a subscription" do
|
context "with a subscription" do
|
||||||
|
|
|
@ -4,6 +4,7 @@ describe CustomWizard::Submission do
|
||||||
fab!(:user) { Fabricate(:user) }
|
fab!(:user) { Fabricate(:user) }
|
||||||
fab!(:user2) { Fabricate(:user) }
|
fab!(:user2) { Fabricate(:user) }
|
||||||
let(:template_json) { get_wizard_fixture("wizard") }
|
let(:template_json) { get_wizard_fixture("wizard") }
|
||||||
|
let(:guest_id) { CustomWizard::Wizard.generate_guest_id }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
CustomWizard::Template.save(template_json, skip_jobs: true)
|
CustomWizard::Template.save(template_json, skip_jobs: true)
|
||||||
|
@ -13,10 +14,20 @@ describe CustomWizard::Submission do
|
||||||
|
|
||||||
it "saves a user's submission" do
|
it "saves a user's submission" do
|
||||||
expect(
|
expect(
|
||||||
described_class.get(@wizard, user.id).fields["step_1_field_1"]
|
described_class.get(@wizard).fields["step_1_field_1"]
|
||||||
).to eq("I am user submission")
|
).to eq("I am user submission")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "saves a guest's submission" do
|
||||||
|
CustomWizard::Template.save(template_json, skip_jobs: true)
|
||||||
|
@wizard = CustomWizard::Wizard.create(template_json["id"], nil, guest_id)
|
||||||
|
described_class.new(@wizard, step_1_field_1: "I am guest submission").save
|
||||||
|
|
||||||
|
expect(
|
||||||
|
described_class.get(@wizard).fields["step_1_field_1"]
|
||||||
|
).to eq("I am guest submission")
|
||||||
|
end
|
||||||
|
|
||||||
describe "#list" do
|
describe "#list" do
|
||||||
before do
|
before do
|
||||||
freeze_time Time.now
|
freeze_time Time.now
|
||||||
|
@ -37,14 +48,17 @@ describe CustomWizard::Submission do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "list submissions by wizard" do
|
it "list submissions by wizard" do
|
||||||
|
@wizard.user = nil
|
||||||
expect(described_class.list(@wizard).total).to eq(@count + 2)
|
expect(described_class.list(@wizard).total).to eq(@count + 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "list submissions by wizard and user" do
|
it "list submissions by wizard and user" do
|
||||||
expect(described_class.list(@wizard, user_id: user.id).total).to eq(@count + 1)
|
@wizard.user = user
|
||||||
|
expect(described_class.list(@wizard).total).to eq(@count + 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "paginates submission lists" do
|
it "paginates submission lists" do
|
||||||
|
@wizard.user = nil
|
||||||
expect(described_class.list(@wizard, page: 1).submissions.size).to eq((@count + 2) - CustomWizard::Submission::PAGE_LIMIT)
|
expect(described_class.list(@wizard, page: 1).submissions.size).to eq((@count + 2) - CustomWizard::Submission::PAGE_LIMIT)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -59,7 +73,7 @@ describe CustomWizard::Submission do
|
||||||
described_class.new(@wizard, step_1_field_1: "I am the second submission").save
|
described_class.new(@wizard, step_1_field_1: "I am the second submission").save
|
||||||
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
|
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
|
||||||
builder.build
|
builder.build
|
||||||
submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions
|
submissions = described_class.list(@wizard).submissions
|
||||||
|
|
||||||
expect(submissions.length).to eq(1)
|
expect(submissions.length).to eq(1)
|
||||||
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the second submission")
|
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the second submission")
|
||||||
|
@ -75,7 +89,7 @@ describe CustomWizard::Submission do
|
||||||
PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, sub_data)
|
PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, sub_data)
|
||||||
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
|
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
|
||||||
builder.build
|
builder.build
|
||||||
submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions
|
submissions = described_class.list(@wizard).submissions
|
||||||
|
|
||||||
expect(submissions.length).to eq(1)
|
expect(submissions.length).to eq(1)
|
||||||
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the second submission")
|
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the second submission")
|
||||||
|
@ -92,7 +106,7 @@ describe CustomWizard::Submission do
|
||||||
|
|
||||||
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
|
builder = CustomWizard::Builder.new(@wizard.id, @wizard.user)
|
||||||
builder.build
|
builder.build
|
||||||
submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions
|
submissions = described_class.list(@wizard).submissions
|
||||||
|
|
||||||
expect(submissions.length).to eq(1)
|
expect(submissions.length).to eq(1)
|
||||||
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the third submission")
|
expect(submissions.first.fields["step_1_field_1"]).to eq("I am the third submission")
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
describe CustomWizard::Subscription do
|
describe CustomWizard::Subscription do
|
||||||
|
let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
|
||||||
|
|
||||||
def undefine_client_classes
|
def undefine_client_classes
|
||||||
Object.send(:remove_const, :SubscriptionClient) if Object.constants.include?(:SubscriptionClient)
|
Object.send(:remove_const, :SubscriptionClient) if Object.constants.include?(:SubscriptionClient)
|
||||||
Object.send(:remove_const, :SubscriptionClientSubscription) if Object.constants.include?(:SubscriptionClientSubscription)
|
Object.send(:remove_const, :SubscriptionClientSubscription) if Object.constants.include?(:SubscriptionClientSubscription)
|
||||||
|
@ -40,7 +42,7 @@ describe CustomWizard::Subscription do
|
||||||
expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true)
|
expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "ubscriber features are not included" do
|
it "subscriber features are not included" do
|
||||||
expect(described_class.includes?(:wizard, :permitted, {})).to eq(false)
|
expect(described_class.includes?(:wizard, :permitted, {})).to eq(false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -69,6 +71,16 @@ describe CustomWizard::Subscription do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with a subscription" do
|
||||||
|
it "handles mapped values" do
|
||||||
|
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID)
|
||||||
|
expect(described_class.includes?(:wizard, :permitted, guests_permitted["permitted"])).to eq(true)
|
||||||
|
|
||||||
|
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::COMMUNITY_PRODUCT_ID)
|
||||||
|
expect(described_class.includes?(:wizard, :permitted, guests_permitted["permitted"])).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "with standard subscription" do
|
context "with standard subscription" do
|
||||||
before do
|
before do
|
||||||
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID)
|
SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID)
|
||||||
|
|
|
@ -7,6 +7,8 @@ describe CustomWizard::TemplateValidator do
|
||||||
let(:user_condition) { get_wizard_fixture("condition/user_condition") }
|
let(:user_condition) { get_wizard_fixture("condition/user_condition") }
|
||||||
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
|
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
|
||||||
let(:composer_preview) { get_wizard_fixture("field/composer_preview") }
|
let(:composer_preview) { get_wizard_fixture("field/composer_preview") }
|
||||||
|
let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
|
||||||
|
let(:upload_field) { get_wizard_fixture("field/upload") }
|
||||||
|
|
||||||
let(:valid_liquid_template) {
|
let(:valid_liquid_template) {
|
||||||
<<-LIQUID.strip
|
<<-LIQUID.strip
|
||||||
|
@ -146,6 +148,20 @@ describe CustomWizard::TemplateValidator do
|
||||||
).to eq(true)
|
).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "validates user-only features" do
|
||||||
|
template[:permitted] = guests_permitted['permitted']
|
||||||
|
template[:steps][0][:fields] << upload_field
|
||||||
|
validator = CustomWizard::TemplateValidator.new(template)
|
||||||
|
expect(validator.perform).to eq(false)
|
||||||
|
errors = validator.errors.to_a
|
||||||
|
expect(errors).to include(
|
||||||
|
I18n.t("wizard.validation.not_permitted_for_guests", object_id: "action_1")
|
||||||
|
)
|
||||||
|
expect(errors).to include(
|
||||||
|
I18n.t("wizard.validation.not_permitted_for_guests", object_id: "step_2_field_7")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
it "validates step attributes" do
|
it "validates step attributes" do
|
||||||
template[:steps][0][:condition] = user_condition['condition']
|
template[:steps][0][:condition] = user_condition['condition']
|
||||||
expect(
|
expect(
|
||||||
|
|
|
@ -6,11 +6,14 @@ describe CustomWizard::Wizard do
|
||||||
fab!(:admin_user) { Fabricate(:user, admin: true) }
|
fab!(:admin_user) { Fabricate(:user, admin: true) }
|
||||||
let(:template_json) { get_wizard_fixture("wizard") }
|
let(:template_json) { get_wizard_fixture("wizard") }
|
||||||
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
|
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
|
||||||
|
let(:guests_permitted_json) { get_wizard_fixture("wizard/guests_permitted") }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
Group.refresh_automatic_group!(:trust_level_3)
|
Group.refresh_automatic_group!(:trust_level_3)
|
||||||
@permitted_template = template_json.dup
|
@permitted_template = template_json.dup
|
||||||
@permitted_template["permitted"] = permitted_json["permitted"]
|
@permitted_template["permitted"] = permitted_json["permitted"]
|
||||||
|
@guests_permitted_template = template_json.dup
|
||||||
|
@guests_permitted_template["permitted"] = guests_permitted_json["permitted"]
|
||||||
@wizard = CustomWizard::Wizard.new(template_json, user)
|
@wizard = CustomWizard::Wizard.new(template_json, user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -21,10 +24,10 @@ describe CustomWizard::Wizard do
|
||||||
@wizard.update!
|
@wizard.update!
|
||||||
end
|
end
|
||||||
|
|
||||||
def progress_step(step_id, acting_user: user, wizard: @wizard)
|
def progress_step(step_id, actor_id: user.id, wizard: @wizard)
|
||||||
UserHistory.create(
|
CustomWizard::UserHistory.create(
|
||||||
action: UserHistory.actions[:custom_wizard_step],
|
action: CustomWizard::UserHistory.actions[:step],
|
||||||
acting_user_id: acting_user.id,
|
actor_id: actor_id,
|
||||||
context: wizard.id,
|
context: wizard.id,
|
||||||
subject: step_id
|
subject: step_id
|
||||||
)
|
)
|
||||||
|
@ -158,9 +161,9 @@ describe CustomWizard::Wizard do
|
||||||
it "lets a permitted user access a complete wizard with multiple submissions" do
|
it "lets a permitted user access a complete wizard with multiple submissions" do
|
||||||
append_steps
|
append_steps
|
||||||
|
|
||||||
progress_step("step_1", acting_user: trusted_user)
|
progress_step("step_1", actor_id: trusted_user.id)
|
||||||
progress_step("step_2", acting_user: trusted_user)
|
progress_step("step_2", actor_id: trusted_user.id)
|
||||||
progress_step("step_3", acting_user: trusted_user)
|
progress_step("step_3", actor_id: trusted_user.id)
|
||||||
|
|
||||||
@permitted_template["multiple_submissions"] = true
|
@permitted_template["multiple_submissions"] = true
|
||||||
|
|
||||||
|
@ -172,9 +175,9 @@ describe CustomWizard::Wizard do
|
||||||
it "does not let an unpermitted user access a complete wizard without multiple submissions" do
|
it "does not let an unpermitted user access a complete wizard without multiple submissions" do
|
||||||
append_steps
|
append_steps
|
||||||
|
|
||||||
progress_step("step_1", acting_user: trusted_user)
|
progress_step("step_1", actor_id: trusted_user.id)
|
||||||
progress_step("step_2", acting_user: trusted_user)
|
progress_step("step_2", actor_id: trusted_user.id)
|
||||||
progress_step("step_3", acting_user: trusted_user)
|
progress_step("step_3", actor_id: trusted_user.id)
|
||||||
|
|
||||||
@permitted_template['multiple_submissions'] = false
|
@permitted_template['multiple_submissions'] = false
|
||||||
|
|
||||||
|
@ -200,6 +203,30 @@ describe CustomWizard::Wizard do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with subscription and guest wizard" do
|
||||||
|
before do
|
||||||
|
enable_subscription("standard")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "permits admins" do
|
||||||
|
expect(
|
||||||
|
CustomWizard::Wizard.new(@guests_permitted_template, admin_user).permitted?
|
||||||
|
).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "permits regular users" do
|
||||||
|
expect(
|
||||||
|
CustomWizard::Wizard.new(@guests_permitted_template, user).permitted?
|
||||||
|
).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "permits guests" do
|
||||||
|
expect(
|
||||||
|
CustomWizard::Wizard.new(@guests_permitted_template, nil, "guest123").permitted?
|
||||||
|
).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "submissions" do
|
context "submissions" do
|
||||||
before do
|
before do
|
||||||
CustomWizard::Submission.new(@wizard, step_1_field_1: "I am a user submission").save
|
CustomWizard::Submission.new(@wizard, step_1_field_1: "I am a user submission").save
|
||||||
|
|
67
spec/extensions/discourse_tagging_spec.rb
Normale Datei
67
spec/extensions/discourse_tagging_spec.rb
Normale Datei
|
@ -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
|
|
@ -1,46 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
describe ::TagsController, type: :request do
|
|
||||||
fab!(:tag_1) { Fabricate(:tag, name: "Angus") }
|
|
||||||
fab!(:tag_2) { Fabricate(:tag, name: "Faizaan") }
|
|
||||||
fab!(:tag_3) { Fabricate(:tag, name: "Robert") }
|
|
||||||
fab!(:tag_4) { Fabricate(:tag, name: "Eli") }
|
|
||||||
fab!(:tag_5) { Fabricate(:tag, name: "Jeff") }
|
|
||||||
|
|
||||||
fab!(:tag_group_1) { Fabricate(:tag_group, tags: [tag_1, tag_2]) }
|
|
||||||
fab!(:tag_group_2) { Fabricate(:tag_group, tags: [tag_3, tag_4]) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
::RequestStore.store[:tag_groups] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "#search" do
|
|
||||||
context "tag group param present" do
|
|
||||||
it "returns tags only in the tag group" do
|
|
||||||
get "/tags/filter/search.json", params: { q: '', tag_groups: [tag_group_1.name, tag_group_2.name] }
|
|
||||||
expect(response.status).to eq(200)
|
|
||||||
results = response.parsed_body['results']
|
|
||||||
names = results.map { |result| result['name'] }
|
|
||||||
|
|
||||||
expected_tag_names = TagGroup
|
|
||||||
.includes(:tags)
|
|
||||||
.where(id: [tag_group_1.id, tag_group_2.id])
|
|
||||||
.map { |tag_group| tag_group.tags.pluck(:name) }.flatten
|
|
||||||
|
|
||||||
expect(names).to contain_exactly(*expected_tag_names)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "tag group param not present" do
|
|
||||||
it "returns all tags" do
|
|
||||||
get "/tags/filter/search.json", params: { q: '' }
|
|
||||||
expect(response.status).to eq(200)
|
|
||||||
results = response.parsed_body['results']
|
|
||||||
names = results.map { |result| result['name'] }
|
|
||||||
|
|
||||||
all_tag_names = Tag.all.pluck(:name)
|
|
||||||
expect(names).to contain_exactly(*all_tag_names)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
12
spec/fixtures/actions/route_to.json
gevendort
Normale Datei
12
spec/fixtures/actions/route_to.json
gevendort
Normale Datei
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"id": "route_to",
|
||||||
|
"type": "route_to",
|
||||||
|
"url": [
|
||||||
|
{
|
||||||
|
"type": "assignment",
|
||||||
|
"output": "https://google.com",
|
||||||
|
"output_type": "text",
|
||||||
|
"output_connector": "set"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
6
spec/fixtures/field/upload.json
gevendort
Normale Datei
6
spec/fixtures/field/upload.json
gevendort
Normale Datei
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"id": "step_2_field_7",
|
||||||
|
"label": "Upload",
|
||||||
|
"type": "upload",
|
||||||
|
"file_types": ".jpg,.jpeg,.png"
|
||||||
|
}
|
6
spec/fixtures/wizard.json
gevendort
6
spec/fixtures/wizard.json
gevendort
|
@ -74,12 +74,6 @@
|
||||||
"id": "step_2_field_5",
|
"id": "step_2_field_5",
|
||||||
"label": "Checkbox",
|
"label": "Checkbox",
|
||||||
"type": "checkbox"
|
"type": "checkbox"
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "step_2_field_7",
|
|
||||||
"label": "Upload",
|
|
||||||
"type": "upload",
|
|
||||||
"file_types": ".jpg,.jpeg,.png"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Because I couldn't think of another name for this step :)"
|
"description": "Because I couldn't think of another name for this step :)"
|
||||||
|
|
12
spec/fixtures/wizard/guests_permitted.json
gevendort
Normale Datei
12
spec/fixtures/wizard/guests_permitted.json
gevendort
Normale Datei
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"permitted": [
|
||||||
|
{
|
||||||
|
"type": "assignment",
|
||||||
|
"output_type": "group",
|
||||||
|
"output_connector": "set",
|
||||||
|
"output": [
|
||||||
|
-1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ describe CustomWizard::AdminManagerController do
|
||||||
template_3["id"] = 'super_mega_fun_wizard_3'
|
template_3["id"] = 'super_mega_fun_wizard_3'
|
||||||
@template_array = [template, template_2, template_3]
|
@template_array = [template, template_2, template_3]
|
||||||
|
|
||||||
FileUtils.mkdir_p(file_from_fixtures_tmp_folder) unless Dir.exists?(file_from_fixtures_tmp_folder)
|
FileUtils.mkdir_p(file_from_fixtures_tmp_folder) unless Dir.exist?(file_from_fixtures_tmp_folder)
|
||||||
@tmp_file_path = File.join(file_from_fixtures_tmp_folder, SecureRandom.hex << 'wizards.json')
|
@tmp_file_path = File.join(file_from_fixtures_tmp_folder, SecureRandom.hex << 'wizards.json')
|
||||||
File.write(@tmp_file_path, @template_array.to_json)
|
File.write(@tmp_file_path, @template_array.to_json)
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,9 +7,111 @@ describe CustomWizard::StepsController do
|
||||||
let(:wizard_field_condition_template) { get_wizard_fixture("condition/wizard_field_condition") }
|
let(:wizard_field_condition_template) { get_wizard_fixture("condition/wizard_field_condition") }
|
||||||
let(:user_condition_template) { get_wizard_fixture("condition/user_condition") }
|
let(:user_condition_template) { get_wizard_fixture("condition/user_condition") }
|
||||||
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
|
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
|
||||||
|
let(:route_to_template) { get_wizard_fixture("actions/route_to") }
|
||||||
|
let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
CustomWizard::Template.save(wizard_template, skip_jobs: true)
|
CustomWizard::Template.save(wizard_template, skip_jobs: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def guest_template
|
||||||
|
temp = wizard_template.dup
|
||||||
|
temp["permitted"] = guests_permitted["permitted"]
|
||||||
|
temp.delete("actions")
|
||||||
|
temp["actions"] = [route_to_template]
|
||||||
|
temp
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with guest" do
|
||||||
|
it "does not perform a step update" do
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
|
||||||
|
fields: {
|
||||||
|
step_1_field_1: "Text input"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with guests permitted" do
|
||||||
|
before do
|
||||||
|
enable_subscription("standard")
|
||||||
|
result = CustomWizard::Template.save(guest_template, skip_jobs: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "performs a step update" do
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
|
||||||
|
fields: {
|
||||||
|
step_1_field_1: "Text input"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.parsed_body['wizard']['start']).to eq("step_2")
|
||||||
|
|
||||||
|
wizard_id = response.parsed_body['wizard']['id']
|
||||||
|
wizard = CustomWizard::Wizard.create(wizard_id, nil, cookies[:custom_wizard_guest_id])
|
||||||
|
expect(wizard.current_submission.fields['step_1_field_1']).to eq("Text input")
|
||||||
|
end
|
||||||
|
|
||||||
|
context "raises an error" do
|
||||||
|
it "when the wizard doesnt exist" do
|
||||||
|
put '/w/not-super-mega-fun-wizard/steps/step_1.json'
|
||||||
|
expect(response.status).to eq(400)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "when the user cant access the wizard" do
|
||||||
|
enable_subscription("standard")
|
||||||
|
new_template = guest_template.dup
|
||||||
|
new_template["permitted"] = permitted_json["permitted"]
|
||||||
|
CustomWizard::Template.save(new_template, skip_jobs: true)
|
||||||
|
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_1.json'
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "when the step doesnt exist" do
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_10.json'
|
||||||
|
expect(response.status).to eq(400)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "works if the step has no fields" do
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_1.json'
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.parsed_body['wizard']['start']).to eq("step_2")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an updated wizard when condition passes" do
|
||||||
|
new_template = guest_template.dup
|
||||||
|
new_template['steps'][1]['condition'] = wizard_field_condition_template['condition']
|
||||||
|
CustomWizard::Template.save(new_template, skip_jobs: true)
|
||||||
|
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
|
||||||
|
fields: {
|
||||||
|
step_1_field_1: "Condition will pass"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.parsed_body['wizard']['start']).to eq("step_2")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "runs completion actions if guest has completed wizard" do
|
||||||
|
new_template = guest_template.dup
|
||||||
|
|
||||||
|
## route_to action
|
||||||
|
new_template['actions'].last['run_after'] = 'wizard_completion'
|
||||||
|
CustomWizard::Template.save(new_template, skip_jobs: true)
|
||||||
|
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_1.json'
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_2.json'
|
||||||
|
put '/w/super-mega-fun-wizard/steps/step_3.json'
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.parsed_body['redirect_on_complete']).to eq("https://google.com")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with user" do
|
||||||
|
before do
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -266,4 +368,5 @@ describe CustomWizard::StepsController do
|
||||||
expect(submission.fields.keys).not_to include("step_2_field_1")
|
expect(submission.fields.keys).not_to include("step_2_field_1")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,6 @@ describe CustomWizard::WizardController do
|
||||||
before do
|
before do
|
||||||
CustomWizard::Template.save(wizard_template, skip_jobs: true)
|
CustomWizard::Template.save(wizard_template, skip_jobs: true)
|
||||||
@template = CustomWizard::Template.find("super_mega_fun_wizard")
|
@template = CustomWizard::Template.find("super_mega_fun_wizard")
|
||||||
sign_in(user)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'plugin disabled' do
|
context 'plugin disabled' do
|
||||||
|
@ -32,8 +31,12 @@ describe CustomWizard::WizardController do
|
||||||
expect(response.parsed_body["error"]).to eq("We couldn't find a wizard at that address.")
|
expect(response.parsed_body["error"]).to eq("We couldn't find a wizard at that address.")
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when user skips the wizard' do
|
context "with user" do
|
||||||
|
before do
|
||||||
|
sign_in(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user skips' do
|
||||||
it 'skips a wizard if user is allowed to skip' do
|
it 'skips a wizard if user is allowed to skip' do
|
||||||
put '/w/super-mega-fun-wizard/skip.json'
|
put '/w/super-mega-fun-wizard/skip.json'
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
|
@ -93,4 +96,5 @@ describe CustomWizard::WizardController do
|
||||||
expect(response.parsed_body["start"]).to eq('step_1')
|
expect(response.parsed_body["start"]).to eq('step_1')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,6 +29,5 @@ describe CustomWizard::FieldSerializer do
|
||||||
scope: Guardian.new(user)
|
scope: Guardian.new(user)
|
||||||
).as_json
|
).as_json
|
||||||
expect(json_array[0][:format]).to eq("YYYY-MM-DD")
|
expect(json_array[0][:format]).to eq("YYYY-MM-DD")
|
||||||
expect(json_array[5][:file_types]).to eq(".jpg,.jpeg,.png")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -54,6 +54,70 @@ acceptance("Field | Fields", function (needs) {
|
||||||
"Input in composer"
|
"Input in composer"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
test("Composer - Hyperlink", async function (assert) {
|
||||||
|
await visit("/w/wizard");
|
||||||
|
assert.ok(
|
||||||
|
visible(".wizard-field.composer-field .wizard-field-composer textarea")
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
exists(".wizard-field.composer-field .d-editor-button-bar button")
|
||||||
|
);
|
||||||
|
assert.ok(visible(".wizard-btn.toggle-preview"));
|
||||||
|
await fillIn(
|
||||||
|
".wizard-field.composer-field .wizard-field-composer textarea",
|
||||||
|
"This is a link to "
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
!exists(".insert-link.modal-body"),
|
||||||
|
"no hyperlink modal by default"
|
||||||
|
);
|
||||||
|
await click(
|
||||||
|
".wizard-field.composer-field .wizard-field-composer .d-editor button.link"
|
||||||
|
);
|
||||||
|
assert.ok(exists(".insert-link.modal-body"), "hyperlink modal visible");
|
||||||
|
|
||||||
|
await fillIn(".modal-body .link-url", "google.com");
|
||||||
|
await fillIn(".modal-body .link-text", "Google");
|
||||||
|
await click(".modal-footer button.btn-primary");
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
query(".wizard-field.composer-field .wizard-field-composer textarea")
|
||||||
|
.value,
|
||||||
|
"This is a link to [Google](https://google.com)",
|
||||||
|
"adds link with url and text, prepends 'https://'"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
!exists(
|
||||||
|
".wizard-field.composer-field .wizard-field-composer .insert-link.modal-body"
|
||||||
|
),
|
||||||
|
"modal dismissed after submitting link"
|
||||||
|
);
|
||||||
|
|
||||||
|
await fillIn(
|
||||||
|
".wizard-field.composer-field .wizard-field-composer textarea",
|
||||||
|
"Reset textarea contents."
|
||||||
|
);
|
||||||
|
|
||||||
|
await click(
|
||||||
|
".wizard-field.composer-field .wizard-field-composer .d-editor button.link"
|
||||||
|
);
|
||||||
|
await fillIn(".modal-body .link-url", "google.com");
|
||||||
|
await fillIn(".modal-body .link-text", "Google");
|
||||||
|
await click(".modal-footer button.btn-danger");
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
query(".wizard-field.composer-field .wizard-field-composer textarea")
|
||||||
|
.value,
|
||||||
|
"Reset textarea contents.",
|
||||||
|
"does not insert anything after cancelling"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
!exists(".insert-link.modal-body"),
|
||||||
|
"modal dismissed after cancelling"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("Text Only", async function (assert) {
|
test("Text Only", async function (assert) {
|
||||||
await visit("/w/wizard");
|
await visit("/w/wizard");
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
import {
|
import {
|
||||||
wizard,
|
wizard,
|
||||||
wizardCompleted,
|
wizardCompleted,
|
||||||
|
wizardGuest,
|
||||||
wizardNoUser,
|
wizardNoUser,
|
||||||
wizardNotPermitted,
|
wizardNotPermitted,
|
||||||
} from "../helpers/wizard";
|
} from "../helpers/wizard";
|
||||||
|
@ -106,3 +107,59 @@ acceptance("Wizard | Wizard", function (needs) {
|
||||||
assert.strictEqual($("body.custom-wizard").length, 0);
|
assert.strictEqual($("body.custom-wizard").length, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
acceptance("Wizard | Guest access", function (needs) {
|
||||||
|
needs.pretender((server, helper) => {
|
||||||
|
server.get("/w/wizard.json", () => helper.response(wizardGuest));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Does not require login", async function (assert) {
|
||||||
|
await visit("/w/wizard");
|
||||||
|
assert.ok(!exists(".wizard-no-access.requires-login"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Starts", async function (assert) {
|
||||||
|
await visit("/w/wizard");
|
||||||
|
assert.ok(query(".wizard-column"), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Applies the wizard body class", async function (assert) {
|
||||||
|
await visit("/w/wizard");
|
||||||
|
assert.ok($("body.custom-wizard").length);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Applies the body background color", async function (assert) {
|
||||||
|
await visit("/w/wizard");
|
||||||
|
assert.ok($("body")[0].style.background);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Renders the wizard form", async function (assert) {
|
||||||
|
await visit("/w/wizard");
|
||||||
|
assert.ok(exists(".wizard-column-contents .wizard-step"), true);
|
||||||
|
assert.ok(exists(".wizard-footer img"), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Renders the first step", async function (assert) {
|
||||||
|
await visit("/w/wizard");
|
||||||
|
assert.strictEqual(
|
||||||
|
query(".wizard-step-title p").textContent.trim(),
|
||||||
|
"Text"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
query(".wizard-step-description p").textContent.trim(),
|
||||||
|
"Text inputs!"
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
query(".wizard-step-description p").textContent.trim(),
|
||||||
|
"Text inputs!"
|
||||||
|
);
|
||||||
|
assert.strictEqual(count(".wizard-step-form .wizard-field"), 6);
|
||||||
|
assert.ok(exists(".wizard-step-footer .wizard-progress"), true);
|
||||||
|
assert.ok(exists(".wizard-step-footer .wizard-buttons"), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Removes the wizard body class when navigating away", async function (assert) {
|
||||||
|
await visit("/");
|
||||||
|
assert.strictEqual($("body.custom-wizard").length, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -6,7 +6,7 @@ export default {
|
||||||
submission_last_updated_at: "2022-03-15T21:11:01+01:00",
|
submission_last_updated_at: "2022-03-15T21:11:01+01:00",
|
||||||
theme_id: 2,
|
theme_id: 2,
|
||||||
required: false,
|
required: false,
|
||||||
permitted: true,
|
permitted: false,
|
||||||
uncategorized_category_id: 1,
|
uncategorized_category_id: 1,
|
||||||
categories: [],
|
categories: [],
|
||||||
subscribed: false,
|
subscribed: false,
|
||||||
|
|
|
@ -6,8 +6,11 @@ import updateJson from "../fixtures/update";
|
||||||
import { cloneJSON } from "discourse-common/lib/object";
|
import { cloneJSON } from "discourse-common/lib/object";
|
||||||
|
|
||||||
const wizardNoUser = cloneJSON(wizardJson);
|
const wizardNoUser = cloneJSON(wizardJson);
|
||||||
|
const wizardGuest = cloneJSON(wizardJson);
|
||||||
|
wizardGuest.permitted = true;
|
||||||
const wizard = cloneJSON(wizardJson);
|
const wizard = cloneJSON(wizardJson);
|
||||||
wizard.user = cloneJSON(userJson);
|
wizard.user = cloneJSON(userJson);
|
||||||
|
wizard.permitted = true;
|
||||||
|
|
||||||
const wizardNotPermitted = cloneJSON(wizard);
|
const wizardNotPermitted = cloneJSON(wizard);
|
||||||
wizardNotPermitted.permitted = false;
|
wizardNotPermitted.permitted = false;
|
||||||
|
@ -40,6 +43,7 @@ export {
|
||||||
wizardNoUser,
|
wizardNoUser,
|
||||||
wizardNotPermitted,
|
wizardNotPermitted,
|
||||||
wizardCompleted,
|
wizardCompleted,
|
||||||
|
wizardGuest,
|
||||||
stepNotPermitted,
|
stepNotPermitted,
|
||||||
allFieldsWizard,
|
allFieldsWizard,
|
||||||
wizard,
|
wizard,
|
||||||
|
|
Laden …
In neuem Issue referenzieren