0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-11-26 02:50:28 +01:00

Merge branch 'master' into sprockets-fix

Dieser Commit ist enthalten in:
Faizaan Gagan 2021-06-16 14:28:55 +05:30
Commit 7b9a54590b
42 geänderte Dateien mit 469 neuen und 178 gelöschten Zeilen

Datei anzeigen

@ -6,6 +6,8 @@ on:
- master - master
- main - main
pull_request: pull_request:
schedule:
- cron: '0 0 * * *'
jobs: jobs:
build: build:

Datei anzeigen

@ -6,6 +6,8 @@ on:
- master - master
- main - main
pull_request: pull_request:
schedule:
- cron: '0 0 * * *'
jobs: jobs:
build: build:
@ -51,23 +53,26 @@ jobs:
repository: discourse/discourse repository: discourse/discourse
fetch-depth: 1 fetch-depth: 1
- run: echo "REPOSITORY_NAME=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV
shell: bash
- name: Install plugin - name: Install plugin
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
path: plugins/${{ github.event.repository.name }} path: plugins/${{ env.REPOSITORY_NAME }}
fetch-depth: 1 fetch-depth: 1
- name: Check spec existence - name: Check spec existence
id: check_spec id: check_spec
uses: andstor/file-existence-action@v1 uses: andstor/file-existence-action@v1
with: with:
files: "plugins/${{ github.event.repository.name }}/spec" files: "plugins/${{ env.REPOSITORY_NAME }}/spec"
- name: Check qunit existence - name: Check qunit existence
id: check_qunit id: check_qunit
uses: andstor/file-existence-action@v1 uses: andstor/file-existence-action@v1
with: with:
files: "plugins/${{ github.event.repository.name }}/test/javascripts" files: "plugins/${{ env.REPOSITORY_NAME }}/test/javascripts"
- name: Setup Git - name: Setup Git
run: | run: |
@ -100,7 +105,7 @@ jobs:
- name: Lint English locale - name: Lint English locale
if: matrix.build_type == 'backend' 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 - name: Get yarn cache directory
id: yarn-cache-dir id: yarn-cache-dir
@ -123,15 +128,11 @@ jobs:
bin/rake db:create bin/rake db:create
bin/rake db:migrate bin/rake db:migrate
- name: Plugin RSpec - name: Plugin RSpec with Coverage
if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exists == 'true' 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 - name: Plugin QUnit
if: matrix.build_type == 'frontend' && steps.check_qunit.outputs.files_exists == 'true' 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 timeout-minutes: 30
- name: Simplecov Report
if: matrix.build_type == 'backend'
run: COVERAGE=1 bin/rake plugin:spec[${{ github.event.repository.name }}]

Datei anzeigen

