diff --git a/.github/workflows/plugin-linting.yml b/.github/workflows/plugin-linting.yml
index a121658d..a79c5462 100644
--- a/.github/workflows/plugin-linting.yml
+++ b/.github/workflows/plugin-linting.yml
@@ -6,6 +6,8 @@ on:
- master
- main
pull_request:
+ schedule:
+ - cron: '0 0 * * *'
jobs:
build:
diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml
index ce6112af..84e055ca 100644
--- a/.github/workflows/plugin-tests.yml
+++ b/.github/workflows/plugin-tests.yml
@@ -6,6 +6,8 @@ on:
- master
- main
pull_request:
+ schedule:
+ - cron: '0 0 * * *'
jobs:
build:
@@ -51,23 +53,26 @@ jobs:
repository: discourse/discourse
fetch-depth: 1
+ - run: echo "REPOSITORY_NAME=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV
+ shell: bash
+
- name: Install plugin
uses: actions/checkout@v2
with:
- path: plugins/${{ github.event.repository.name }}
+ path: plugins/${{ env.REPOSITORY_NAME }}
fetch-depth: 1
- name: Check spec existence
id: check_spec
uses: andstor/file-existence-action@v1
with:
- files: "plugins/${{ github.event.repository.name }}/spec"
+ files: "plugins/${{ env.REPOSITORY_NAME }}/spec"
- name: Check qunit existence
id: check_qunit
uses: andstor/file-existence-action@v1
with:
- files: "plugins/${{ github.event.repository.name }}/test/javascripts"
+ files: "plugins/${{ env.REPOSITORY_NAME }}/test/javascripts"
- name: Setup Git
run: |
@@ -100,7 +105,7 @@ jobs:
- 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"
+ run: bundle exec ruby script/i18n_lint.rb "plugins/${{ env.REPOSITORY_NAME }}/locales/{client,server}.en.yml"
- name: Get yarn cache directory
id: yarn-cache-dir
@@ -123,15 +128,11 @@ jobs:
bin/rake db:create
bin/rake db:migrate
- - name: Plugin RSpec
+ - name: Plugin RSpec with Coverage
if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exists == 'true'
- run: bin/rake plugin:spec[${{ github.event.repository.name }}]
+ run: SIMPLECOV=1 bin/rake plugin:spec[${{ env.REPOSITORY_NAME }}]
- name: Plugin QUnit
if: matrix.build_type == 'frontend' && steps.check_qunit.outputs.files_exists == 'true'
- run: bundle exec rake plugin:qunit['${{ github.event.repository.name }}','1200000']
+ run: bundle exec rake plugin:qunit['${{ env.REPOSITORY_NAME }}','1200000']
timeout-minutes: 30
-
- - name: Simplecov Report
- if: matrix.build_type == 'backend'
- run: COVERAGE=1 bin/rake plugin:spec[${{ github.event.repository.name }}]
diff --git a/assets/javascripts/discourse/components/custom-field-input.js.es6 b/assets/javascripts/discourse/components/custom-field-input.js.es6
index f2dca4c7..e49c6f1d 100644
--- a/assets/javascripts/discourse/components/custom-field-input.js.es6
+++ b/assets/javascripts/discourse/components/custom-field-input.js.es6
@@ -1,6 +1,6 @@
import Component from "@ember/component";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
-import { alias, or } from "@ember/object/computed";
+import { alias, equal, or } from "@ember/object/computed";
import I18n from "I18n";
const generateContent = function (array, type) {
@@ -29,6 +29,7 @@ export default Component.extend({
loading: or("saving", "destroying"),
destroyDisabled: alias("loading"),
closeDisabled: alias("loading"),
+ isExternal: equal("field.id", "external"),
didInsertElement() {
this.set("originalField", JSON.parse(JSON.stringify(this.field)));
@@ -61,13 +62,14 @@ export default Component.extend({
@discourseComputed(
"saving",
+ "isExternal",
"field.name",
"field.klass",
"field.type",
"field.serializers"
)
- saveDisabled(saving) {
- if (saving) {
+ saveDisabled(saving, isExternal) {
+ if (saving || isExternal) {
return true;
}
diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6
index c8309f10..feb83754 100644
--- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6
+++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6
@@ -62,6 +62,11 @@ export default Component.extend(UndoChanges, {
return key;
},
+ @discourseComputed("action.type")
+ customFieldsContext(type) {
+ return `action.${type}`;
+ },
+
@discourseComputed("wizard.steps")
runAfterContent(steps) {
let content = steps.map(function (step) {
diff --git a/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 b/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6
index 6d65d782..7d9b0bbd 100644
--- a/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6
+++ b/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6
@@ -6,11 +6,24 @@ import {
} from "discourse-common/utils/decorators";
import { getOwner } from "discourse-common/lib/get-owner";
import { defaultSelectionType, selectionTypes } from "../lib/wizard-mapper";
-import { generateName, snakeCase, userProperties } from "../lib/wizard";
+import {
+ generateName,
+ sentenceCase,
+ snakeCase,
+ userProperties,
+} from "../lib/wizard";
import Component from "@ember/component";
import { bind, later } from "@ember/runloop";
import I18n from "I18n";
+const customFieldActionMap = {
+ topic: ["create_topic", "send_message"],
+ post: ["create_topic", "send_message"],
+ category: ["create_category"],
+ group: ["create_group"],
+ user: ["update_profile"],
+};
+
export default Component.extend({
classNameBindings: [":mapper-selector", "activeType"],
@@ -188,11 +201,19 @@ export default Component.extend({
customFields
) {
let content;
+ let context;
+ let contextType;
+
+ if (this.options.context) {
+ let contextAttrs = this.options.context.split(".");
+ context = contextAttrs[0];
+ contextType = contextAttrs[1];
+ }
if (activeType === "wizardField") {
content = wizardFields;
- if (this.options.context === "field") {
+ if (context === "field") {
content = content.filter((field) => field.id !== currentFieldId);
}
}
@@ -204,7 +225,7 @@ export default Component.extend({
type: a.type,
}));
- if (this.options.context === "action") {
+ if (context === "action") {
content = content.filter((a) => a.id !== currentActionId);
}
}
@@ -218,7 +239,7 @@ export default Component.extend({
.concat(userFields || []);
if (
- this.options.context === "action" &&
+ context === "action" &&
this.inputType === "association" &&
this.selectorType === "key"
) {
@@ -234,7 +255,17 @@ export default Component.extend({
}
if (activeType === "customField") {
- content = customFields;
+ content = customFields
+ .filter((f) => {
+ return (
+ f.type !== "json" &&
+ customFieldActionMap[f.klass].includes(contextType)
+ );
+ })
+ .map((f) => ({
+ id: f.name,
+ name: `${sentenceCase(f.klass)} ${f.name} (${f.type})`,
+ }));
}
return content;
diff --git a/assets/javascripts/discourse/controllers/admin-wizards-custom-fields.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-custom-fields.js.es6
index 2081cfe3..404c6afd 100644
--- a/assets/javascripts/discourse/controllers/admin-wizards-custom-fields.js.es6
+++ b/assets/javascripts/discourse/controllers/admin-wizards-custom-fields.js.es6
@@ -3,12 +3,12 @@ import CustomWizardCustomField from "../models/custom-wizard-custom-field";
export default Controller.extend({
messageKey: "create",
- fieldKeys: ["klass", "type", "serializers", "name"],
+ fieldKeys: ["klass", "type", "name", "serializers"],
documentationUrl: "https://thepavilion.io/t/3572",
actions: {
addField() {
- this.get("customFields").pushObject(
+ this.get("customFields").unshiftObject(
CustomWizardCustomField.create({ edit: true })
);
},
diff --git a/assets/javascripts/discourse/lib/wizard.js.es6 b/assets/javascripts/discourse/lib/wizard.js.es6
index 1896b1fe..98bdbfdd 100644
--- a/assets/javascripts/discourse/lib/wizard.js.es6
+++ b/assets/javascripts/discourse/lib/wizard.js.es6
@@ -120,4 +120,5 @@ export {
listProperties,
notificationLevels,
wizardFieldList,
+ sentenceCase,
};
diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6
index eaa6591c..cb2d54c3 100644
--- a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6
+++ b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6
@@ -2,7 +2,6 @@ import CustomWizard from "../models/custom-wizard";
import { ajax } from "discourse/lib/ajax";
import DiscourseRoute from "discourse/routes/discourse";
import I18n from "I18n";
-import { selectKitContent } from "../lib/wizard";
export default DiscourseRoute.extend({
model(params) {
@@ -33,9 +32,7 @@ export default DiscourseRoute.extend({
wizardList: parentModel.wizard_list,
fieldTypes,
userFields: parentModel.userFields,
- customFields: selectKitContent(
- parentModel.custom_fields.map((f) => f.name)
- ),
+ customFields: parentModel.custom_fields,
apis: parentModel.apis,
themes: parentModel.themes,
wizard,
diff --git a/assets/javascripts/discourse/templates/components/custom-field-input.hbs b/assets/javascripts/discourse/templates/components/custom-field-input.hbs
index 205b1644..43a97be8 100644
--- a/assets/javascripts/discourse/templates/components/custom-field-input.hbs
+++ b/assets/javascripts/discourse/templates/components/custom-field-input.hbs
@@ -13,6 +13,11 @@
none="admin.wizard.custom_field.type.select"
onChange=(action (mut field.type))}}
+
+ {{input
+ value=field.name
+ placeholder=(i18n "admin.wizard.custom_field.name.select")}}
+ |
{{multi-select
value=field.serializers
@@ -20,11 +25,6 @@
none="admin.wizard.custom_field.serializers.select"
onChange=(action (mut field.serializers))}}
|
-
- {{input
- value=field.name
- placeholder=(i18n "admin.wizard.custom_field.name.select")}}
- |
{{#if loading}}
{{loading-spinner size="small"}}
@@ -51,13 +51,25 @@
{{else}}
| |
|
-
- {{#each field.serializers as |serializer|}}
-
- {{/each}}
- |
|
-
- {{d-button action="edit" icon="pencil-alt"}}
+ |
+ {{#if isExternal}}
+ —
+ {{else}}
+ {{#each field.serializers as |serializer|}}
+
+ {{/each}}
+ {{/if}}
|
+ {{#if isExternal}}
+
+
+ |
+ {{else}}
+
+ {{d-button action="edit" icon="pencil-alt"}}
+ |
+ {{/if}}
{{/if}}
diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs
index f06e0d89..4c645cf7 100644
--- a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs
+++ b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs
@@ -738,7 +738,7 @@
wizardActionSelection="value"
userFieldSelection="value"
keyPlaceholder="admin.wizard.action.custom_fields.key"
- context="action"
+ context=customFieldsContext
)}}
diff --git a/assets/javascripts/wizard/custom-wizard.js.es6 b/assets/javascripts/wizard/custom-wizard.js.es6
index 63a9ea10..8c0a473c 100644
--- a/assets/javascripts/wizard/custom-wizard.js.es6
+++ b/assets/javascripts/wizard/custom-wizard.js.es6
@@ -4,6 +4,10 @@ export default Ember.Application.extend({
rootElement: "#custom-wizard-main",
Resolver: buildResolver("wizard"),
+ customEvents: {
+ paste: "paste",
+ },
+
start() {
Object.keys(requirejs._eak_seen).forEach((key) => {
if (/\/pre\-initializers\//.test(key)) {
diff --git a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6
index 3ede2b05..f5deb927 100644
--- a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6
+++ b/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6
@@ -15,7 +15,7 @@ export default {
);
const DEditor = requirejs("discourse/components/d-editor").default;
const { clipboardHelpers } = requirejs("discourse/lib/utilities");
- const { toMarkdown } = requirejs("discourse/lib/to-markdown");
+ const toMarkdown = requirejs("discourse/lib/to-markdown").default;
FieldComponent.reopen({
classNameBindings: ["field.id"],
@@ -181,7 +181,7 @@ export default {
markdown = pre.match(/\S$/) ? ` ${markdown}` : markdown;
}
- this.appEvents.trigger("composer:insert-text", {
+ this.appEvents.trigger("wizard-editor:insert-text", {
fieldId: this.fieldId,
text: markdown,
});
diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss
index 3c4b78da..9c2838eb 100644
--- a/assets/stylesheets/common/wizard-admin.scss
+++ b/assets/stylesheets/common/wizard-admin.scss
@@ -667,6 +667,10 @@
margin-left: 5px !important;
}
}
+
+ td.external {
+ font-style: italic;
+ }
}
}
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 0c364853..43b86698 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -75,7 +75,7 @@ en:
edit: "You're editing an action"
documentation: "Check out the action documentation"
custom_fields:
- create: "Create, edit or destroy a custom field record"
+ create: "View, create, edit and destroy custom fields"
saved: "Saved custom field"
error: "Failed to save: {{messages}}"
documentation: Check out the custom field documentation
@@ -322,6 +322,9 @@ en:
custom_field:
nav_label: "Custom Fields"
add: "Add"
+ external:
+ label: "from another plugin"
+ title: "This custom field has been added by another plugin. You can use it in your wizards but you can't edit the field here."
name:
label: "Name"
select: "underscored_name"
diff --git a/controllers/custom_wizard/admin/admin.rb b/controllers/custom_wizard/admin/admin.rb
index 8d5e3cad..c99954d6 100644
--- a/controllers/custom_wizard/admin/admin.rb
+++ b/controllers/custom_wizard/admin/admin.rb
@@ -14,7 +14,7 @@ class CustomWizard::AdminController < ::Admin::AdminController
end
def custom_field_list
- serialize_data(CustomWizard::CustomField.list, CustomWizard::CustomFieldSerializer)
+ serialize_data(CustomWizard::CustomField.full_list, CustomWizard::CustomFieldSerializer)
end
def render_error(message)
diff --git a/controllers/custom_wizard/steps.rb b/controllers/custom_wizard/steps.rb
index 277b94b2..aa4fbd7f 100644
--- a/controllers/custom_wizard/steps.rb
+++ b/controllers/custom_wizard/steps.rb
@@ -23,12 +23,12 @@ class CustomWizard::StepsController < ::ApplicationController
if updater.success?
wizard_id = update_params[:wizard_id]
builder = CustomWizard::Builder.new(wizard_id, current_user)
- @wizard = builder.build
+ @wizard = builder.build(force: true)
current_step = @wizard.find_step(update[:step_id])
current_submission = @wizard.current_submission
result = {}
-
+ @wizard.filter_conditional_fields
if current_step.conditional_final_step && !current_step.last_step
current_step.force_final = true
end
diff --git a/controllers/custom_wizard/wizard.rb b/controllers/custom_wizard/wizard.rb
index 37728ecb..9670fd62 100644
--- a/controllers/custom_wizard/wizard.rb
+++ b/controllers/custom_wizard/wizard.rb
@@ -61,7 +61,7 @@ class CustomWizard::WizardController < ::ApplicationController
result = success_json
user = current_user
- if user
+ if user && wizard.can_access?
submission = wizard.current_submission
if submission && submission['redirect_to']
result.merge!(redirect_to: submission['redirect_to'])
diff --git a/extensions/custom_field/extension.rb b/extensions/custom_field/extension.rb
new file mode 100644
index 00000000..876f56d4
--- /dev/null
+++ b/extensions/custom_field/extension.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+module CustomWizardCustomFieldExtension
+ def custom_field_types
+ @custom_field_types
+ end
+end
diff --git a/extensions/extra_locales_controller.rb b/extensions/extra_locales_controller.rb
index e7c5a02e..6242f7ca 100644
--- a/extensions/extra_locales_controller.rb
+++ b/extensions/extra_locales_controller.rb
@@ -4,7 +4,8 @@ module ExtraLocalesControllerCustomWizard
super || begin
return false unless bundle =~ /wizard/ && request.referer =~ /\/w\//
path = URI(request.referer).path
- wizard_id = path.split('/w/').last
+ wizard_path = path.split('/w/').last
+ wizard_id = wizard_path.split('/').first
CustomWizard::Template.exists?(wizard_id.underscore)
end
end
diff --git a/jobs/clear_after_time_wizard.rb b/jobs/clear_after_time_wizard.rb
deleted file mode 100644
index 37d997db..00000000
--- a/jobs/clear_after_time_wizard.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-module Jobs
- class ClearAfterTimeWizard < ::Jobs::Base
- sidekiq_options queue: 'critical'
-
- def execute(args)
- User.human_users.each do |u|
- if u.custom_fields['redirect_to_wizard'] == args[:wizard_id]
- u.custom_fields.delete('redirect_to_wizard')
- u.save_custom_fields(true)
- end
- end
- end
- end
-end
diff --git a/lib/custom_wizard/action.rb b/lib/custom_wizard/action.rb
index d68e978b..5388a326 100644
--- a/lib/custom_wizard/action.rb
+++ b/lib/custom_wizard/action.rb
@@ -454,32 +454,51 @@ class CustomWizard::Action
data: data,
user: user
).perform
-
- registered_fields = CustomWizard::CustomField.cached_list
+ registered_fields = CustomWizard::CustomField.full_list
field_map.each do |field|
keyArr = field[:key].split('.')
value = field[:value]
if keyArr.length > 1
- klass = keyArr.first
- name = keyArr.last
+ klass = keyArr.first.to_sym
+ name = keyArr.second
+
+ if keyArr.length === 3 && name.include?("{}")
+ name = name.gsub("{}", "")
+ json_attr = keyArr.last
+ type = :json
+ end
else
name = keyArr.first
end
- registered = registered_fields.select { |f| f[:name] == name }
- if registered.first.present?
- klass = registered.first[:klass]
+ registered = registered_fields.select { |f| f.name == name }.first
+ if registered.present?
+ klass = registered.klass
+ type = registered.type
end
- if klass === 'topic'
+ next if type === :json && json_attr.blank?
+
+ if klass === :topic
params[:topic_opts] ||= {}
params[:topic_opts][:custom_fields] ||= {}
- params[:topic_opts][:custom_fields][name] = value
+
+ if type === :json
+ params[:topic_opts][:custom_fields][name] ||= {}
+ params[:topic_opts][:custom_fields][name][json_attr] = value
+ else
+ params[:topic_opts][:custom_fields][name] = value
+ end
else
- params[:custom_fields] ||= {}
- params[:custom_fields][name] = value
+ if type === :json
+ params[:custom_fields][name] ||= {}
+ params[:custom_fields][name][json_attr] = value
+ else
+ params[:custom_fields] ||= {}
+ params[:custom_fields][name] = value
+ end
end
end
end
diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb
index 813680c6..a9fc6263 100644
--- a/lib/custom_wizard/builder.rb
+++ b/lib/custom_wizard/builder.rb
@@ -30,7 +30,7 @@ class CustomWizard::Builder
def build(build_opts = {}, params = {})
return nil if !SiteSetting.custom_wizard_enabled || !@wizard
- return @wizard if !@wizard.can_access?
+ return @wizard if !@wizard.can_access? && !build_opts[:force]
build_opts[:reset] = build_opts[:reset] || @wizard.restart_on_revisit
diff --git a/lib/custom_wizard/custom_field.rb b/lib/custom_wizard/custom_field.rb
index e3f01a1a..9cc185ba 100644
--- a/lib/custom_wizard/custom_field.rb
+++ b/lib/custom_wizard/custom_field.rb
@@ -66,10 +66,12 @@ class ::CustomWizard::CustomField
value = send(attr)
i18n_key = "wizard.custom_field.error"
- if value.blank?
- if REQUIRED.include?(attr)
- add_error(I18n.t("#{i18n_key}.required_attribute", attr: attr))
- end
+ if value.blank? && REQUIRED.include?(attr)
+ add_error(I18n.t("#{i18n_key}.required_attribute", attr: attr))
+ break
+ end
+
+ if attr == 'serializers' && !value.is_a?(Array)
next
end
@@ -140,7 +142,7 @@ class ::CustomWizard::CustomField
fields.select do |cf|
if attr == :serializers
- cf[attr].include?(value)
+ cf[attr] && cf[attr].include?(value)
else
cf[attr] == value
end
@@ -215,4 +217,32 @@ class ::CustomWizard::CustomField
def self.enabled?
any?
end
+
+ def self.external_list
+ external = []
+
+ CLASSES.keys.each do |klass|
+ field_types = klass.to_s.classify.constantize.custom_field_types
+
+ if field_types.present?
+ field_types.each do |name, type|
+ unless list.any? { |field| field.name === name }
+ field = new(
+ 'external',
+ name: name,
+ klass: klass,
+ type: type
+ )
+ external.push(field)
+ end
+ end
+ end
+ end
+
+ external
+ end
+
+ def self.full_list
+ (list + external_list).uniq
+ end
end
diff --git a/lib/custom_wizard/mapper.rb b/lib/custom_wizard/mapper.rb
index c1187b0f..0c3543cf 100644
--- a/lib/custom_wizard/mapper.rb
+++ b/lib/custom_wizard/mapper.rb
@@ -5,20 +5,27 @@ class CustomWizard::Mapper
USER_FIELDS = [
'name',
'username',
- 'email',
'date_of_birth',
'title',
'locale',
'trust_level',
+ 'email'
+ ]
+
+ USER_OPTION_FIELDS = [
'email_level',
'email_messages_level',
'email_digests'
]
- PROFILE_FIELDS = ['location', 'website', 'bio_raw']
+ PROFILE_FIELDS = [
+ 'location',
+ 'website',
+ 'bio_raw'
+ ]
def self.user_fields
- USER_FIELDS + PROFILE_FIELDS
+ USER_FIELDS + USER_OPTION_FIELDS + PROFILE_FIELDS
end
OPERATORS = {
@@ -197,11 +204,15 @@ class CustomWizard::Mapper
def map_user_field(value)
if value.include?(User::USER_FIELD_PREFIX)
- UserCustomField.where(user_id: user.id, name: value).pluck(:value).first
+ user.custom_fields[value]
elsif PROFILE_FIELDS.include?(value)
- UserProfile.find_by(user_id: user.id).send(value)
+ user.user_profile.send(value)
elsif USER_FIELDS.include?(value)
- User.find(user.id).send(value)
+ user.send(value)
+ elsif USER_OPTION_FIELDS.include?(value)
+ user.user_option.send(value)
+ else
+ nil
end
end
@@ -217,19 +228,11 @@ class CustomWizard::Mapper
return string if string.blank?
if opts[:user]
- string.gsub!(/u\{(.*?)\}/) do |match|
- result = ''
- result = user.send($1) if USER_FIELDS.include?($1)
- result = user.user_profile.send($1) if PROFILE_FIELDS.include?($1)
- result
- end
+ string.gsub!(/u\{(.*?)\}/) { |match| map_user_field($1) || '' }
end
if opts[:wizard]
- string.gsub!(/w\{(.*?)\}/) do |match|
- value = recurse(data, [*$1.split('.')])
- value.present? ? value : ''
- end
+ string.gsub!(/w\{(.*?)\}/) { |match| recurse(data, [*$1.split('.')]) || '' }
end
if opts[:value]
diff --git a/lib/custom_wizard/template.rb b/lib/custom_wizard/template.rb
index a1c0aad0..8e944dca 100644
--- a/lib/custom_wizard/template.rb
+++ b/lib/custom_wizard/template.rb
@@ -49,18 +49,15 @@ class CustomWizard::Template
def self.remove(wizard_id)
wizard = CustomWizard::Wizard.create(wizard_id)
-
return false if !wizard
ActiveRecord::Base.transaction do
PluginStore.remove(CustomWizard::PLUGIN_NAME, wizard.id)
-
- if wizard.after_time
- Jobs.cancel_scheduled_job(:set_after_time_wizard)
- Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard_id)
- end
+ clear_user_wizard_redirect(wizard_id)
end
+ Jobs.cancel_scheduled_job(:set_after_time_wizard) if wizard.after_time
+
true
end
@@ -88,6 +85,10 @@ class CustomWizard::Template
end
end
+ def self.clear_user_wizard_redirect(wizard_id)
+ UserCustomField.where(name: 'redirect_to_wizard', value: wizard_id).destroy_all
+ end
+
private
def normalize_data
@@ -132,7 +133,7 @@ class CustomWizard::Template
Jobs.enqueue_at(enqueue_wizard_at, :set_after_time_wizard, wizard_id: wizard_id)
elsif old_data && old_data[:after_time]
Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard_id)
- Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard_id)
+ self.class.clear_user_wizard_redirect(wizard_id)
end
end
end
diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb
index 82693eed..f92f3d61 100644
--- a/lib/custom_wizard/wizard.rb
+++ b/lib/custom_wizard/wizard.rb
@@ -247,6 +247,26 @@ class CustomWizard::Wizard
set_submissions(submissions)
end
+ def filter_conditional_fields
+ included_fields = steps.map { |s| s.fields.map { |f| f.id } }.flatten
+ filtered_submision = current_submission&.select do |key, _|
+ key = key.to_s
+ included_fields.include?(key) ||
+ required_fields.include?(key) ||
+ key.include?("action")
+ end
+
+ save_submission(filtered_submision)
+ end
+
+ def required_fields
+ %w{
+ submitted_at
+ route_to
+ saved_param
+ }
+ end
+
def final_cleanup!
if id == user.custom_fields['redirect_to_wizard']
user.custom_fields.delete('redirect_to_wizard')
diff --git a/package.json b/package.json
index c6692218..f06823c8 100644
--- a/package.json
+++ b/package.json
@@ -2,9 +2,9 @@
"name": "discourse-custom-wizard",
"version": "1.0.0",
"repository": "git@github.com:paviliondev/discourse-custom-wizard.git",
- "author": "Discourse",
- "license": "MIT",
+ "author": "Pavilion",
+ "license": "GPL V2",
"devDependencies": {
"eslint-config-discourse": "^1.1.8"
}
-}
+}
\ No newline at end of file
diff --git a/plugin.rb b/plugin.rb
index 6f5f203a..e6287a44 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -65,7 +65,6 @@ after_initialize do
../controllers/custom_wizard/wizard.rb
../controllers/custom_wizard/steps.rb
../controllers/custom_wizard/realtime_validations.rb
- ../jobs/clear_after_time_wizard.rb
../jobs/refresh_api_access_token.rb
../jobs/set_after_time_wizard.rb
../lib/custom_wizard/validators/template.rb
@@ -109,6 +108,7 @@ after_initialize do
../extensions/users_controller.rb
../extensions/custom_field/preloader.rb
../extensions/custom_field/serializer.rb
+ ../extensions/custom_field/extension.rb
].each do |path|
load File.expand_path(path, __FILE__)
end
@@ -201,18 +201,18 @@ after_initialize do
end
CustomWizard::CustomField::CLASSES.keys.each do |klass|
+ class_constant = klass.to_s.classify.constantize
+
add_model_callback(klass, :after_initialize) do
if CustomWizard::CustomField.enabled?
CustomWizard::CustomField.list_by(:klass, klass.to_s).each do |field|
- klass.to_s
- .classify
- .constantize
- .register_custom_field_type(field[:name], field[:type].to_sym)
+ class_constant.register_custom_field_type(field[:name], field[:type].to_sym)
end
end
end
- klass.to_s.classify.constantize.singleton_class.prepend CustomWizardCustomFieldPreloader
+ class_constant.singleton_class.prepend CustomWizardCustomFieldPreloader
+ class_constant.singleton_class.prepend CustomWizardCustomFieldExtension
end
CustomWizard::CustomField.serializers.each do |serializer_klass|
diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb
index 28f2cab8..9910c0bd 100644
--- a/spec/components/custom_wizard/action_spec.rb
+++ b/spec/components/custom_wizard/action_spec.rb
@@ -72,6 +72,42 @@ describe CustomWizard::Action do
raw: "topic body"
).exists?).to eq(false)
end
+
+ it "adds custom fields" do
+ wizard = CustomWizard::Builder.new(@template[:id], user).build
+ wizard.create_updater(wizard.steps.first.id,
+ step_1_field_1: "Topic Title",
+ step_1_field_2: "topic body"
+ ).update
+ wizard.create_updater(wizard.steps.second.id, {}).update
+ wizard.create_updater(wizard.steps.last.id,
+ step_3_field_3: category.id
+ ).update
+
+ topic = Topic.where(
+ title: "Topic Title",
+ category_id: category.id
+ ).first
+ topic_custom_field = TopicCustomField.where(
+ name: "topic_field",
+ value: "Topic custom field value",
+ topic_id: topic.id
+ )
+ topic_json_custom_field = TopicCustomField.where("
+ name = 'topic_json_field' AND
+ (value::json->>'key_1') = 'Key 1 value' AND
+ (value::json->>'key_2') = 'Key 2 value' AND
+ topic_id = #{topic.id}"
+ )
+ post_custom_field = PostCustomField.where(
+ name: "post_field",
+ value: "Post custom field value",
+ post_id: topic.first_post.id
+ )
+ expect(topic_custom_field.exists?).to eq(true)
+ expect(topic_json_custom_field.exists?).to eq(true)
+ expect(post_custom_field.exists?).to eq(true)
+ end
end
context 'sending a message' do
diff --git a/spec/components/custom_wizard/custom_field_spec.rb b/spec/components/custom_wizard/custom_field_spec.rb
index 3c9f1706..b17e26c6 100644
--- a/spec/components/custom_wizard/custom_field_spec.rb
+++ b/spec/components/custom_wizard/custom_field_spec.rb
@@ -49,6 +49,40 @@ describe CustomWizard::CustomField do
end
context "validation" do
+ it "does not save without required attributes" do
+ invalid_field_json = custom_field_json['custom_fields'].first
+ invalid_field_json['klass'] = nil
+
+ custom_field = CustomWizard::CustomField.new(nil, invalid_field_json)
+ expect(custom_field.save).to eq(false)
+ expect(custom_field.valid?).to eq(false)
+ expect(custom_field.errors.full_messages.first).to eq(
+ I18n.t("wizard.custom_field.error.required_attribute", attr: "klass")
+ )
+ expect(
+ PluginStoreRow.where(
+ plugin_name: CustomWizard::CustomField::NAMESPACE,
+ key: custom_field.name
+ ).exists?
+ ).to eq(false)
+ end
+
+ it "does save without optional attributes" do
+ field_json = custom_field_json['custom_fields'].first
+ field_json['serializers'] = nil
+
+ custom_field = CustomWizard::CustomField.new(nil, field_json)
+ expect(custom_field.save).to eq(true)
+ expect(custom_field.valid?).to eq(true)
+ expect(
+ PluginStoreRow.where("
+ plugin_name = '#{CustomWizard::CustomField::NAMESPACE}' AND
+ key = '#{custom_field.name}' AND
+ value::jsonb = '#{field_json.except('name').to_json}'::jsonb
+ ",).exists?
+ ).to eq(true)
+ end
+
it "does not save with an unsupported class" do
invalid_field_json = custom_field_json['custom_fields'].first
invalid_field_json['klass'] = 'user'
@@ -178,6 +212,22 @@ describe CustomWizard::CustomField do
it "lists saved custom field records by attribute value" do
expect(CustomWizard::CustomField.list_by(:klass, 'topic').length).to eq(1)
end
+
+ it "lists saved custom field records by optional values" do
+ field_json = custom_field_json['custom_fields'].first
+ field_json['serializers'] = nil
+
+ custom_field = CustomWizard::CustomField.new(nil, field_json)
+ expect(CustomWizard::CustomField.list_by(:serializers, ['post']).length).to eq(0)
+ end
+
+ it "lists custom field records added by other plugins " do
+ expect(CustomWizard::CustomField.external_list.length).to eq(11)
+ end
+
+ it "lists all custom field records" do
+ expect(CustomWizard::CustomField.full_list.length).to eq(15)
+ end
end
it "is enabled if there are custom fields" do
diff --git a/spec/components/custom_wizard/mapper_spec.rb b/spec/components/custom_wizard/mapper_spec.rb
index 434f0001..ed66d7c1 100644
--- a/spec/components/custom_wizard/mapper_spec.rb
+++ b/spec/components/custom_wizard/mapper_spec.rb
@@ -229,28 +229,40 @@ describe CustomWizard::Mapper do
).perform).to eq("value 2")
end
- it "interpolates user fields" do
- expect(CustomWizard::Mapper.new(
- inputs: inputs['interpolate_user_field'],
- data: data,
- user: user1
- ).perform).to eq("Name: Angus")
- end
+ context "interpolates" do
+ it "user fields" do
+ expect(CustomWizard::Mapper.new(
+ inputs: inputs['interpolate_user_field'],
+ data: data,
+ user: user1
+ ).perform).to eq("Name: Angus")
+ end
- it "interpolates wizard fields" do
- expect(CustomWizard::Mapper.new(
- inputs: inputs['interpolate_wizard_field'],
- data: data,
- user: user1
- ).perform).to eq("Input 1: value 1")
- end
+ it "user emails" do
+ expect(CustomWizard::Mapper.new(
+ inputs: inputs['interpolate_user_email'],
+ data: data,
+ user: user1
+ ).perform).to eq("Email: angus@email.com")
+ end
- it "interpolates date" do
- expect(CustomWizard::Mapper.new(
- inputs: inputs['interpolate_timestamp'],
- data: data,
- user: user1
- ).perform).to eq("Time: #{Time.now.strftime("%B %-d, %Y")}")
+ it "user options" do
+ user1.user_option.update_columns(email_level: UserOption.email_level_types[:never])
+
+ expect(CustomWizard::Mapper.new(
+ inputs: inputs['interpolate_user_option'],
+ data: data,
+ user: user1
+ ).perform).to eq("Email Level: #{UserOption.email_level_types[:never]}")
+ end
+
+ it "date" do
+ expect(CustomWizard::Mapper.new(
+ inputs: inputs['interpolate_timestamp'],
+ data: data,
+ user: user1
+ ).perform).to eq("Time: #{Time.now.strftime("%B %-d, %Y")}")
+ end
end
it "handles greater than pairs" do
diff --git a/spec/components/custom_wizard/template_spec.rb b/spec/components/custom_wizard/template_spec.rb
index fb76e0c4..0e3dbdbe 100644
--- a/spec/components/custom_wizard/template_spec.rb
+++ b/spec/components/custom_wizard/template_spec.rb
@@ -41,6 +41,14 @@ describe CustomWizard::Template do
).to eq(nil)
end
+ it "removes user wizard redirects if template is removed" do
+ user.custom_fields['redirect_to_wizard'] = 'super_mega_fun_wizard'
+ user.save_custom_fields(true)
+
+ CustomWizard::Template.remove('super_mega_fun_wizard')
+ expect(user.reload.custom_fields['redirect_to_wizard']).to eq(nil)
+ end
+
it "checks for wizard template existence" do
expect(
CustomWizard::Template.exists?('super_mega_fun_wizard')
diff --git a/spec/components/custom_wizard/wizard_spec.rb b/spec/components/custom_wizard/wizard_spec.rb
index aed44fe6..9808f32f 100644
--- a/spec/components/custom_wizard/wizard_spec.rb
+++ b/spec/components/custom_wizard/wizard_spec.rb
@@ -173,6 +173,8 @@ describe CustomWizard::Wizard do
progress_step("step_2", acting_user: trusted_user)
progress_step("step_3", acting_user: trusted_user)
+ @permitted_template["multiple_submissions"] = true
+
expect(
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
).to eq(true)
diff --git a/spec/extensions/extra_locales_controller_spec.rb b/spec/extensions/extra_locales_controller_spec.rb
index 91a4e8c3..a71e39c4 100644
--- a/spec/extensions/extra_locales_controller_spec.rb
+++ b/spec/extensions/extra_locales_controller_spec.rb
@@ -37,6 +37,13 @@ describe ExtraLocalesControllerCustomWizard, type: :request do
expect(response.status).to eq(200)
end
+ it "returns wizard locales when requested by user in a wizard step" do
+ sign_in(new_user)
+
+ get @locale_url, headers: { 'REFERER' => "/w/super-mega-fun-wizard/steps/step_1" }
+ expect(response.status).to eq(200)
+ end
+
it "return wizard locales if user cant access wizard" do
template[:permitted] = permitted["permitted"]
CustomWizard::Template.save(template.as_json)
diff --git a/spec/fixtures/mapper/inputs.json b/spec/fixtures/mapper/inputs.json
index f7d98903..443f186b 100644
--- a/spec/fixtures/mapper/inputs.json
+++ b/spec/fixtures/mapper/inputs.json
@@ -57,6 +57,22 @@
"output": "Name: u{name}"
}
],
+ "interpolate_user_email": [
+ {
+ "type": "assignment",
+ "output_type": "text",
+ "output_connector": "set",
+ "output": "Email: u{email}"
+ }
+ ],
+ "interpolate_user_option": [
+ {
+ "type": "assignment",
+ "output_type": "text",
+ "output_connector": "set",
+ "output": "Email Level: u{email_level}"
+ }
+ ],
"interpolate_wizard_field": [
{
"type": "assignment",
diff --git a/spec/fixtures/wizard.json b/spec/fixtures/wizard.json
index c21d445c..a505c0d3 100644
--- a/spec/fixtures/wizard.json
+++ b/spec/fixtures/wizard.json
@@ -3,7 +3,6 @@
"name": "Super Mega Fun Wizard",
"background": "#333333",
"save_submissions": true,
- "multiple_submissions": true,
"after_signup": false,
"prompt_completion": false,
"theme_id": 2,
@@ -391,10 +390,34 @@
"pairs": [
{
"index": 0,
- "key": "custom_field_1",
+ "key": "post_field",
"key_type": "text",
- "value": "title",
- "value_type": "user_field",
+ "value": "Post custom field value",
+ "value_type": "text",
+ "connector": "association"
+ },
+ {
+ "index": 1,
+ "key": "topic.topic_field",
+ "key_type": "text",
+ "value": "Topic custom field value",
+ "value_type": "text",
+ "connector": "association"
+ },
+ {
+ "index": 2,
+ "key": "topic.topic_json_field{}.key_1",
+ "key_type": "text",
+ "value": "Key 1 value",
+ "value_type": "text",
+ "connector": "association"
+ },
+ {
+ "index": 3,
+ "key": "topic.topic_json_field{}.key_2",
+ "key_type": "text",
+ "value": "Key 2 value",
+ "value_type": "text",
"connector": "association"
}
]
diff --git a/spec/jobs/clear_after_time_wizard_spec.rb b/spec/jobs/clear_after_time_wizard_spec.rb
deleted file mode 100644
index 935036a3..00000000
--- a/spec/jobs/clear_after_time_wizard_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../plugin_helper'
-
-describe Jobs::ClearAfterTimeWizard do
- fab!(:user1) { Fabricate(:user) }
- fab!(:user2) { Fabricate(:user) }
- fab!(:user3) { Fabricate(:user) }
-
- let(:template) {
- JSON.parse(File.open(
- "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
- ).read).with_indifferent_access
- }
-
- it "clears wizard redirect for all users " do
- after_time_template = template.dup
- after_time_template["after_time"] = true
- after_time_template["after_time_scheduled"] = (Time.now + 3.hours).iso8601
-
- CustomWizard::Template.save(after_time_template)
-
- Jobs::SetAfterTimeWizard.new.execute(wizard_id: 'super_mega_fun_wizard')
-
- expect(
- UserCustomField.where(
- name: 'redirect_to_wizard',
- value: 'super_mega_fun_wizard'
- ).length
- ).to eq(3)
-
- described_class.new.execute(wizard_id: 'super_mega_fun_wizard')
-
- expect(
- UserCustomField.where("
- name = 'redirect_to_wizard' AND
- value = 'super_mega_fun_wizard'
- ").exists?
- ).to eq(false)
- end
-end
diff --git a/spec/plugin_helper.rb b/spec/plugin_helper.rb
index 6680874f..93f33a81 100644
--- a/spec/plugin_helper.rb
+++ b/spec/plugin_helper.rb
@@ -6,7 +6,7 @@ if ENV['SIMPLECOV']
SimpleCov.start do
root "plugins/discourse-custom-wizard"
track_files "plugins/discourse-custom-wizard/**/*.rb"
- add_filter { |src| src.filename =~ /(\/spec\/|\/db\/|plugin\.rb|api)/ }
+ add_filter { |src| src.filename =~ /(\/spec\/|\/db\/|plugin\.rb|api|gems)/ }
SimpleCov.minimum_coverage 80
end
end
diff --git a/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb b/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb
index e006e65a..8c1a8550 100644
--- a/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb
+++ b/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb
@@ -17,9 +17,9 @@ describe CustomWizard::AdminCustomFieldsController do
sign_in(admin_user)
end
- it "returns the list of custom fields" do
+ it "returns the full list of custom fields" do
get "/admin/wizards/custom-fields.json"
- expect(response.parsed_body.length).to eq(4)
+ expect(response.parsed_body.length).to eq(15)
end
it "saves custom fields" do
diff --git a/spec/requests/custom_wizard/application_controller_spec.rb b/spec/requests/custom_wizard/application_controller_spec.rb
index f79db877..e8b45c48 100644
--- a/spec/requests/custom_wizard/application_controller_spec.rb
+++ b/spec/requests/custom_wizard/application_controller_spec.rb
@@ -43,6 +43,12 @@ describe ApplicationController do
.first['redirect_to']
).to eq("/t/2")
end
+
+ it "does not redirect if wizard does not exist" do
+ CustomWizard::Template.remove('super_mega_fun_wizard')
+ get "/"
+ expect(response.status).to eq(200)
+ end
end
context "who is not required to complete wizard" do
diff --git a/spec/requests/custom_wizard/steps_controller_spec.rb b/spec/requests/custom_wizard/steps_controller_spec.rb
index c58f13a2..2424274b 100644
--- a/spec/requests/custom_wizard/steps_controller_spec.rb
+++ b/spec/requests/custom_wizard/steps_controller_spec.rb
@@ -249,4 +249,33 @@ describe CustomWizard::StepsController do
expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true)
end
+
+ it "excludes the non-included conditional fields from the submissions" do
+ new_template = wizard_template.dup
+ new_template['steps'][1]['fields'][0]['condition'] = wizard_field_condition_template['condition']
+ CustomWizard::Template.save(new_template, skip_jobs: true)
+
+ put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
+ fields: {
+ step_1_field_1: "Condition will pass"
+ }
+ }
+
+ put '/w/super-mega-fun-wizard/steps/step_2.json', params: {
+ fields: {
+ step_2_field_1: "1995-04-23"
+ }
+ }
+
+ put '/w/super-mega-fun-wizard/steps/step_1.json', params: {
+ fields: {
+ step_1_field_1: "Condition will not pass"
+ }
+ }
+
+ wizard_id = response.parsed_body['wizard']['id']
+ wizard = CustomWizard::Wizard.create(wizard_id, user)
+ submission = wizard.submissions.last
+ expect(submission.keys).not_to include("step_2_field_1")
+ end
end
diff --git a/spec/requests/custom_wizard/wizard_controller_spec.rb b/spec/requests/custom_wizard/wizard_controller_spec.rb
index 3e7ddd3d..4380bc73 100644
--- a/spec/requests/custom_wizard/wizard_controller_spec.rb
+++ b/spec/requests/custom_wizard/wizard_controller_spec.rb
@@ -11,6 +11,14 @@ describe CustomWizard::WizardController do
)
}
+ let(:permitted_json) {
+ JSON.parse(
+ File.open(
+ "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json"
+ ).read
+ )
+ }
+
before do
CustomWizard::Template.save(
JSON.parse(File.open(
@@ -47,6 +55,14 @@ describe CustomWizard::WizardController do
expect(response.status).to eq(200)
end
+ it 'lets user skip if user cant access wizard' do
+ @template["permitted"] = permitted_json["permitted"]
+ CustomWizard::Template.save(@template, skip_jobs: true)
+
+ put '/w/super-mega-fun-wizard/skip.json'
+ expect(response.status).to eq(200)
+ end
+
it 'returns a no skip message if user is not allowed to skip' do
@template['required'] = 'true'
CustomWizard::Template.save(@template)