Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-25 18:50:27 +01:00
Commit
c182435805
47 geänderte Dateien mit 1066 neuen und 510 gelöschten Zeilen
1
.gitignore
gevendort
Normale Datei
1
.gitignore
gevendort
Normale Datei
|
@ -0,0 +1 @@
|
|||
coverage
|
29
.travis.yml
29
.travis.yml
|
@ -1,27 +1,12 @@
|
|||
# Uncomment tests runner when tests are added.
|
||||
|
||||
# We want to use the KVM-based system, so require sudo
|
||||
sudo: required
|
||||
#names:
|
||||
#- docker
|
||||
services:
|
||||
- docker
|
||||
|
||||
before_install:
|
||||
- plugin_name=${PWD##*/} && echo $plugin_name
|
||||
- chmod -R 777 .
|
||||
- git clone --depth=1 https://github.com/discourse/discourse-plugin-ci
|
||||
|
||||
#script:
|
||||
#- >
|
||||
#docker run
|
||||
#-e "COMMIT_HASH=origin/tests-passed"
|
||||
#-e "SKIP_LINT=1"
|
||||
#-e "RUBY_ONLY=1"
|
||||
#-e SINGLE_PLUGIN=$plugin_name
|
||||
#-v $(pwd):/var/www/discourse/plugins/$plugin_name
|
||||
#discourse/discourse_test:release
|
||||
install: true # Prevent travis doing bundle install
|
||||
|
||||
after_success:
|
||||
- pip install virtualenv
|
||||
- virtualenv ~/env
|
||||
- source ~/env/bin/activate
|
||||
- pip install transifex-client
|
||||
- sudo echo $'[https://www.transifex.com]\nhostname = https://www.transifex.com\nusername = '"$TRANSIFEX_USER"$'\npassword = '"$TRANSIFEX_PASSWORD"$'\ntoken = '"$TRANSIFEX_API_TOKEN"$'\n' > ~/.transifexrc
|
||||
- tx push -s
|
||||
script:
|
||||
- discourse-plugin-ci/script.sh
|
||||
|
|
|
@ -3,7 +3,11 @@ import DiscourseURL from 'discourse/lib/url';
|
|||
|
||||
export default {
|
||||
name: 'custom-wizard-edits',
|
||||
initialize() {
|
||||
initialize(container) {
|
||||
const siteSettings = container.lookup('site-settings:main');
|
||||
|
||||
if (!siteSettings.custom_wizard_enabled) return;
|
||||
|
||||
withPluginApi('0.8.12', api => {
|
||||
api.modifyClass('component:global-notice', {
|
||||
buildBuffer(buffer) {
|
||||
|
|
|
@ -6,8 +6,9 @@ export default {
|
|||
|
||||
initialize: function (container) {
|
||||
const messageBus = container.lookup('message-bus:main');
|
||||
|
||||
if (!messageBus) { return; }
|
||||
const siteSettings = container.lookup('site-settings:main');
|
||||
|
||||
if (!siteSettings.custom_wizard_enabled || !messageBus) return;
|
||||
|
||||
messageBus.subscribe("/redirect_to_wizard", function (wizardId) {
|
||||
const wizardUrl = window.location.origin + '/w/' + wizardId;
|
||||
|
|
|
@ -8,6 +8,7 @@ en:
|
|||
custom_title: "Wizard"
|
||||
field:
|
||||
too_short: "%{label} must be at least %{min} characters"
|
||||
required: "%{label} is required."
|
||||
none: "We couldn't find a wizard at that address."
|
||||
no_skip: "Wizard can't be skipped"
|
||||
export:
|
||||
|
@ -21,5 +22,6 @@ en:
|
|||
no_valid_wizards: "File doesn't contain any valid wizards"
|
||||
|
||||
site_settings:
|
||||
custom_wizard_enabled: "Enable custom wizards."
|
||||
wizard_redirect_exclude_paths: "Routes excluded from wizard redirects."
|
||||
wizard_recognised_image_upload_formats: "File types which will result in upload displaying an image preview"
|
36
config/routes.rb
Normale Datei
36
config/routes.rb
Normale Datei
|
@ -0,0 +1,36 @@
|
|||
CustomWizard::Engine.routes.draw do
|
||||
get ':wizard_id' => 'wizard#index'
|
||||
put ':wizard_id/skip' => 'wizard#skip'
|
||||
get ':wizard_id/steps' => 'wizard#index'
|
||||
get ':wizard_id/steps/:step_id' => 'wizard#index'
|
||||
put ':wizard_id/steps/:step_id' => 'steps#update'
|
||||
end
|
||||
|
||||
Discourse::Application.routes.append do
|
||||
mount ::CustomWizard::Engine, at: 'w'
|
||||
post 'wizard/authorization/callback' => "custom_wizard/authorization#callback"
|
||||
|
||||
scope module: 'custom_wizard', constraints: AdminConstraint.new do
|
||||
get 'admin/wizards' => 'admin#index'
|
||||
get 'admin/wizards/field-types' => 'admin#field_types'
|
||||
get 'admin/wizards/custom' => 'admin#index'
|
||||
get 'admin/wizards/custom/new' => 'admin#index'
|
||||
get 'admin/wizards/custom/all' => 'admin#custom_wizards'
|
||||
get 'admin/wizards/custom/:wizard_id' => 'admin#find_wizard'
|
||||
put 'admin/wizards/custom/save' => 'admin#save'
|
||||
delete 'admin/wizards/custom/remove' => 'admin#remove'
|
||||
get 'admin/wizards/submissions' => 'admin#index'
|
||||
get 'admin/wizards/submissions/:wizard_id' => 'admin#submissions'
|
||||
get 'admin/wizards/apis' => 'api#list'
|
||||
get 'admin/wizards/apis/new' => 'api#index'
|
||||
get 'admin/wizards/apis/:name' => 'api#find'
|
||||
put 'admin/wizards/apis/:name' => 'api#save'
|
||||
delete 'admin/wizards/apis/:name' => 'api#remove'
|
||||
delete 'admin/wizards/apis/logs/:name' => 'api#clearlogs'
|
||||
get 'admin/wizards/apis/:name/redirect' => 'api#redirect'
|
||||
get 'admin/wizards/apis/:name/authorize' => 'api#authorize'
|
||||
get 'admin/wizards/transfer' => 'transfer#index'
|
||||
get 'admin/wizards/transfer/export' => 'transfer#export'
|
||||
post 'admin/wizards/transfer/import' => 'transfer#import'
|
||||
end
|
||||
end
|
|
@ -1,4 +1,5 @@
|
|||
plugins:
|
||||
custom_wizard_enabled: true
|
||||
wizard_redirect_exclude_paths:
|
||||
client: true
|
||||
type: list
|
||||
|
|
27
controllers/application_controller.rb
Normale Datei
27
controllers/application_controller.rb
Normale Datei
|
@ -0,0 +1,27 @@
|
|||
module ApplicationControllerCWExtension
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :redirect_to_wizard_if_required, if: :current_user
|
||||
end
|
||||
|
||||
def redirect_to_wizard_if_required
|
||||
wizard_id = current_user.custom_fields['redirect_to_wizard']
|
||||
@excluded_routes ||= SiteSetting.wizard_redirect_exclude_paths.split('|') + ['/w/']
|
||||
url = request.referer || request.original_url
|
||||
|
||||
if request.format === 'text/html' && !@excluded_routes.any? {|str| /#{str}/ =~ url} && wizard_id
|
||||
if request.referer !~ /\/w\// && request.referer !~ /\/invites\//
|
||||
CustomWizard::Wizard.set_submission_redirect(current_user, wizard_id, request.referer)
|
||||
end
|
||||
|
||||
if CustomWizard::Wizard.exists?(wizard_id)
|
||||
redirect_to "/w/#{wizard_id.dasherize}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ApplicationController
|
||||
prepend ApplicationControllerCWExtension if SiteSetting.custom_wizard_enabled
|
||||
end
|
20
controllers/extra_locales_controller.rb
Normale Datei
20
controllers/extra_locales_controller.rb
Normale Datei
|
@ -0,0 +1,20 @@
|
|||
module CustomWizardExtraLocalesController
|
||||
def show
|
||||
if request.referer && URI(request.referer).path.include?('/w/')
|
||||
bundle = params[:bundle]
|
||||
|
||||
if params[:v]&.size == 32
|
||||
hash = ExtraLocalesController.bundle_js_hash(bundle)
|
||||
immutable_for(1.year) if hash == params[:v]
|
||||
end
|
||||
|
||||
render plain: ExtraLocalesController.bundle_js(bundle), content_type: "application/javascript"
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ExtraLocalesController
|
||||
prepend CustomWizardExtraLocalesController if SiteSetting.custom_wizard_enabled
|
||||
end
|
22
controllers/invites_controller.rb
Normale Datei
22
controllers/invites_controller.rb
Normale Datei
|
@ -0,0 +1,22 @@
|
|||
module InvitesControllerCustomWizard
|
||||
def path(url)
|
||||
if Wizard.user_requires_completion?(@user)
|
||||
wizard_id = @user.custom_fields['custom_wizard_redirect']
|
||||
|
||||
if wizard_id && url != '/'
|
||||
CustomWizard::Wizard.set_submission_redirect(@user, wizard_id, url)
|
||||
url = "/w/#{wizard_id.dasherize}"
|
||||
end
|
||||
end
|
||||
super(url)
|
||||
end
|
||||
|
||||
private def post_process_invite(user)
|
||||
super(user)
|
||||
@user = user
|
||||
end
|
||||
end
|
||||
|
||||
class InvitesController
|
||||
prepend InvitesControllerCustomWizard if SiteSetting.custom_wizard_enabled
|
||||
end
|
|
@ -77,112 +77,119 @@ class CustomWizard::Builder
|
|||
end
|
||||
|
||||
def build(build_opts = {}, params = {})
|
||||
unless (@wizard.completed? && !@wizard.multiple_submissions && !@wizard.user.admin) || !@steps || !@wizard.permitted?
|
||||
reset_submissions if build_opts[:reset]
|
||||
|
||||
return @wizard if !SiteSetting.custom_wizard_enabled ||
|
||||
(!@wizard.multiple_submissions &&
|
||||
@wizard.completed? &&
|
||||
!@wizard.user.admin) ||
|
||||
!@steps ||
|
||||
!@wizard.permitted?
|
||||
|
||||
reset_submissions if build_opts[:reset]
|
||||
|
||||
@steps.each do |step_template|
|
||||
@wizard.append_step(step_template['id']) do |step|
|
||||
step.title = step_template['title'] if step_template['title']
|
||||
step.description = step_template['description'] if step_template['description']
|
||||
step.banner = step_template['banner'] if step_template['banner']
|
||||
step.key = step_template['key'] if step_template['key']
|
||||
step.permitted = true
|
||||
@steps.each do |step_template|
|
||||
@wizard.append_step(step_template['id']) do |step|
|
||||
step.title = step_template['title'] if step_template['title']
|
||||
step.description = step_template['description'] if step_template['description']
|
||||
step.banner = step_template['banner'] if step_template['banner']
|
||||
step.key = step_template['key'] if step_template['key']
|
||||
step.permitted = true
|
||||
|
||||
if permitted_params = step_template['permitted_params']
|
||||
permitted_data = {}
|
||||
if permitted_params = step_template['permitted_params']
|
||||
permitted_data = {}
|
||||
|
||||
permitted_params.each do |param|
|
||||
key = param['key'].to_sym
|
||||
permitted_data[key] = params[key] if params[key]
|
||||
end
|
||||
|
||||
if permitted_data.present?
|
||||
current_data = @submissions.last || {}
|
||||
save_submissions(current_data.merge(permitted_data), false)
|
||||
end
|
||||
permitted_params.each do |p|
|
||||
params_key = p['key'].to_sym
|
||||
submission_key = p['value'].to_sym
|
||||
permitted_data[submission_key] = params[params_key] if params[params_key]
|
||||
end
|
||||
|
||||
if required_data = step_template['required_data']
|
||||
if !@submissions.last && required_data.present?
|
||||
step.permitted = false
|
||||
next
|
||||
end
|
||||
if permitted_data.present?
|
||||
current_data = @submissions.last || {}
|
||||
save_submissions(current_data.merge(permitted_data), false)
|
||||
end
|
||||
end
|
||||
|
||||
required_data.each do |rd|
|
||||
if rd['connector'] === 'equals'
|
||||
step.permitted = @submissions.last[rd['key']] == @submissions.last[rd['value']]
|
||||
end
|
||||
end
|
||||
|
||||
if !step.permitted
|
||||
step.permitted_message = step_template['required_data_message'] if step_template['required_data_message']
|
||||
next
|
||||
end
|
||||
if required_data = step_template['required_data']
|
||||
if !@submissions.last && required_data.present?
|
||||
step.permitted = false
|
||||
next
|
||||
end
|
||||
|
||||
required_data.each do |rd|
|
||||
if rd['connector'] === 'equals'
|
||||
step.permitted = @submissions.last[rd['key']] == @submissions.last[rd['value']]
|
||||
end
|
||||
end
|
||||
|
||||
if !step.permitted
|
||||
step.permitted_message = step_template['required_data_message'] if step_template['required_data_message']
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
if step_template['fields'] && step_template['fields'].length
|
||||
step_template['fields'].each do |field_template|
|
||||
append_field(step, step_template, field_template, build_opts)
|
||||
end
|
||||
end
|
||||
|
||||
step.on_update do |updater|
|
||||
@updater = updater
|
||||
user = @wizard.user
|
||||
|
||||
if step_template['fields'] && step_template['fields'].length
|
||||
step_template['fields'].each do |field_template|
|
||||
append_field(step, step_template, field_template, build_opts)
|
||||
step_template['fields'].each do |field|
|
||||
validate_field(field, updater, step_template) if field['type'] != 'text-only'
|
||||
end
|
||||
end
|
||||
|
||||
next if updater.errors.any?
|
||||
|
||||
CustomWizard::Builder.step_handlers.each do |handler|
|
||||
if handler[:wizard_id] == @wizard.id
|
||||
handler[:block].call(self)
|
||||
end
|
||||
end
|
||||
|
||||
step.on_update do |updater|
|
||||
@updater = updater
|
||||
user = @wizard.user
|
||||
|
||||
if step_template['fields'] && step_template['fields'].length
|
||||
step_template['fields'].each do |field|
|
||||
validate_field(field, updater, step_template) if field['type'] != 'text-only'
|
||||
end
|
||||
next if updater.errors.any?
|
||||
|
||||
data = updater.fields
|
||||
|
||||
## if the wizard has data from the previous steps make that accessible to the actions.
|
||||
if @submissions && @submissions.last && !@submissions.last.key?("submitted_at")
|
||||
submission = @submissions.last
|
||||
data = submission.merge(data)
|
||||
end
|
||||
|
||||
if step_template['actions'] && step_template['actions'].length && data
|
||||
step_template['actions'].each do |action|
|
||||
self.send(action['type'].to_sym, user, action, data)
|
||||
end
|
||||
|
||||
next if updater.errors.any?
|
||||
end
|
||||
|
||||
CustomWizard::Builder.step_handlers.each do |handler|
|
||||
if handler[:wizard_id] == @wizard.id
|
||||
handler[:block].call(self)
|
||||
end
|
||||
end
|
||||
final_step = updater.step.next.nil?
|
||||
|
||||
if route_to = data['route_to']
|
||||
data.delete('route_to')
|
||||
end
|
||||
|
||||
next if updater.errors.any?
|
||||
if @wizard.save_submissions && updater.errors.empty?
|
||||
save_submissions(data, final_step)
|
||||
elsif final_step
|
||||
PluginStore.remove("#{@wizard.id}_submissions", @wizard.user.id)
|
||||
end
|
||||
|
||||
data = updater.fields.to_h
|
||||
if final_step && @wizard.id === @wizard.user.custom_fields['redirect_to_wizard']
|
||||
@wizard.user.custom_fields.delete('redirect_to_wizard');
|
||||
@wizard.user.save_custom_fields(true)
|
||||
end
|
||||
|
||||
## if the wizard has data from the previous steps make that accessible to the actions.
|
||||
if @submissions && @submissions.last && !@submissions.last.key?("submitted_at")
|
||||
submission = @submissions.last
|
||||
data = submission.merge(data)
|
||||
end
|
||||
|
||||
if step_template['actions'] && step_template['actions'].length && data
|
||||
step_template['actions'].each do |action|
|
||||
self.send(action['type'].to_sym, user, action, data)
|
||||
end
|
||||
end
|
||||
|
||||
final_step = updater.step.next.nil?
|
||||
|
||||
if route_to = data['route_to']
|
||||
data.delete('route_to')
|
||||
end
|
||||
|
||||
if @wizard.save_submissions && updater.errors.empty?
|
||||
save_submissions(data, final_step)
|
||||
elsif final_step
|
||||
PluginStore.remove("#{@wizard.id}_submissions", @wizard.user.id)
|
||||
end
|
||||
|
||||
if final_step && @wizard.id === @wizard.user.custom_fields['redirect_to_wizard']
|
||||
@wizard.user.custom_fields.delete('redirect_to_wizard');
|
||||
@wizard.user.save_custom_fields(true)
|
||||
end
|
||||
|
||||
if updater.errors.empty?
|
||||
if final_step
|
||||
updater.result[:redirect_on_complete] = route_to || data['redirect_on_complete']
|
||||
elsif route_to
|
||||
updater.result[:redirect_on_next] = route_to
|
||||
end
|
||||
if updater.errors.empty?
|
||||
if final_step
|
||||
updater.result[:redirect_on_complete] = route_to || data['redirect_on_complete']
|
||||
elsif route_to
|
||||
updater.result[:redirect_on_next] = route_to
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -209,7 +216,7 @@ class CustomWizard::Builder
|
|||
submission = @submissions.last
|
||||
params[:value] = submission[field_template['id']] if submission[field_template['id']]
|
||||
end
|
||||
|
||||
|
||||
## If a field updates a profile field, load the current value
|
||||
if step_template['actions'] && step_template['actions'].any?
|
||||
profile_actions = step_template['actions'].select { |a| a['type'] === 'update_profile' }
|
||||
|
@ -217,7 +224,7 @@ class CustomWizard::Builder
|
|||
if profile_actions.any?
|
||||
profile_actions.each do |action|
|
||||
if update = action['profile_updates'].select { |u| u['key'] === field_template['id'] }.first
|
||||
params[:value] = prefill_profile_field(update)
|
||||
params[:value] = prefill_profile_field(update) || params[:value]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -313,13 +320,17 @@ class CustomWizard::Builder
|
|||
def validate_field(field, updater, step_template)
|
||||
value = updater.fields[field['id']]
|
||||
min_length = false
|
||||
label = field['label'] || I18n.t("#{field['key']}.label")
|
||||
|
||||
if field['required'] && !value
|
||||
updater.errors.add(field['id'].to_s, I18n.t('wizard.field.required', label: label))
|
||||
end
|
||||
|
||||
if is_text_type(field)
|
||||
min_length = field['min_length']
|
||||
end
|
||||
|
||||
if min_length && value.is_a?(String) && value.strip.length < min_length.to_i
|
||||
label = field['label'] || I18n.t("#{field['key']}.label")
|
||||
updater.errors.add(field['id'].to_s, I18n.t('wizard.field.too_short', label: label, min: min_length.to_i))
|
||||
end
|
||||
|
||||
|
@ -349,7 +360,7 @@ class CustomWizard::Builder
|
|||
else
|
||||
title = data[action['title']]
|
||||
end
|
||||
|
||||
|
||||
if action['post_builder']
|
||||
post = CustomWizard::Builder.fill_placeholders(action['post_template'], user, data)
|
||||
else
|
6
lib/custom_wizard/engine.rb
Normale Datei
6
lib/custom_wizard/engine.rb
Normale Datei
|
@ -0,0 +1,6 @@
|
|||
module ::CustomWizard
|
||||
class Engine < ::Rails::Engine
|
||||
engine_name 'custom_wizard'
|
||||
isolate_namespace CustomWizard
|
||||
end
|
||||
end
|
|
@ -8,11 +8,13 @@ class CustomWizard::StepUpdater
|
|||
@wizard = wizard
|
||||
@step = step
|
||||
@refresh_required = false
|
||||
@fields = fields
|
||||
@fields = fields.to_h.with_indifferent_access
|
||||
@result = {}
|
||||
end
|
||||
|
||||
def update
|
||||
return false if !SiteSetting.custom_wizard_enabled
|
||||
|
||||
@step.updater.call(self) if @step.present? && @step.updater.present?
|
||||
|
||||
if success?
|
|
@ -3,6 +3,8 @@ require_dependency 'wizard/field'
|
|||
require_dependency 'wizard/step_updater'
|
||||
require_dependency 'wizard/builder'
|
||||
|
||||
UserHistory.actions[:custom_wizard_step] = 1000
|
||||
|
||||
class CustomWizard::Wizard
|
||||
|
||||
attr_reader :steps, :user
|
||||
|
@ -190,8 +192,8 @@ class CustomWizard::Wizard
|
|||
end
|
||||
end
|
||||
|
||||
def self.add_wizard(json)
|
||||
wizard = ::JSON.parse(json)
|
||||
def self.add_wizard(obj)
|
||||
wizard = obj.is_a?(String) ? ::JSON.parse(json) : obj
|
||||
PluginStore.set('custom_wizard', wizard["id"], wizard)
|
||||
end
|
||||
|
17
lib/wizard/choice.rb
Normale Datei
17
lib/wizard/choice.rb
Normale Datei
|
@ -0,0 +1,17 @@
|
|||
module CustomWizardChoiceExtension
|
||||
def initialize(id, opts)
|
||||
@id = id
|
||||
@opts = opts
|
||||
@data = opts[:data]
|
||||
@extra_label = opts[:extra_label]
|
||||
@icon = opts[:icon]
|
||||
end
|
||||
|
||||
def label
|
||||
@label ||= PrettyText.cook(@opts[:label])
|
||||
end
|
||||
end
|
||||
|
||||
class Wizard::Choice
|
||||
prepend CustomWizardChoiceExtension if SiteSetting.custom_wizard_enabled
|
||||
end
|
37
lib/wizard/field.rb
Normale Datei
37
lib/wizard/field.rb
Normale Datei
|
@ -0,0 +1,37 @@
|
|||
module CustomWizardFieldExtension
|
||||
attr_reader :label,
|
||||
:description,
|
||||
:image,
|
||||
:key,
|
||||
:min_length,
|
||||
:file_types,
|
||||
:limit,
|
||||
:property
|
||||
|
||||
attr_accessor :dropdown_none
|
||||
|
||||
def initialize(attrs)
|
||||
@attrs = attrs || {}
|
||||
@id = attrs[:id]
|
||||
@type = attrs[:type]
|
||||
@required = !!attrs[:required]
|
||||
@description = attrs[:description]
|
||||
@image = attrs[:image]
|
||||
@key = attrs[:key]
|
||||
@min_length = attrs[:min_length]
|
||||
@value = attrs[:value]
|
||||
@choices = []
|
||||
@dropdown_none = attrs[:dropdown_none]
|
||||
@file_types = attrs[:file_types]
|
||||
@limit = attrs[:limit]
|
||||
@property = attrs[:property]
|
||||
end
|
||||
|
||||
def label
|
||||
@label ||= PrettyText.cook(@attrs[:label])
|
||||
end
|
||||
end
|
||||
|
||||
class Wizard::Field
|
||||
prepend CustomWizardFieldExtension if SiteSetting.custom_wizard_enabled
|
||||
end
|
7
lib/wizard/step.rb
Normale Datei
7
lib/wizard/step.rb
Normale Datei
|
@ -0,0 +1,7 @@
|
|||
module CustomWizardStepExtension
|
||||
attr_accessor :title, :description, :key, :permitted, :permitted_message
|
||||
end
|
||||
|
||||
class Wizard::Step
|
||||
prepend CustomWizardStepExtension if SiteSetting.custom_wizard_enabled
|
||||
end
|
|
@ -1,227 +0,0 @@
|
|||
require_dependency 'wizard'
|
||||
require_dependency 'wizard/field'
|
||||
require_dependency 'wizard/step'
|
||||
|
||||
::Wizard.class_eval do
|
||||
def self.user_requires_completion?(user)
|
||||
wizard_result = self.new(user).requires_completion?
|
||||
return wizard_result if wizard_result
|
||||
|
||||
custom_redirect = false
|
||||
|
||||
if user && user.first_seen_at.blank? && wizard_id = CustomWizard::Wizard.after_signup
|
||||
wizard = CustomWizard::Wizard.create(user, wizard_id)
|
||||
|
||||
if !wizard.completed? && wizard.permitted?
|
||||
custom_redirect = true
|
||||
CustomWizard::Wizard.set_wizard_redirect(user, wizard_id)
|
||||
end
|
||||
end
|
||||
|
||||
!!custom_redirect
|
||||
end
|
||||
end
|
||||
|
||||
::Wizard::Field.class_eval do
|
||||
attr_reader :label, :description, :image, :key, :min_length, :file_types, :limit, :property
|
||||
attr_accessor :dropdown_none
|
||||
|
||||
def initialize(attrs)
|
||||
@attrs = attrs || {}
|
||||
@id = attrs[:id]
|
||||
@type = attrs[:type]
|
||||
@required = !!attrs[:required]
|
||||
@description = attrs[:description]
|
||||
@image = attrs[:image]
|
||||
@key = attrs[:key]
|
||||
@min_length = attrs[:min_length]
|
||||
@value = attrs[:value]
|
||||
@choices = []
|
||||
@dropdown_none = attrs[:dropdown_none]
|
||||
@file_types = attrs[:file_types]
|
||||
@limit = attrs[:limit]
|
||||
@property = attrs[:property]
|
||||
end
|
||||
|
||||
def label
|
||||
@label ||= PrettyText.cook(@attrs[:label])
|
||||
end
|
||||
end
|
||||
|
||||
::Wizard::Choice.class_eval do
|
||||
def initialize(id, opts)
|
||||
@id = id
|
||||
@opts = opts
|
||||
@data = opts[:data]
|
||||
@extra_label = opts[:extra_label]
|
||||
@icon = opts[:icon]
|
||||
end
|
||||
|
||||
def label
|
||||
@label ||= PrettyText.cook(@opts[:label])
|
||||
end
|
||||
end
|
||||
|
||||
class ::Wizard::Step
|
||||
attr_accessor :title, :description, :key, :permitted, :permitted_message
|
||||
end
|
||||
|
||||
::WizardSerializer.class_eval do
|
||||
attributes :id,
|
||||
:name,
|
||||
:background,
|
||||
:completed,
|
||||
:required,
|
||||
:min_trust,
|
||||
:permitted,
|
||||
:user,
|
||||
:categories,
|
||||
:uncategorized_category_id
|
||||
|
||||
def id
|
||||
object.id
|
||||
end
|
||||
|
||||
def include_id?
|
||||
object.respond_to?(:id)
|
||||
end
|
||||
|
||||
def name
|
||||
object.name
|
||||
end
|
||||
|
||||
def include_name?
|
||||
object.respond_to?(:name)
|
||||
end
|
||||
|
||||
def background
|
||||
object.background
|
||||
end
|
||||
|
||||
def include_background?
|
||||
object.respond_to?(:background)
|
||||
end
|
||||
|
||||
def completed
|
||||
object.completed?
|
||||
end
|
||||
|
||||
def include_completed?
|
||||
object.completed? &&
|
||||
(!object.respond_to?(:multiple_submissions) || !object.multiple_submissions) &&
|
||||
!scope.is_admin?
|
||||
end
|
||||
|
||||
def min_trust
|
||||
object.min_trust
|
||||
end
|
||||
|
||||
def include_min_trust?
|
||||
object.respond_to?(:min_trust)
|
||||
end
|
||||
|
||||
def permitted
|
||||
object.permitted?
|
||||
end
|
||||
|
||||
def include_permitted?
|
||||
object.respond_to?(:permitted?)
|
||||
end
|
||||
|
||||
def include_start?
|
||||
object.start && include_steps?
|
||||
end
|
||||
|
||||
def include_steps?
|
||||
!include_completed?
|
||||
end
|
||||
|
||||
def required
|
||||
object.required
|
||||
end
|
||||
|
||||
def include_required?
|
||||
object.respond_to?(:required)
|
||||
end
|
||||
|
||||
def user
|
||||
object.user
|
||||
end
|
||||
|
||||
def categories
|
||||
begin
|
||||
site = ::Site.new(scope)
|
||||
::ActiveModel::ArraySerializer.new(site.categories, each_serializer: BasicCategorySerializer)
|
||||
rescue => e
|
||||
puts "HERE IS THE ERROR: #{e.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def uncategorized_category_id
|
||||
SiteSetting.uncategorized_category_id
|
||||
end
|
||||
end
|
||||
|
||||
::WizardStepSerializer.class_eval do
|
||||
attributes :permitted, :permitted_message
|
||||
|
||||
def title
|
||||
return PrettyText.cook(object.title) if object.title
|
||||
PrettyText.cook(I18n.t("#{object.key || i18n_key}.title", default: ''))
|
||||
end
|
||||
|
||||
def description
|
||||
return object.description if object.description
|
||||
PrettyText.cook(I18n.t("#{object.key || i18n_key}.description", default: '', base_url: Discourse.base_url))
|
||||
end
|
||||
|
||||
def permitted
|
||||
object.permitted
|
||||
end
|
||||
|
||||
def permitted_message
|
||||
object.permitted_message
|
||||
end
|
||||
end
|
||||
|
||||
::WizardFieldSerializer.class_eval do
|
||||
attributes :dropdown_none, :image, :file_types, :limit, :property
|
||||
|
||||
def label
|
||||
return object.label if object.label.present?
|
||||
I18n.t("#{object.key || i18n_key}.label", default: '')
|
||||
end
|
||||
|
||||
def description
|
||||
return object.description if object.description.present?
|
||||
I18n.t("#{object.key || i18n_key}.description", default: '', base_url: Discourse.base_url)
|
||||
end
|
||||
|
||||
def image
|
||||
object.image
|
||||
end
|
||||
|
||||
def include_image?
|
||||
object.image.present?
|
||||
end
|
||||
|
||||
def placeholder
|
||||
I18n.t("#{object.key || i18n_key}.placeholder", default: '')
|
||||
end
|
||||
|
||||
def dropdown_none
|
||||
object.dropdown_none
|
||||
end
|
||||
|
||||
def file_types
|
||||
object.file_types
|
||||
end
|
||||
|
||||
def limit
|
||||
object.limit
|
||||
end
|
||||
|
||||
def property
|
||||
object.property
|
||||
end
|
||||
end
|
229
plugin.rb
229
plugin.rb
|
@ -8,10 +8,12 @@ register_asset 'stylesheets/wizard_custom_admin.scss'
|
|||
register_asset 'lib/jquery.timepicker.min.js'
|
||||
register_asset 'lib/jquery.timepicker.scss'
|
||||
|
||||
config = Rails.application.config
|
||||
config.assets.paths << Rails.root.join('plugins', 'discourse-custom-wizard', 'assets', 'javascripts')
|
||||
config.assets.paths << Rails.root.join('plugins', 'discourse-custom-wizard', 'assets', 'stylesheets', 'wizard')
|
||||
enabled_site_setting :custom_wizard_enabled
|
||||
|
||||
config = Rails.application.config
|
||||
plugin_asset_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets"
|
||||
config.assets.paths << "#{plugin_asset_path}/javascripts"
|
||||
config.assets.paths << "#{plugin_asset_path}/stylesheets/wizard"
|
||||
|
||||
if Rails.env.production?
|
||||
config.assets.precompile += %w{
|
||||
|
@ -35,181 +37,84 @@ if respond_to?(:register_svg_icon)
|
|||
end
|
||||
|
||||
after_initialize do
|
||||
UserHistory.actions[:custom_wizard_step] = 1000
|
||||
|
||||
module ::CustomWizard
|
||||
class Engine < ::Rails::Engine
|
||||
engine_name 'custom_wizard'
|
||||
isolate_namespace CustomWizard
|
||||
end
|
||||
end
|
||||
|
||||
CustomWizard::Engine.routes.draw do
|
||||
get ':wizard_id' => 'wizard#index'
|
||||
put ':wizard_id/skip' => 'wizard#skip'
|
||||
get ':wizard_id/steps' => 'wizard#index'
|
||||
get ':wizard_id/steps/:step_id' => 'wizard#index'
|
||||
put ':wizard_id/steps/:step_id' => 'steps#update'
|
||||
end
|
||||
|
||||
Discourse::Application.routes.append do
|
||||
mount ::CustomWizard::Engine, at: 'w'
|
||||
post 'wizard/authorization/callback' => "custom_wizard/authorization#callback"
|
||||
|
||||
scope module: 'custom_wizard', constraints: AdminConstraint.new do
|
||||
get 'admin/wizards' => 'admin#index'
|
||||
get 'admin/wizards/field-types' => 'admin#field_types'
|
||||
get 'admin/wizards/custom' => 'admin#index'
|
||||
get 'admin/wizards/custom/new' => 'admin#index'
|
||||
get 'admin/wizards/custom/all' => 'admin#custom_wizards'
|
||||
get 'admin/wizards/custom/:wizard_id' => 'admin#find_wizard'
|
||||
put 'admin/wizards/custom/save' => 'admin#save'
|
||||
delete 'admin/wizards/custom/remove' => 'admin#remove'
|
||||
get 'admin/wizards/submissions' => 'admin#index'
|
||||
get 'admin/wizards/submissions/:wizard_id' => 'admin#submissions'
|
||||
get 'admin/wizards/apis' => 'api#list'
|
||||
get 'admin/wizards/apis/new' => 'api#index'
|
||||
get 'admin/wizards/apis/:name' => 'api#find'
|
||||
put 'admin/wizards/apis/:name' => 'api#save'
|
||||
delete 'admin/wizards/apis/:name' => 'api#remove'
|
||||
delete 'admin/wizards/apis/logs/:name' => 'api#clearlogs'
|
||||
get 'admin/wizards/apis/:name/redirect' => 'api#redirect'
|
||||
get 'admin/wizards/apis/:name/authorize' => 'api#authorize'
|
||||
get 'admin/wizards/transfer' => 'transfer#index'
|
||||
get 'admin/wizards/transfer/export' => 'transfer#export'
|
||||
post 'admin/wizards/transfer/import' => 'transfer#import'
|
||||
end
|
||||
end
|
||||
|
||||
load File.expand_path('../jobs/clear_after_time_wizard.rb', __FILE__)
|
||||
load File.expand_path('../jobs/set_after_time_wizard.rb', __FILE__)
|
||||
load File.expand_path('../lib/builder.rb', __FILE__)
|
||||
load File.expand_path('../lib/field.rb', __FILE__)
|
||||
load File.expand_path('../lib/step_updater.rb', __FILE__)
|
||||
load File.expand_path('../lib/template.rb', __FILE__)
|
||||
load File.expand_path('../lib/wizard.rb', __FILE__)
|
||||
load File.expand_path('../lib/wizard_edits.rb', __FILE__)
|
||||
load File.expand_path('../controllers/wizard.rb', __FILE__)
|
||||
load File.expand_path('../controllers/steps.rb', __FILE__)
|
||||
load File.expand_path('../controllers/admin.rb', __FILE__)
|
||||
#transfer code
|
||||
load File.expand_path('../controllers/transfer.rb', __FILE__)
|
||||
|
||||
load File.expand_path('../jobs/refresh_api_access_token.rb', __FILE__)
|
||||
load File.expand_path('../lib/api/api.rb', __FILE__)
|
||||
load File.expand_path('../lib/api/authorization.rb', __FILE__)
|
||||
load File.expand_path('../lib/api/endpoint.rb', __FILE__)
|
||||
load File.expand_path('../lib/api/log_entry.rb', __FILE__)
|
||||
load File.expand_path('../controllers/api.rb', __FILE__)
|
||||
load File.expand_path('../serializers/api/api_serializer.rb', __FILE__)
|
||||
load File.expand_path('../serializers/api/authorization_serializer.rb', __FILE__)
|
||||
load File.expand_path('../serializers/api/basic_api_serializer.rb', __FILE__)
|
||||
load File.expand_path('../serializers/api/endpoint_serializer.rb', __FILE__)
|
||||
load File.expand_path('../serializers/api/basic_endpoint_serializer.rb', __FILE__)
|
||||
load File.expand_path('../serializers/api/log_serializer.rb', __FILE__)
|
||||
|
||||
::UsersController.class_eval do
|
||||
def wizard_path
|
||||
if custom_wizard_redirect = current_user.custom_fields['redirect_to_wizard']
|
||||
"#{Discourse.base_url}/w/#{custom_wizard_redirect.dasherize}"
|
||||
else
|
||||
"#{Discourse.base_url}/wizard"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module InvitesControllerCustomWizard
|
||||
def path(url)
|
||||
if Wizard.user_requires_completion?(@user)
|
||||
wizard_id = @user.custom_fields['custom_wizard_redirect']
|
||||
|
||||
if wizard_id && url != '/'
|
||||
CustomWizard::Wizard.set_submission_redirect(@user, wizard_id, url)
|
||||
url = "/w/#{wizard_id.dasherize}"
|
||||
end
|
||||
end
|
||||
super(url)
|
||||
end
|
||||
|
||||
private def post_process_invite(user)
|
||||
super(user)
|
||||
@user = user
|
||||
end
|
||||
[
|
||||
'../lib/custom_wizard/engine.rb',
|
||||
'../config/routes.rb',
|
||||
'../controllers/custom_wizard/wizard.rb',
|
||||
'../controllers/custom_wizard/steps.rb',
|
||||
'../controllers/custom_wizard/admin.rb',
|
||||
'../controllers/custom_wizard/transfer.rb',
|
||||
'../controllers/custom_wizard/api.rb',
|
||||
'../controllers/application_controller.rb',
|
||||
'../controllers/extra_locales_controller.rb',
|
||||
'../controllers/invites_controller.rb',
|
||||
'../jobs/clear_after_time_wizard.rb',
|
||||
'../jobs/refresh_api_access_token.rb',
|
||||
'../jobs/set_after_time_wizard.rb',
|
||||
'../lib/custom_wizard/builder.rb',
|
||||
'../lib/custom_wizard/field.rb',
|
||||
'../lib/custom_wizard/step_updater.rb',
|
||||
'../lib/custom_wizard/template.rb',
|
||||
'../lib/custom_wizard/wizard.rb',
|
||||
'../lib/custom_wizard/api/api.rb',
|
||||
'../lib/custom_wizard/api/authorization.rb',
|
||||
'../lib/custom_wizard/api/endpoint.rb',
|
||||
'../lib/custom_wizard/api/log_entry.rb',
|
||||
'../lib/wizard/choice.rb',
|
||||
'../lib/wizard/field.rb',
|
||||
'../lib/wizard/step.rb',
|
||||
'../serializers/custom_wizard/api_serializer.rb',
|
||||
'../serializers/custom_wizard/basic_api_serializer.rb',
|
||||
'../serializers/custom_wizard/api/authorization_serializer.rb',
|
||||
'../serializers/custom_wizard/api/basic_endpoint_serializer.rb',
|
||||
'../serializers/custom_wizard/api/endpoint_serializer.rb',
|
||||
'../serializers/custom_wizard/api/log_serializer.rb',
|
||||
'../serializers/site_serializer.rb',
|
||||
'../serializers/wizard_serializer.rb',
|
||||
'../serializers/wizard_step_serializer.rb',
|
||||
'../serializers/wizard_field_serializer.rb'
|
||||
].each do |path|
|
||||
load File.expand_path(path, __FILE__)
|
||||
end
|
||||
|
||||
require_dependency 'invites_controller'
|
||||
class ::InvitesController
|
||||
prepend InvitesControllerCustomWizard
|
||||
end
|
||||
|
||||
require_dependency 'application_controller'
|
||||
class ::ApplicationController
|
||||
before_action :redirect_to_wizard_if_required, if: :current_user
|
||||
add_class_method(:wizard, :user_requires_completion?) do |user|
|
||||
wizard_result = self.new(user).requires_completion?
|
||||
return wizard_result if wizard_result
|
||||
|
||||
def redirect_to_wizard_if_required
|
||||
wizard_id = current_user.custom_fields['redirect_to_wizard']
|
||||
@excluded_routes ||= SiteSetting.wizard_redirect_exclude_paths.split('|') + ['/w/']
|
||||
url = request.referer || request.original_url
|
||||
custom_redirect = false
|
||||
|
||||
if request.format === 'text/html' && !@excluded_routes.any? {|str| /#{str}/ =~ url} && wizard_id
|
||||
if request.referer !~ /\/w\// && request.referer !~ /\/invites\//
|
||||
CustomWizard::Wizard.set_submission_redirect(current_user, wizard_id, request.referer)
|
||||
end
|
||||
if user &&
|
||||
user.first_seen_at.blank? &&
|
||||
wizard_id = CustomWizard::Wizard.after_signup
|
||||
|
||||
wizard = CustomWizard::Wizard.create(user, wizard_id)
|
||||
|
||||
if CustomWizard::Wizard.exists?(wizard_id)
|
||||
redirect_to "/w/#{wizard_id.dasherize}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
add_to_serializer(:current_user, :redirect_to_wizard) {object.custom_fields['redirect_to_wizard']}
|
||||
|
||||
## TODO limit this to the first admin
|
||||
SiteSerializer.class_eval do
|
||||
attributes :complete_custom_wizard
|
||||
|
||||
def include_wizard_required?
|
||||
scope.is_admin? && Wizard.new(scope.user).requires_completion?
|
||||
end
|
||||
|
||||
def complete_custom_wizard
|
||||
if scope.user && requires_completion = CustomWizard::Wizard.prompt_completion(scope.user)
|
||||
requires_completion.map {|w| {name: w[:name], url: "/w/#{w[:id]}"}}
|
||||
if !wizard.completed? && wizard.permitted?
|
||||
custom_redirect = true
|
||||
CustomWizard::Wizard.set_wizard_redirect(user, wizard_id)
|
||||
end
|
||||
end
|
||||
|
||||
def include_complete_custom_wizard?
|
||||
complete_custom_wizard.present?
|
||||
!!custom_redirect
|
||||
end
|
||||
|
||||
add_to_class(:users_controller, :wizard_path) do
|
||||
if custom_wizard_redirect = current_user.custom_fields['redirect_to_wizard']
|
||||
"#{Discourse.base_url}/w/#{custom_wizard_redirect.dasherize}"
|
||||
else
|
||||
"#{Discourse.base_url}/wizard"
|
||||
end
|
||||
end
|
||||
|
||||
add_to_serializer(:current_user, :redirect_to_wizard) do
|
||||
object.custom_fields['redirect_to_wizard']
|
||||
end
|
||||
|
||||
DiscourseEvent.on(:user_approved) do |user|
|
||||
if wizard_id = CustomWizard::Wizard.after_signup
|
||||
CustomWizard::Wizard.set_wizard_redirect(user, wizard_id)
|
||||
end
|
||||
end
|
||||
|
||||
module CustomWizardExtraLocalesController
|
||||
def show
|
||||
if request.referer && URI(request.referer).path.include?('/w/')
|
||||
bundle = params[:bundle]
|
||||
|
||||
if params[:v]&.size == 32
|
||||
hash = ExtraLocalesController.bundle_js_hash(bundle)
|
||||
immutable_for(1.year) if hash == params[:v]
|
||||
end
|
||||
|
||||
render plain: ExtraLocalesController.bundle_js(bundle), content_type: "application/javascript"
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ::ExtraLocalesController
|
||||
prepend CustomWizardExtraLocalesController
|
||||
end
|
||||
|
||||
DiscourseEvent.trigger(:custom_wizard_ready)
|
||||
end
|
||||
|
|
27
serializers/site_serializer.rb
Normale Datei
27
serializers/site_serializer.rb
Normale Datei
|
@ -0,0 +1,27 @@
|
|||
## TODO limit this to the first admin
|
||||
module SiteSerializerCWX
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
attributes :complete_custom_wizard
|
||||
end
|
||||
|
||||
|
||||
def include_wizard_required?
|
||||
scope.is_admin? && Wizard.new(scope.user).requires_completion?
|
||||
end
|
||||
|
||||
def complete_custom_wizard
|
||||
if scope.user && requires_completion = CustomWizard::Wizard.prompt_completion(scope.user)
|
||||
requires_completion.map {|w| {name: w[:name], url: "/w/#{w[:id]}"}}
|
||||
end
|
||||
end
|
||||
|
||||
def include_complete_custom_wizard?
|
||||
complete_custom_wizard.present?
|
||||
end
|
||||
end
|
||||
|
||||
class SiteSerializer
|
||||
prepend SiteSerializerCWX if SiteSetting.custom_wizard_enabled
|
||||
end
|
49
serializers/wizard_field_serializer.rb
Normale Datei
49
serializers/wizard_field_serializer.rb
Normale Datei
|
@ -0,0 +1,49 @@
|
|||
module CustomWizardWizardFieldSerializerExtension
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
attributes :dropdown_none, :image, :file_types, :limit, :property
|
||||
end
|
||||
|
||||
def label
|
||||
return object.label if object.label.present?
|
||||
I18n.t("#{object.key || i18n_key}.label", default: '')
|
||||
end
|
||||
|
||||
def description
|
||||
return object.description if object.description.present?
|
||||
I18n.t("#{object.key || i18n_key}.description", default: '', base_url: Discourse.base_url)
|
||||
end
|
||||
|
||||
def image
|
||||
object.image
|
||||
end
|
||||
|
||||
def include_image?
|
||||
object.image.present?
|
||||
end
|
||||
|
||||
def placeholder
|
||||
I18n.t("#{object.key || i18n_key}.placeholder", default: '')
|
||||
end
|
||||
|
||||
def dropdown_none
|
||||
object.dropdown_none
|
||||
end
|
||||
|
||||
def file_types
|
||||
object.file_types
|
||||
end
|
||||
|
||||
def limit
|
||||
object.limit
|
||||
end
|
||||
|
||||
def property
|
||||
object.property
|
||||
end
|
||||
end
|
||||
|
||||
class WizardFieldSerializer
|
||||
prepend CustomWizardWizardFieldSerializerExtension if SiteSetting.custom_wizard_enabled
|
||||
end
|
103
serializers/wizard_serializer.rb
Normale Datei
103
serializers/wizard_serializer.rb
Normale Datei
|
@ -0,0 +1,103 @@
|
|||
module CustomWizardWizardSerializerExtension
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
attributes :id,
|
||||
:name,
|
||||
:background,
|
||||
:completed,
|
||||
:required,
|
||||
:min_trust,
|
||||
:permitted,
|
||||
:user,
|
||||
:categories,
|
||||
:uncategorized_category_id
|
||||
end
|
||||
|
||||
def id
|
||||
object.id
|
||||
end
|
||||
|
||||
def include_id?
|
||||
object.respond_to?(:id)
|
||||
end
|
||||
|
||||
def name
|
||||
object.name
|
||||
end
|
||||
|
||||
def include_name?
|
||||
object.respond_to?(:name)
|
||||
end
|
||||
|
||||
def background
|
||||
object.background
|
||||
end
|
||||
|
||||
def include_background?
|
||||
object.respond_to?(:background)
|
||||
end
|
||||
|
||||
def completed
|
||||
object.completed?
|
||||
end
|
||||
|
||||
def include_completed?
|
||||
object.completed? &&
|
||||
(!object.respond_to?(:multiple_submissions) || !object.multiple_submissions) &&
|
||||
!scope.is_admin?
|
||||
end
|
||||
|
||||
def min_trust
|
||||
object.min_trust
|
||||
end
|
||||
|
||||
def include_min_trust?
|
||||
object.respond_to?(:min_trust)
|
||||
end
|
||||
|
||||
def permitted
|
||||
object.permitted?
|
||||
end
|
||||
|
||||
def include_permitted?
|
||||
object.respond_to?(:permitted?)
|
||||
end
|
||||
|
||||
def include_start?
|
||||
object.start && include_steps?
|
||||
end
|
||||
|
||||
def include_steps?
|
||||
!include_completed?
|
||||
end
|
||||
|
||||
def required
|
||||
object.required
|
||||
end
|
||||
|
||||
def include_required?
|
||||
object.respond_to?(:required)
|
||||
end
|
||||
|
||||
def user
|
||||
object.user
|
||||
end
|
||||
|
||||
def categories
|
||||
begin
|
||||
site = ::Site.new(scope)
|
||||
::ActiveModel::ArraySerializer.new(site.categories, each_serializer: BasicCategorySerializer)
|
||||
rescue => e
|
||||
puts "HERE IS THE ERROR: #{e.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def uncategorized_category_id
|
||||
SiteSetting.uncategorized_category_id
|
||||
end
|
||||
end
|
||||
|
||||
class WizardSerializer
|
||||
prepend CustomWizardWizardSerializerExtension if SiteSetting.custom_wizard_enabled
|
||||
end
|
29
serializers/wizard_step_serializer.rb
Normale Datei
29
serializers/wizard_step_serializer.rb
Normale Datei
|
@ -0,0 +1,29 @@
|
|||
module CustomWizardWizardStepSerializerExtension
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
attributes :permitted, :permitted_message
|
||||
end
|
||||
|
||||
def title
|
||||
return PrettyText.cook(object.title) if object.title
|
||||
PrettyText.cook(I18n.t("#{object.key || i18n_key}.title", default: ''))
|
||||
end
|
||||
|
||||
def description
|
||||
return object.description if object.description
|
||||
PrettyText.cook(I18n.t("#{object.key || i18n_key}.description", default: '', base_url: Discourse.base_url))
|
||||
end
|
||||
|
||||
def permitted
|
||||
object.permitted
|
||||
end
|
||||
|
||||
def permitted_message
|
||||
object.permitted_message
|
||||
end
|
||||
end
|
||||
|
||||
class WizardStepSerializer
|
||||
prepend CustomWizardWizardStepSerializerExtension if SiteSetting.custom_wizard_enabled
|
||||
end
|
21
spec/components/custom_wizard/api_spec.rb
Normale Datei
21
spec/components/custom_wizard/api_spec.rb
Normale Datei
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe CustomWizard::Api do
|
||||
context 'authorization' do
|
||||
it 'authorizes with an oauth2 api' do
|
||||
|
||||
end
|
||||
|
||||
it 'refreshes the api access token' do
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
context 'endpoint' do
|
||||
it 'requests an api endpoint' do
|
||||
|
||||
end
|
||||
end
|
||||
end
|
370
spec/components/custom_wizard/builder_spec.rb
Normale Datei
370
spec/components/custom_wizard/builder_spec.rb
Normale Datei
|
@ -0,0 +1,370 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe CustomWizard::Builder do
|
||||
fab!(:user) { Fabricate(:user, username: 'angus') }
|
||||
fab!(:trusted_user) { Fabricate(:user, trust_level: 3) }
|
||||
fab!(:group) { Fabricate(:group) }
|
||||
|
||||
let!(:template) do
|
||||
JSON.parse(File.open(
|
||||
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
|
||||
).read)
|
||||
end
|
||||
|
||||
let(:permitted_params) {[{"key":"param_key","value":"submission_param_key"}]}
|
||||
let(:required_data) {[{"key":"nickname","connector":"equals","value":"name"}]}
|
||||
let(:required_data_message) {"Nickname is required to match your name"}
|
||||
let(:checkbox_field) {{"id":"checkbox","type":"checkbox","label":"Checkbox"}}
|
||||
let(:composer_field) {{"id": "composer","label":"Composer","type":"composer"}}
|
||||
let(:dropdown_categories_field) {{"id": "dropdown_categories","type": "dropdown","label": "Dropdown Categories","choices_type": "preset","choices_preset": "categories"}}
|
||||
let(:tag_field) {{"id": "tag","type": "tag","label": "Tag","limit": "2"}}
|
||||
let(:category_field) {{"id": "category","type": "category","limit": "1","label": "Category"}}
|
||||
let(:image_field) {{"id": "image","type": "image","label": "Image"}}
|
||||
let(:text_field) {{"id": "text","type": "text","label": "Text"}}
|
||||
let(:textarea_field) {{"id": "textarea","type": "textarea","label": "Textarea"}}
|
||||
let(:text_only_field) {{"id": "text_only","type": "text-only","label": "Text only"}}
|
||||
let(:upload_field) {{"id": "upload","type": "upload","file_types": ".jpg,.png,.pdf","label": "Upload"}}
|
||||
let(:user_selector_field) {{"id": "user_selector","type": "user-selector","label": "User selector"}}
|
||||
let(:dropdown_groups_field) {{"id": "dropdown_groups","type": "dropdown","choices_type": "preset","choices_preset": "groups","label": "Dropdown Groups"}}
|
||||
let(:dropdown_tags_field) {{"id": "dropdown_tags","type": "dropdown","choices_type": "preset","choices_preset": "tags","label": "Dropdown Tags"}}
|
||||
let(:dropdown_custom_field) {{"id": "dropdown_custom","type": "dropdown","choices_type": "custom","choices": [{"key": "option_1","value": "Option 1"},{"key": "option_2","value": "Option 2"}]}}
|
||||
let(:dropdown_translation_field) {{"id": "dropdown_translation","type": "dropdown","choices_type": "translation","choices_key": "key1.key2"}}
|
||||
let(:dropdown_categories_filtered_field) {{"id": "dropdown_categories_filtered_field","type": "dropdown","choices_type": "preset","choices_preset": "categories","choices_filters": [{"key": "slug","value": "staff"}]}}
|
||||
let(:create_topic_action) {{"id":"create_topic","type":"create_topic","title":"text","post":"textarea"}}
|
||||
let(:send_message_action) {{"id":"send_message","type":"send_message","title":"text","post":"textarea","username":"angus"}}
|
||||
let(:route_to_action) {{"id":"route_to","type":"route_to","url":"https://google.com"}}
|
||||
let(:open_composer_action) {{"id":"open_composer","type":"open_composer","title":"text","post":"textarea"}}
|
||||
let(:add_to_group_action) {{"id":"add_to_group","type":"add_to_group","group_id":"dropdown_groups"}}
|
||||
|
||||
def build_wizard(t = template, u = user, build_opts = {}, params = {})
|
||||
CustomWizard::Wizard.add_wizard(t)
|
||||
CustomWizard::Builder.new(u, 'welcome').build(build_opts, params)
|
||||
end
|
||||
|
||||
def add_submission_data(data = {})
|
||||
PluginStore.set("welcome_submissions", user.id, {
|
||||
name: 'Angus',
|
||||
website: 'https://thepavilion.io'
|
||||
}.merge(data))
|
||||
end
|
||||
|
||||
def get_submission_data
|
||||
PluginStore.get("welcome_submissions", user.id)
|
||||
end
|
||||
|
||||
def run_update(t = template, step_id = nil, data = {})
|
||||
wizard = build_wizard(t)
|
||||
updater = wizard.create_updater(step_id || t['steps'][0]['id'], data)
|
||||
updater.update
|
||||
updater
|
||||
end
|
||||
|
||||
def send_message(extra_field = nil, extra_action_opts = {})
|
||||
fields = [text_field, textarea_field]
|
||||
|
||||
if extra_field
|
||||
fields.push(extra_field)
|
||||
end
|
||||
|
||||
template['steps'][0]['fields'] = fields
|
||||
template['steps'][0]["actions"] = [send_message_action.merge(extra_action_opts)]
|
||||
|
||||
run_update(template, nil,
|
||||
text: "Message Title",
|
||||
textarea: "message body"
|
||||
)
|
||||
end
|
||||
|
||||
context 'disabled' do
|
||||
before do
|
||||
SiteSetting.custom_wizard_enabled = false
|
||||
end
|
||||
|
||||
it "returns no steps" do
|
||||
wizard = build_wizard
|
||||
expect(wizard.steps.length).to eq(0)
|
||||
expect(wizard.name).to eq('Welcome')
|
||||
end
|
||||
|
||||
it "doesn't save submissions" do
|
||||
run_update(template, nil, name: 'Angus')
|
||||
expect(get_submission_data.blank?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'enabled' do
|
||||
before do
|
||||
SiteSetting.custom_wizard_enabled = true
|
||||
end
|
||||
|
||||
it "returns steps" do
|
||||
expect(build_wizard.steps.length).to eq(2)
|
||||
end
|
||||
|
||||
it 'returns no steps if the multiple submissions are disabled and user has completed it' do
|
||||
history_params = {
|
||||
action: UserHistory.actions[:custom_wizard_step],
|
||||
acting_user_id: user.id,
|
||||
context: template['id']
|
||||
}
|
||||
UserHistory.create!(history_params.merge(subject: template['steps'][0]['id']))
|
||||
UserHistory.create!(history_params.merge(subject: template['steps'][1]['id']))
|
||||
|
||||
template["multiple_submissions"] = false
|
||||
expect(build_wizard(template).steps.length).to eq(0)
|
||||
end
|
||||
|
||||
it 'returns no steps if has min trust and user does not meet it' do
|
||||
template["min_trust"] = 3
|
||||
expect(build_wizard(template).steps.length).to eq(0)
|
||||
end
|
||||
|
||||
it 'returns steps if it has min trust and user meets it' do
|
||||
template["min_trust"] = 3
|
||||
expect(build_wizard(template, trusted_user).steps.length).to eq(2)
|
||||
end
|
||||
|
||||
it 'returns a wizard with prefilled data if user has partially completed it' do
|
||||
add_submission_data
|
||||
wizard = build_wizard
|
||||
expect(wizard.steps[0].fields.first.value).to eq('Angus')
|
||||
expect(wizard.steps[1].fields.first.value).to eq('https://thepavilion.io')
|
||||
end
|
||||
|
||||
it 'returns a wizard with no prefilled data if options include reset' do
|
||||
add_submission_data
|
||||
wizard = build_wizard(template, user, reset: true)
|
||||
expect(wizard.steps[0].fields.first.value).to eq(nil)
|
||||
expect(wizard.steps[1].fields.first.value).to eq(nil)
|
||||
end
|
||||
|
||||
context 'building steps' do
|
||||
it 'returns step metadata' do
|
||||
expect(build_wizard.steps[0].title).to eq('Welcome to Pavilion')
|
||||
expect(build_wizard.steps[1].title).to eq('Tell us about you')
|
||||
end
|
||||
|
||||
it 'saves permitted params' do
|
||||
template['steps'][0]['permitted_params'] = permitted_params
|
||||
wizard = build_wizard(template, user, {}, param_key: 'param_value')
|
||||
submissions = get_submission_data
|
||||
expect(submissions.first['submission_param_key']).to eq('param_value')
|
||||
end
|
||||
|
||||
it 'is not permitted if required data is not present' do
|
||||
template['steps'][0]['required_data'] = required_data
|
||||
expect(build_wizard(template, user).steps[0].permitted).to eq(false)
|
||||
end
|
||||
|
||||
it "is not permitted if required data is not present" do
|
||||
template['steps'][0]['required_data'] = required_data
|
||||
add_submission_data(nickname: "John")
|
||||
expect(build_wizard(template, user).steps[0].permitted).to eq(false)
|
||||
end
|
||||
|
||||
it 'it shows required data message if required data has message' do
|
||||
template['steps'][0]['required_data'] = required_data
|
||||
template['steps'][0]['required_data_message'] = required_data_message
|
||||
add_submission_data(nickname: "John")
|
||||
wizard = build_wizard(template, user)
|
||||
expect(wizard.steps[0].permitted).to eq(false)
|
||||
expect(wizard.steps[0].permitted_message).to eq(required_data_message)
|
||||
end
|
||||
|
||||
it 'is permitted if required data is present' do
|
||||
template['steps'][0]['required_data'] = required_data
|
||||
PluginStore.set('welcome_submissions', user.id, nickname: "Angus", name: "Angus")
|
||||
expect(build_wizard(template, user).steps[0].permitted).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns field metadata' do
|
||||
expect(build_wizard(template, user).steps[0].fields[0].label).to eq("<p>Name</p>")
|
||||
expect(build_wizard(template, user).steps[0].fields[0].type).to eq("text")
|
||||
end
|
||||
|
||||
it 'returns fields' do
|
||||
template['steps'][0]['fields'][1] = checkbox_field
|
||||
expect(build_wizard(template, user).steps[0].fields.length).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'on update' do
|
||||
it 'saves submissions' do
|
||||
run_update(template, nil, name: 'Angus')
|
||||
expect(get_submission_data.first['name']).to eq('Angus')
|
||||
end
|
||||
|
||||
context 'validation' do
|
||||
it 'applies min length' do
|
||||
template['steps'][0]['fields'][0]['min_length'] = 10
|
||||
updater = run_update(template, nil, name: 'short')
|
||||
expect(updater.errors.messages[:name].first).to eq(
|
||||
I18n.t('wizard.field.too_short', label: 'Name', min: 10)
|
||||
)
|
||||
end
|
||||
|
||||
it 'standardises boolean entries' do
|
||||
template['steps'][0]['fields'][0] = checkbox_field
|
||||
run_update(template, nil, checkbox: 'false')
|
||||
expect(get_submission_data.first['checkbox']).to eq(false)
|
||||
end
|
||||
|
||||
it 'requires required fields' do
|
||||
template['steps'][0]['fields'][0]['required'] = true
|
||||
expect(run_update(template).errors.messages[:name].first).to eq(
|
||||
I18n.t('wizard.field.required', label: 'Name')
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'actions' do
|
||||
it 'runs actions attached to a step' do
|
||||
run_update(template, template['steps'][1]['id'], name: "Gus")
|
||||
expect(user.name).to eq('Gus')
|
||||
end
|
||||
|
||||
it 'interpolates user data correctly' do
|
||||
user.name = "Angus"
|
||||
user.save!
|
||||
|
||||
expect(
|
||||
CustomWizard::Builder.fill_placeholders(
|
||||
"My name is u{name}",
|
||||
user,
|
||||
{}
|
||||
)
|
||||
).to eq('My name is Angus')
|
||||
end
|
||||
|
||||
it 'creates a topic' do
|
||||
template['steps'][0]['fields'] = [text_field, textarea_field]
|
||||
template['steps'][0]["actions"] = [create_topic_action]
|
||||
updater = run_update(template, nil,
|
||||
text: "Topic Title",
|
||||
textarea: "topic body"
|
||||
)
|
||||
topic = Topic.where(title: "Topic Title")
|
||||
|
||||
expect(topic.exists?).to eq(true)
|
||||
expect(Post.where(
|
||||
topic_id: topic.pluck(:id),
|
||||
raw: "topic body"
|
||||
).exists?).to eq(true)
|
||||
end
|
||||
|
||||
it 'creates a topic with a custom title' do
|
||||
user.name = "Angus"
|
||||
user.save!
|
||||
|
||||
template['steps'][0]['fields'] = [text_field, textarea_field]
|
||||
|
||||
create_topic_action['custom_title_enabled'] = true
|
||||
create_topic_action['custom_title'] = "u{name}' Topic Title"
|
||||
template['steps'][0]["actions"] = [create_topic_action]
|
||||
|
||||
run_update(template, nil, textarea: "topic body")
|
||||
|
||||
topic = Topic.where(title: "Angus' Topic Title")
|
||||
|
||||
expect(topic.exists?).to eq(true)
|
||||
expect(Post.where(
|
||||
topic_id: topic.pluck(:id),
|
||||
raw: "topic body"
|
||||
).exists?).to eq(true)
|
||||
end
|
||||
|
||||
it 'creates a topic with a custom post' do
|
||||
user.name = "Angus"
|
||||
user.save!
|
||||
|
||||
template['steps'][0]['fields'] = [text_field, textarea_field]
|
||||
|
||||
create_topic_action['post_builder'] = true
|
||||
create_topic_action['post_template'] = "u{name}' w{textarea}"
|
||||
template['steps'][0]["actions"] = [create_topic_action]
|
||||
|
||||
run_update(template, nil,
|
||||
text: "Topic Title",
|
||||
textarea: "topic body"
|
||||
)
|
||||
|
||||
topic = Topic.where(title: "Topic Title")
|
||||
|
||||
expect(topic.exists?).to eq(true)
|
||||
expect(Post.where(
|
||||
topic_id: topic.pluck(:id),
|
||||
raw: "Angus' topic body"
|
||||
).exists?).to eq(true)
|
||||
end
|
||||
|
||||
it 'sends a message' do
|
||||
send_message
|
||||
|
||||
topic = Topic.where(
|
||||
archetype: Archetype.private_message,
|
||||
title: "Message Title"
|
||||
)
|
||||
|
||||
expect(topic.exists?).to eq(true)
|
||||
expect(
|
||||
topic.first.topic_allowed_users.first.user.username
|
||||
).to eq('angus')
|
||||
expect(Post.where(
|
||||
topic_id: topic.pluck(:id),
|
||||
raw: "message body"
|
||||
).exists?).to eq(true)
|
||||
end
|
||||
|
||||
it 'doesnt sent a message if the required data is not present' do
|
||||
send_message(user_selector_field, required: "user_selector")
|
||||
topic = Topic.where(
|
||||
archetype: Archetype.private_message,
|
||||
title: "Message Title"
|
||||
)
|
||||
expect(topic.exists?).to eq(false)
|
||||
end
|
||||
|
||||
it 'updates a profile' do
|
||||
run_update(template, template['steps'][1]['id'], name: "Sally")
|
||||
expect(user.name).to eq('Sally')
|
||||
end
|
||||
|
||||
it 'opens a composer' do
|
||||
template['steps'][0]['fields'] = [text_field, textarea_field]
|
||||
template['steps'][0]["actions"] = [open_composer_action]
|
||||
|
||||
updater = run_update(template, nil,
|
||||
text: "Topic Title",
|
||||
textarea: "topic body"
|
||||
)
|
||||
|
||||
expect(updater.result.blank?).to eq(true)
|
||||
|
||||
updater = run_update(template, template['steps'][1]['id'])
|
||||
|
||||
expect(updater.result[:redirect_on_complete]).to eq(
|
||||
"/new-topic?title=Topic%20Title&body=topic%20body"
|
||||
)
|
||||
end
|
||||
|
||||
it 'adds a user to a group' do
|
||||
template['steps'][0]['fields'] = [dropdown_groups_field]
|
||||
template['steps'][0]["actions"] = [add_to_group_action]
|
||||
|
||||
updater = run_update(template, nil, dropdown_groups: group.id)
|
||||
expect(group.users.first.username).to eq('angus')
|
||||
end
|
||||
|
||||
it 're-routes a user' do
|
||||
template['steps'][0]["actions"] = [route_to_action]
|
||||
updater = run_update(template, nil, {})
|
||||
expect(updater.result[:redirect_on_next]).to eq(
|
||||
"https://google.com"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
49
spec/fixtures/wizard.json
gevendort
Normale Datei
49
spec/fixtures/wizard.json
gevendort
Normale Datei
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"id": "welcome",
|
||||
"name": "Welcome",
|
||||
"background": "#006da3",
|
||||
"save_submissions": true,
|
||||
"multiple_submissions": true,
|
||||
"after_signup": true,
|
||||
"theme_id": 4,
|
||||
"steps": [
|
||||
{
|
||||
"id": "welcome",
|
||||
"title": "Welcome to Pavilion",
|
||||
"raw_description": "Hey there, thanks for signing up.\n\nWe're Pavilion, an international freelancer cooperative that specialises in online communities.\n\nThis site is our own community, where we work with our clients, users of our open source work and our broader community.\n\n",
|
||||
"description": "<p>Hey there, thanks for signing up.</p>\n<p>We’re Pavilion, an international freelancer cooperative that specialises in online communities.</p>\n<p>This site is our own community, where we work with our clients, users of our open source work and our broader community.</p>",
|
||||
"fields": [
|
||||
{
|
||||
"id": "name",
|
||||
"type": "text",
|
||||
"label": "Name"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "about_you",
|
||||
"title": "Tell us about you",
|
||||
"raw_description": "We'd like to know a little more about you. Add a your name and your website below. This will update your user profile.",
|
||||
"description": "<p>We’d like to know a little more about you. Add a your name and your website below. This will update your user profile.</p>",
|
||||
"fields": [
|
||||
{
|
||||
"id": "website",
|
||||
"label": "Website",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "update_profile",
|
||||
"type": "update_profile",
|
||||
"profile_updates": [
|
||||
{
|
||||
"key": "name",
|
||||
"value": "name"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
8
spec/plugin_helper.rb
Normale Datei
8
spec/plugin_helper.rb
Normale Datei
|
@ -0,0 +1,8 @@
|
|||
require 'simplecov'
|
||||
|
||||
SimpleCov.configure do
|
||||
add_filter do |src|
|
||||
src.filename !~ /discourse-custom-wizard/ ||
|
||||
src.filename =~ /spec/
|
||||
end
|
||||
end
|
5
spec/requests/custom_wizard/admin_controller_spec.rb
Normale Datei
5
spec/requests/custom_wizard/admin_controller_spec.rb
Normale Datei
|
@ -0,0 +1,5 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe CustomWizard::AdminController do
|
||||
|
||||
end
|
5
spec/requests/custom_wizard/application_controller_spec.rb
Normale Datei
5
spec/requests/custom_wizard/application_controller_spec.rb
Normale Datei
|
@ -0,0 +1,5 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe ApplicationController do
|
||||
|
||||
end
|
31
spec/requests/custom_wizard/wizard_controller_spec.rb
Normale Datei
31
spec/requests/custom_wizard/wizard_controller_spec.rb
Normale Datei
|
@ -0,0 +1,31 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe CustomWizard::WizardController do
|
||||
it 'returns a wizard if enabled' do
|
||||
|
||||
end
|
||||
|
||||
it 'returns a disabled message if disabled' do
|
||||
|
||||
end
|
||||
|
||||
it 'returns a missing message if no wizard exists' do
|
||||
|
||||
end
|
||||
|
||||
it 'returns a custom wizard theme' do
|
||||
|
||||
end
|
||||
|
||||
it 'updates the page title' do
|
||||
|
||||
end
|
||||
|
||||
it 'skips a wizard if user is allowed to skip' do
|
||||
|
||||
end
|
||||
|
||||
it 'returns a no skip message if user is not allowed to skip' do
|
||||
|
||||
end
|
||||
end
|
Laden …
In neuem Issue referenzieren