@ -1,6 +1,6 @@
import Component from "@ember/component"; import Component from "@ember/component";
import discourseComputed, { observes } from "discourse-common/utils/decorators"; 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"; import I18n from "I18n";
const generateContent = function (array, type) { const generateContent = function (array, type) {
@ -29,6 +29,7 @@ export default Component.extend({
loading: or("saving", "destroying"), loading: or("saving", "destroying"),
destroyDisabled: alias("loading"), destroyDisabled: alias("loading"),
closeDisabled: alias("loading"), closeDisabled: alias("loading"),
isExternal: equal("field.id", "external"),
didInsertElement() { didInsertElement() {
this.set("originalField", JSON.parse(JSON.stringify(this.field))); this.set("originalField", JSON.parse(JSON.stringify(this.field)));
@ -61,13 +62,14 @@ export default Component.extend({
@discourseComputed( @discourseComputed(
"saving", "saving",
"isExternal",
"field.name", "field.name",
"field.klass", "field.klass",
"field.type", "field.type",
"field.serializers" "field.serializers"
) )
saveDisabled(saving) { saveDisabled(saving, isExternal) {
if (saving) { if (saving || isExternal) {
return true; return true;
} }

Datei anzeigen

@ -62,6 +62,11 @@ export default Component.extend(UndoChanges, {
return key; return key;
}, },
@discourseComputed("action.type")
customFieldsContext(type) {
return `action.${type}`;
},
@discourseComputed("wizard.steps") @discourseComputed("wizard.steps")
runAfterContent(steps) { runAfterContent(steps) {
let content = steps.map(function (step) { let content = steps.map(function (step) {

Datei anzeigen

@ -6,11 +6,24 @@ import {
} from "discourse-common/utils/decorators"; } from "discourse-common/utils/decorators";
import { getOwner } from "discourse-common/lib/get-owner"; import { getOwner } from "discourse-common/lib/get-owner";
import { defaultSelectionType, selectionTypes } from "../lib/wizard-mapper"; 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 Component from "@ember/component";
import { bind, later } from "@ember/runloop"; import { bind, later } from "@ember/runloop";
import I18n from "I18n"; 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({ export default Component.extend({
classNameBindings: [":mapper-selector", "activeType"], classNameBindings: [":mapper-selector", "activeType"],
@ -188,11 +201,19 @@ export default Component.extend({
customFields customFields
) { ) {
let content; 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") { if (activeType === "wizardField") {
content = wizardFields; content = wizardFields;
if (this.options.context === "field") { if (context === "field") {
content = content.filter((field) => field.id !== currentFieldId); content = content.filter((field) => field.id !== currentFieldId);
} }
} }
@ -204,7 +225,7 @@ export default Component.extend({
type: a.type, type: a.type,
})); }));
if (this.options.context === "action") { if (context === "action") {
content = content.filter((a) => a.id !== currentActionId); content = content.filter((a) => a.id !== currentActionId);
} }
} }
@ -218,7 +239,7 @@ export default Component.extend({
.concat(userFields || []); .concat(userFields || []);
if ( if (
this.options.context === "action" && context === "action" &&
this.inputType === "association" && this.inputType === "association" &&
this.selectorType === "key" this.selectorType === "key"
) { ) {
@ -234,7 +255,17 @@ export default Component.extend({
} }
if (activeType === "customField") { 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; return content;

Datei anzeigen

@ -3,12 +3,12 @@ import CustomWizardCustomField from "../models/custom-wizard-custom-field";
export default Controller.extend({ export default Controller.extend({
messageKey: "create", messageKey: "create",
fieldKeys: ["klass", "type", "serializers", "name"], fieldKeys: ["klass", "type", "name", "serializers"],
documentationUrl: "https://thepavilion.io/t/3572", documentationUrl: "https://thepavilion.io/t/3572",
actions: { actions: {
addField() { addField() {
this.get("customFields").pushObject( this.get("customFields").unshiftObject(
CustomWizardCustomField.create({ edit: true }) CustomWizardCustomField.create({ edit: true })
); );
}, },

Datei anzeigen

@ -120,4 +120,5 @@ export {
listProperties, listProperties,
notificationLevels, notificationLevels,
wizardFieldList, wizardFieldList,
sentenceCase,
}; };

Datei anzeigen

@ -2,7 +2,6 @@ import CustomWizard from "../models/custom-wizard";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
import I18n from "I18n"; import I18n from "I18n";
import { selectKitContent } from "../lib/wizard";
export default DiscourseRoute.extend({ export default DiscourseRoute.extend({
model(params) { model(params) {
@ -33,9 +32,7 @@ export default DiscourseRoute.extend({
wizardList: parentModel.wizard_list, wizardList: parentModel.wizard_list,
fieldTypes, fieldTypes,
userFields: parentModel.userFields, userFields: parentModel.userFields,
customFields: selectKitContent( customFields: parentModel.custom_fields,
parentModel.custom_fields.map((f) => f.name)
),
apis: parentModel.apis, apis: parentModel.apis,
themes: parentModel.themes, themes: parentModel.themes,
wizard, wizard,

Datei anzeigen

@ -13,6 +13,11 @@
none="admin.wizard.custom_field.type.select" none="admin.wizard.custom_field.type.select"
onChange=(action (mut field.type))}} onChange=(action (mut field.type))}}
</td> </td>
<td class="input">
{{input
value=field.name
placeholder=(i18n "admin.wizard.custom_field.name.select")}}
</td>
<td class="multi-select"> <td class="multi-select">
{{multi-select {{multi-select
value=field.serializers value=field.serializers
@ -20,11 +25,6 @@
none="admin.wizard.custom_field.serializers.select" none="admin.wizard.custom_field.serializers.select"
onChange=(action (mut field.serializers))}} onChange=(action (mut field.serializers))}}
</td> </td>
<td class="input">
{{input
value=field.name
placeholder=(i18n "admin.wizard.custom_field.name.select")}}
</td>
<td class="actions"> <td class="actions">
{{#if loading}} {{#if loading}}
{{loading-spinner size="small"}} {{loading-spinner size="small"}}
@ -51,13 +51,25 @@
{{else}} {{else}}
<td><label>{{field.klass}}</label></td> <td><label>{{field.klass}}</label></td>
<td><label>{{field.type}}</label></td> <td><label>{{field.type}}</label></td>
<td class="input"><label>{{field.name}}</label></td>
<td class="multi-select"> <td class="multi-select">
{{#if isExternal}}
&mdash;
{{else}}
{{#each field.serializers as |serializer|}} {{#each field.serializers as |serializer|}}
<label>{{serializer}}</label> <label>{{serializer}}</label>
{{/each}} {{/each}}
{{/if}}
</td> </td>
<td class="input"><label>{{field.name}}</label></td> {{#if isExternal}}
<td class="external">
<label title={{i18n "admin.wizard.custom_field.external.title"}}>
{{i18n "admin.wizard.custom_field.external.label"}}
</label>
</td>
{{else}}
<td class="actions"> <td class="actions">
{{d-button action="edit" icon="pencil-alt"}} {{d-button action="edit" icon="pencil-alt"}}
</td> </td>
{{/if}}
{{/if}} {{/if}}

Datei anzeigen

@ -738,7 +738,7 @@
wizardActionSelection="value" wizardActionSelection="value"
userFieldSelection="value" userFieldSelection="value"
keyPlaceholder="admin.wizard.action.custom_fields.key" keyPlaceholder="admin.wizard.action.custom_fields.key"
context="action" context=customFieldsContext
)}} )}}
</div> </div>
</div> </div>

Datei anzeigen

@ -4,6 +4,10 @@ export default Ember.Application.extend({
rootElement: "#custom-wizard-main", rootElement: "#custom-wizard-main",
Resolver: buildResolver("wizard"), Resolver: buildResolver("wizard"),
customEvents: {
paste: "paste",
},
start() { start() {
Object.keys(requirejs._eak_seen).forEach((key) => { Object.keys(requirejs._eak_seen).forEach((key) => {
if (/\/pre\-initializers\//.test(key)) { if (/\/pre\-initializers\//.test(key)) {

Datei anzeigen

@ -15,7 +15,7 @@ export default {
); );
const DEditor = requirejs("discourse/components/d-editor").default; const DEditor = requirejs("discourse/components/d-editor").default;
const { clipboardHelpers } = requirejs("discourse/lib/utilities"); const { clipboardHelpers } = requirejs("discourse/lib/utilities");
const { toMarkdown } = requirejs("discourse/lib/to-markdown"); const toMarkdown = requirejs("discourse/lib/to-markdown").default;
FieldComponent.reopen({ FieldComponent.reopen({
classNameBindings: ["field.id"], classNameBindings: ["field.id"],
@ -181,7 +181,7 @@ export default {
markdown = pre.match(/\S$/) ? ` ${markdown}` : markdown; markdown = pre.match(/\S$/) ? ` ${markdown}` : markdown;
} }
this.appEvents.trigger("composer:insert-text", { this.appEvents.trigger("wizard-editor:insert-text", {
fieldId: this.fieldId, fieldId: this.fieldId,
text: markdown, text: markdown,
}); });

Datei anzeigen

@ -667,6 +667,10 @@
margin-left: 5px !important; margin-left: 5px !important;
} }
} }
td.external {
font-style: italic;
}
} }
} }

Datei anzeigen

@ -75,7 +75,7 @@ en:
edit: "You're editing an action" edit: "You're editing an action"
documentation: "Check out the action documentation" documentation: "Check out the action documentation"
custom_fields: custom_fields:
create: "Create, edit or destroy a custom field record" create: "View, create, edit and destroy custom fields"
saved: "Saved custom field" saved: "Saved custom field"
error: "Failed to save: {{messages}}" error: "Failed to save: {{messages}}"
documentation: Check out the custom field documentation documentation: Check out the custom field documentation
@ -322,6 +322,9 @@ en:
custom_field: custom_field:
nav_label: "Custom Fields" nav_label: "Custom Fields"
add: "Add" 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: name:
label: "Name" label: "Name"
select: "underscored_name" select: "underscored_name"

Datei anzeigen

@ -14,7 +14,7 @@ class CustomWizard::AdminController < ::Admin::AdminController
end end
def custom_field_list def custom_field_list
serialize_data(CustomWizard::CustomField.list, CustomWizard::CustomFieldSerializer) serialize_data(CustomWizard::CustomField.full_list, CustomWizard::CustomFieldSerializer)
end end
def render_error(message) def render_error(message)

Datei anzeigen

@ -23,12 +23,12 @@ class CustomWizard::StepsController < ::ApplicationController
if updater.success? if updater.success?
wizard_id = update_params[:wizard_id] wizard_id = update_params[:wizard_id]
builder = CustomWizard::Builder.new(wizard_id, current_user) builder = CustomWizard::Builder.new(wizard_id, current_user)
@wizard = builder.build @wizard = builder.build(force: true)
current_step = @wizard.find_step(update[:step_id]) current_step = @wizard.find_step(update[:step_id])
current_submission = @wizard.current_submission current_submission = @wizard.current_submission
result = {} result = {}
@wizard.filter_conditional_fields
if current_step.conditional_final_step && !current_step.last_step if current_step.conditional_final_step && !current_step.last_step
current_step.force_final = true current_step.force_final = true
end end

Datei anzeigen

@ -61,7 +61,7 @@ class CustomWizard::WizardController < ::ApplicationController
result = success_json result = success_json
user = current_user user = current_user
if user if user && wizard.can_access?
submission = wizard.current_submission submission = wizard.current_submission
if submission && submission['redirect_to'] if submission && submission['redirect_to']
result.merge!(redirect_to: submission['redirect_to']) result.merge!(redirect_to: submission['redirect_to'])

Datei anzeigen

@ -0,0 +1,6 @@
# frozen_string_literal: true
module CustomWizardCustomFieldExtension
def custom_field_types
@custom_field_types
end
end

Datei anzeigen

@ -4,7 +4,8 @@ module ExtraLocalesControllerCustomWizard
super || begin super || begin
return false unless bundle =~ /wizard/ && request.referer =~ /\/w\// return false unless bundle =~ /wizard/ && request.referer =~ /\/w\//
path = URI(request.referer).path 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) CustomWizard::Template.exists?(wizard_id.underscore)
end end
end end

Datei anzeigen

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

Datei anzeigen

@ -454,35 +454,54 @@ class CustomWizard::Action
data: data, data: data,
user: user user: user
).perform ).perform
registered_fields = CustomWizard::CustomField.full_list
registered_fields = CustomWizard::CustomField.cached_list
field_map.each do |field| field_map.each do |field|
keyArr = field[:key].split('.') keyArr = field[:key].split('.')
value = field[:value] value = field[:value]
if keyArr.length > 1 if keyArr.length > 1
klass = keyArr.first klass = keyArr.first.to_sym
name = keyArr.last name = keyArr.second
if keyArr.length === 3 && name.include?("{}")
name = name.gsub("{}", "")
json_attr = keyArr.last
type = :json
end
else else
name = keyArr.first name = keyArr.first
end end
registered = registered_fields.select { |f| f[:name] == name } registered = registered_fields.select { |f| f.name == name }.first
if registered.first.present? if registered.present?
klass = registered.first[:klass] klass = registered.klass
type = registered.type
end end
if klass === 'topic' next if type === :json && json_attr.blank?
if klass === :topic
params[:topic_opts] ||= {} params[:topic_opts] ||= {}
params[:topic_opts][:custom_fields] ||= {} params[:topic_opts][:custom_fields] ||= {}
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 params[:topic_opts][:custom_fields][name] = value
end
else
if type === :json
params[:custom_fields][name] ||= {}
params[:custom_fields][name][json_attr] = value
else else
params[:custom_fields] ||= {} params[:custom_fields] ||= {}
params[:custom_fields][name] = value params[:custom_fields][name] = value
end end
end end
end end
end
params params
end end

Datei anzeigen

@ -30,7 +30,7 @@ class CustomWizard::Builder
def build(build_opts = {}, params = {}) def build(build_opts = {}, params = {})
return nil if !SiteSetting.custom_wizard_enabled || !@wizard 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 build_opts[:reset] = build_opts[:reset] || @wizard.restart_on_revisit

Datei anzeigen

@ -66,10 +66,12 @@ class ::CustomWizard::CustomField
value = send(attr) value = send(attr)
i18n_key = "wizard.custom_field.error" i18n_key = "wizard.custom_field.error"
if value.blank? if value.blank? && REQUIRED.include?(attr)
if REQUIRED.include?(attr)
add_error(I18n.t("#{i18n_key}.required_attribute", attr: attr)) add_error(I18n.t("#{i18n_key}.required_attribute", attr: attr))
break
end end
if attr == 'serializers' && !value.is_a?(Array)
next next
end end
@ -140,7 +142,7 @@ class ::CustomWizard::CustomField
fields.select do |cf| fields.select do |cf|
if attr == :serializers if attr == :serializers
cf[attr].include?(value) cf[attr] && cf[attr].include?(value)
else else
cf[attr] == value cf[attr] == value
end end
@ -215,4 +217,32 @@ class ::CustomWizard::CustomField
def self.enabled? def self.enabled?
any? any?
end 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 end

Datei anzeigen

@ -5,20 +5,27 @@ class CustomWizard::Mapper
USER_FIELDS = [ USER_FIELDS = [
'name', 'name',
'username', 'username',
'email',
'date_of_birth', 'date_of_birth',
'title', 'title',
'locale', 'locale',
'trust_level', 'trust_level',
'email'
]
USER_OPTION_FIELDS = [
'email_level', 'email_level',
'email_messages_level', 'email_messages_level',
'email_digests' 'email_digests'
] ]
PROFILE_FIELDS = ['location', 'website', 'bio_raw'] PROFILE_FIELDS = [
'location',
'website',
'bio_raw'
]
def self.user_fields def self.user_fields
USER_FIELDS + PROFILE_FIELDS USER_FIELDS + USER_OPTION_FIELDS + PROFILE_FIELDS
end end
OPERATORS = { OPERATORS = {
@ -197,11 +204,15 @@ class CustomWizard::Mapper
def map_user_field(value) def map_user_field(value)
if value.include?(User::USER_FIELD_PREFIX) 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) elsif PROFILE_FIELDS.include?(value)
UserProfile.find_by(user_id: user.id).send(value) user.user_profile.send(value)
elsif USER_FIELDS.include?(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
end end
@ -217,19 +228,11 @@ class CustomWizard::Mapper
return string if string.blank? return string if string.blank?
if opts[:user] if opts[:user]
string.gsub!(/u\{(.*?)\}/) do |match| string.gsub!(/u\{(.*?)\}/) { |match| map_user_field($1) || '' }
result = ''
result = user.send($1) if USER_FIELDS.include?($1)
result = user.user_profile.send($1) if PROFILE_FIELDS.include?($1)
result
end
end end
if opts[:wizard] if opts[:wizard]
string.gsub!(/w\{(.*?)\}/) do |match| string.gsub!(/w\{(.*?)\}/) { |match| recurse(data, [*$1.split('.')]) || '' }
value = recurse(data, [*$1.split('.')])
value.present? ? value : ''
end
end end
if opts[:value] if opts[:value]

Datei anzeigen

@ -49,17 +49,14 @@ class CustomWizard::Template
def self.remove(wizard_id) def self.remove(wizard_id)
wizard = CustomWizard::Wizard.create(wizard_id) wizard = CustomWizard::Wizard.create(wizard_id)
return false if !wizard return false if !wizard
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
PluginStore.remove(CustomWizard::PLUGIN_NAME, wizard.id) PluginStore.remove(CustomWizard::PLUGIN_NAME, wizard.id)
clear_user_wizard_redirect(wizard_id)
end
if wizard.after_time Jobs.cancel_scheduled_job(:set_after_time_wizard) if wizard.after_time
Jobs.cancel_scheduled_job(:set_after_time_wizard)
Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard_id)
end
end
true true
end end
@ -88,6 +85,10 @@ class CustomWizard::Template
end end
end end
def self.clear_user_wizard_redirect(wizard_id)
UserCustomField.where(name: 'redirect_to_wizard', value: wizard_id).destroy_all
end
private private
def normalize_data def normalize_data
@ -132,7 +133,7 @@ class CustomWizard::Template
Jobs.enqueue_at(enqueue_wizard_at, :set_after_time_wizard, wizard_id: wizard_id) Jobs.enqueue_at(enqueue_wizard_at, :set_after_time_wizard, wizard_id: wizard_id)
elsif old_data && old_data[:after_time] elsif old_data && old_data[:after_time]
Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard_id) 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 end
end end

Datei anzeigen

@ -247,6 +247,26 @@ class CustomWizard::Wizard
set_submissions(submissions) set_submissions(submissions)
end 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! def final_cleanup!
if id == user.custom_fields['redirect_to_wizard'] if id == user.custom_fields['redirect_to_wizard']
user.custom_fields.delete('redirect_to_wizard') user.custom_fields.delete('redirect_to_wizard')

Datei anzeigen

@ -2,8 +2,8 @@
"name": "discourse-custom-wizard", "name": "discourse-custom-wizard",
"version": "1.0.0", "version": "1.0.0",
"repository": "git@github.com:paviliondev/discourse-custom-wizard.git", "repository": "git@github.com:paviliondev/discourse-custom-wizard.git",
"author": "Discourse", "author": "Pavilion",
"license": "MIT", "license": "GPL V2",
"devDependencies": { "devDependencies": {
"eslint-config-discourse": "^1.1.8" "eslint-config-discourse": "^1.1.8"
} }

Datei anzeigen

@ -65,7 +65,6 @@ after_initialize do
../controllers/custom_wizard/wizard.rb ../controllers/custom_wizard/wizard.rb
../controllers/custom_wizard/steps.rb ../controllers/custom_wizard/steps.rb
../controllers/custom_wizard/realtime_validations.rb ../controllers/custom_wizard/realtime_validations.rb
../jobs/clear_after_time_wizard.rb
../jobs/refresh_api_access_token.rb ../jobs/refresh_api_access_token.rb
../jobs/set_after_time_wizard.rb ../jobs/set_after_time_wizard.rb
../lib/custom_wizard/validators/template.rb ../lib/custom_wizard/validators/template.rb
@ -109,6 +108,7 @@ after_initialize do
../extensions/users_controller.rb ../extensions/users_controller.rb
../extensions/custom_field/preloader.rb ../extensions/custom_field/preloader.rb
../extensions/custom_field/serializer.rb ../extensions/custom_field/serializer.rb
../extensions/custom_field/extension.rb
].each do |path| ].each do |path|
load File.expand_path(path, __FILE__) load File.expand_path(path, __FILE__)
end end
@ -201,18 +201,18 @@ after_initialize do
end end
CustomWizard::CustomField::CLASSES.keys.each do |klass| CustomWizard::CustomField::CLASSES.keys.each do |klass|
class_constant = klass.to_s.classify.constantize
add_model_callback(klass, :after_initialize) do add_model_callback(klass, :after_initialize) do
if CustomWizard::CustomField.enabled? if CustomWizard::CustomField.enabled?
CustomWizard::CustomField.list_by(:klass, klass.to_s).each do |field| CustomWizard::CustomField.list_by(:klass, klass.to_s).each do |field|
klass.to_s class_constant.register_custom_field_type(field[:name], field[:type].to_sym)
.classify
.constantize
.register_custom_field_type(field[:name], field[:type].to_sym)
end end
end 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 end
CustomWizard::CustomField.serializers.each do |serializer_klass| CustomWizard::CustomField.serializers.each do |serializer_klass|

Datei anzeigen

@ -72,6 +72,42 @@ describe CustomWizard::Action do
raw: "topic body" raw: "topic body"
).exists?).to eq(false) ).exists?).to eq(false)
end 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 end
context 'sending a message' do context 'sending a message' do

Datei anzeigen

@ -49,6 +49,40 @@ describe CustomWizard::CustomField do
end end
context "validation" do 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 it "does not save with an unsupported class" do
invalid_field_json = custom_field_json['custom_fields'].first invalid_field_json = custom_field_json['custom_fields'].first
invalid_field_json['klass'] = 'user' invalid_field_json['klass'] = 'user'
@ -178,6 +212,22 @@ describe CustomWizard::CustomField do
it "lists saved custom field records by attribute value" do it "lists saved custom field records by attribute value" do
expect(CustomWizard::CustomField.list_by(:klass, 'topic').length).to eq(1) expect(CustomWizard::CustomField.list_by(:klass, 'topic').length).to eq(1)
end 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 end
it "is enabled if there are custom fields" do it "is enabled if there are custom fields" do

Datei anzeigen

@ -229,7 +229,8 @@ describe CustomWizard::Mapper do
).perform).to eq("value 2") ).perform).to eq("value 2")
end end
it "interpolates user fields" do context "interpolates" do
it "user fields" do
expect(CustomWizard::Mapper.new( expect(CustomWizard::Mapper.new(
inputs: inputs['interpolate_user_field'], inputs: inputs['interpolate_user_field'],
data: data, data: data,
@ -237,21 +238,32 @@ describe CustomWizard::Mapper do
).perform).to eq("Name: Angus") ).perform).to eq("Name: Angus")
end end
it "interpolates wizard fields" do it "user emails" do
expect(CustomWizard::Mapper.new( expect(CustomWizard::Mapper.new(
inputs: inputs['interpolate_wizard_field'], inputs: inputs['interpolate_user_email'],
data: data, data: data,
user: user1 user: user1
).perform).to eq("Input 1: value 1") ).perform).to eq("Email: angus@email.com")
end end
it "interpolates date" do 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( expect(CustomWizard::Mapper.new(
inputs: inputs['interpolate_timestamp'], inputs: inputs['interpolate_timestamp'],
data: data, data: data,
user: user1 user: user1
).perform).to eq("Time: #{Time.now.strftime("%B %-d, %Y")}") ).perform).to eq("Time: #{Time.now.strftime("%B %-d, %Y")}")
end end
end
it "handles greater than pairs" do it "handles greater than pairs" do
expect(CustomWizard::Mapper.new( expect(CustomWizard::Mapper.new(

Datei anzeigen

@ -41,6 +41,14 @@ describe CustomWizard::Template do
).to eq(nil) ).to eq(nil)
end 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 it "checks for wizard template existence" do
expect( expect(
CustomWizard::Template.exists?('super_mega_fun_wizard') CustomWizard::Template.exists?('super_mega_fun_wizard')

Datei anzeigen

@ -173,6 +173,8 @@ describe CustomWizard::Wizard do
progress_step("step_2", acting_user: trusted_user) progress_step("step_2", acting_user: trusted_user)
progress_step("step_3", acting_user: trusted_user) progress_step("step_3", acting_user: trusted_user)
@permitted_template["multiple_submissions"] = true
expect( expect(
CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access? CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access?
).to eq(true) ).to eq(true)

Datei anzeigen

@ -37,6 +37,13 @@ describe ExtraLocalesControllerCustomWizard, type: :request do
expect(response.status).to eq(200) expect(response.status).to eq(200)
end 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 it "return wizard locales if user cant access wizard" do
template[:permitted] = permitted["permitted"] template[:permitted] = permitted["permitted"]
CustomWizard::Template.save(template.as_json) CustomWizard::Template.save(template.as_json)

Datei anzeigen

@ -57,6 +57,22 @@
"output": "Name: u{name}" "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": [ "interpolate_wizard_field": [
{ {
"type": "assignment", "type": "assignment",

Datei anzeigen

@ -3,7 +3,6 @@
"name": "Super Mega Fun Wizard", "name": "Super Mega Fun Wizard",
"background": "#333333", "background": "#333333",
"save_submissions": true, "save_submissions": true,
"multiple_submissions": true,
"after_signup": false, "after_signup": false,
"prompt_completion": false, "prompt_completion": false,
"theme_id": 2, "theme_id": 2,
@ -391,10 +390,34 @@
"pairs": [ "pairs": [
{ {
"index": 0, "index": 0,
"key": "custom_field_1", "key": "post_field",
"key_type": "text", "key_type": "text",
"value": "title", "value": "Post custom field value",
"value_type": "user_field", "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" "connector": "association"
} }
] ]

Datei anzeigen

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

Datei anzeigen

@ -6,7 +6,7 @@ if ENV['SIMPLECOV']
SimpleCov.start do SimpleCov.start do
root "plugins/discourse-custom-wizard" root "plugins/discourse-custom-wizard"
track_files "plugins/discourse-custom-wizard/**/*.rb" 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 SimpleCov.minimum_coverage 80
end end
end end

Datei anzeigen

@ -17,9 +17,9 @@ describe CustomWizard::AdminCustomFieldsController do
sign_in(admin_user) sign_in(admin_user)
end end
it "returns the list of custom fields" do it "returns the full list of custom fields" do
get "/admin/wizards/custom-fields.json" get "/admin/wizards/custom-fields.json"
expect(response.parsed_body.length).to eq(4) expect(response.parsed_body.length).to eq(15)
end end
it "saves custom fields" do it "saves custom fields" do

Datei anzeigen

@ -43,6 +43,12 @@ describe ApplicationController do
.first['redirect_to'] .first['redirect_to']
).to eq("/t/2") ).to eq("/t/2")
end 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 end
context "who is not required to complete wizard" do context "who is not required to complete wizard" do

Datei anzeigen

@ -249,4 +249,33 @@ describe CustomWizard::StepsController do
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.parsed_body['final']).to eq(true) expect(response.parsed_body['final']).to eq(true)
end 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 end

Datei anzeigen

@ -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 before do
CustomWizard::Template.save( CustomWizard::Template.save(
JSON.parse(File.open( JSON.parse(File.open(
@ -47,6 +55,14 @@ describe CustomWizard::WizardController do
expect(response.status).to eq(200) expect(response.status).to eq(200)
end 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 it 'returns a no skip message if user is not allowed to skip' do
@template['required'] = 'true' @template['required'] = 'true'
CustomWizard::Template.save(@template) CustomWizard::Template.save(@template)