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