diff --git a/app/controllers/custom_wizard/admin/user.rb b/app/controllers/custom_wizard/admin/user.rb
new file mode 100644
index 0000000000..77452c6b80
--- /dev/null
+++ b/app/controllers/custom_wizard/admin/user.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+class CustomWizard::UserController < ::Admin::AdminController
+ before_action :ensure_admin
+ requires_plugin "discourse-custom-wizard"
+
+ def clear_redirect
+ user = User.find_by(id: params[:id])
+
+ if user
+ user.custom_fields["redirect_to_wizard"] = nil
+ user.save_custom_fields(true)
+ render json: success_json
+ else
+ render json: failed_json
+ end
+ end
+end
diff --git a/app/controllers/custom_wizard/admin/wizard.rb b/app/controllers/custom_wizard/admin/wizard.rb
index fdb1ca7020..9c297b3654 100644
--- a/app/controllers/custom_wizard/admin/wizard.rb
+++ b/app/controllers/custom_wizard/admin/wizard.rb
@@ -78,6 +78,7 @@ def save_wizard_params
:resume_on_revisit,
:theme_id,
permitted: mapped_params,
+ after_time_groups: [],
steps: [
:id,
:index,
diff --git a/app/jobs/regular/set_after_time_wizard.rb b/app/jobs/regular/set_after_time_wizard.rb
index 2938524bac..15077e0e08 100644
--- a/app/jobs/regular/set_after_time_wizard.rb
+++ b/app/jobs/regular/set_after_time_wizard.rb
@@ -3,20 +3,32 @@ module Jobs
class SetAfterTimeWizard < ::Jobs::Base
def execute(args)
if SiteSetting.custom_wizard_enabled
- wizard = CustomWizard::Wizard.create(args[:wizard_id])
+ @wizard = CustomWizard::Wizard.create(args[:wizard_id])
- if wizard && wizard.after_time
+ if @wizard && @wizard.after_time
user_ids = []
- User.human_users.each do |user|
- user_ids.push(user.id) if CustomWizard::Wizard.set_user_redirect(wizard.id, user)
+ target_users.each do |user|
+ user_ids.push(user.id) if CustomWizard::Wizard.set_after_time_redirect(@wizard.id, user)
end
CustomWizard::Template.clear_cache_keys
- MessageBus.publish "/redirect_to_wizard", wizard.id, user_ids: user_ids
+ MessageBus.publish "/redirect_to_wizard", @wizard.id, user_ids: user_ids
end
end
end
+
+ def target_users
+ users = []
+
+ if @wizard.after_time_groups.exists?
+ @wizard.after_time_groups.each { |group| users += group.users }
+ else
+ users = User.human_users
+ end
+
+ users
+ end
end
end
diff --git a/assets/javascripts/discourse/connectors/admin-user-details/admin-user-wizard-details.hbs b/assets/javascripts/discourse/connectors/admin-user-details/admin-user-wizard-details.hbs
new file mode 100644
index 0000000000..ddf15546ca
--- /dev/null
+++ b/assets/javascripts/discourse/connectors/admin-user-details/admin-user-wizard-details.hbs
@@ -0,0 +1,28 @@
+
+ {{i18n "admin.wizard.user.label"}}
+
+
+
{{i18n "admin.wizard.user.redirect.label"}}
+
+ {{#if model.redirect_to_wizard}}
+
+ {{model.redirect_to_wizard}}
+
+ {{else}}
+ —
+ {{/if}}
+
+
+ {{#if model.redirect_to_wizard}}
+
+ {{/if}}
+
+
+
\ No newline at end of file
diff --git a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6
index 7ae487097f..e84154b660 100644
--- a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6
+++ b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6
@@ -12,9 +12,11 @@ import Controller from "@ember/controller";
import copyText from "discourse/lib/copy-text";
import I18n from "I18n";
import { filterValues } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
+import { action } from "@ember/object";
export default Controller.extend({
modal: service(),
+ site: service(),
hasName: notEmpty("wizard.name"),
@observes("currentStep")
@@ -91,6 +93,24 @@ export default Controller.extend({
return I18n.t(`admin.wizard.error.${errorType}`, errorParams);
},
+ setAfterTimeGroupIds() {
+ const groups = this.site.groups.filter((g) =>
+ this.wizard.after_time_groups.includes(g.name)
+ );
+ this.setProperties({
+ afterTimeGroupIds: groups.map((g) => g.id),
+ });
+ },
+
+ @action
+ setAfterTimeGroups(groupIds) {
+ const groups = this.site.groups.filter((g) => groupIds.includes(g.id));
+ this.setProperties({
+ afterTimeGroupIds: groups.map((g) => g.id),
+ "wizard.after_time_groups": groups.map((g) => g.name),
+ });
+ },
+
actions: {
save() {
this.setProperties({
diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6
index 5debc380e2..52c400d4c1 100644
--- a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6
+++ b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6
@@ -2,6 +2,8 @@ import DiscourseURL from "discourse/lib/url";
import { withPluginApi } from "discourse/lib/plugin-api";
import getUrl from "discourse-common/lib/get-url";
import { observes } from "discourse-common/utils/decorators";
+import { popupAjaxError } from "discourse/lib/ajax-error";
+import { ajax } from "discourse/lib/ajax";
export default {
name: "custom-wizard-edits",
@@ -105,6 +107,24 @@ export default {
route: "adminWizardsWizard",
icon: "hat-wizard",
});
+
+ if (api.getCurrentUser()?.admin) {
+ api.modifyClass("model:admin-user", {
+ pluginId: "custom-wizard",
+
+ clearWizardRedirect(user) {
+ return ajax(`/admin/users/${user.id}/wizards/clear_redirect`, {
+ type: "PUT",
+ })
+ .then(() => {
+ user.setProperties({
+ redirect_to_wizard: null,
+ });
+ })
+ .catch(popupAjaxError);
+ },
+ });
+ }
});
},
};
diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6
index 3c286e182a..688bc24547 100644
--- a/assets/javascripts/discourse/lib/wizard-schema.js.es6
+++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6
@@ -17,11 +17,13 @@ const wizard = {
resume_on_revisit: null,
theme_id: null,
permitted: null,
+ after_time_groups: null,
},
mapped: ["permitted"],
required: ["id"],
dependent: {
after_time: "after_time_scheduled",
+ after_time: "after_time_groups",
},
objectArrays: {
step: {
diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6
index 2ed2627fae..9934b003c2 100644
--- a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6
+++ b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6
@@ -47,5 +47,6 @@ export default DiscourseRoute.extend({
};
controller.setProperties(props);
+ controller.setAfterTimeGroupIds();
},
});
diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs
index 053deb739b..af3f063437 100644
--- a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs
+++ b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs
@@ -162,6 +162,22 @@
}}
+
+
+
+
+
+
+
+
+ {{i18n "admin.wizard.after_time_groups.description"}}
+
+
+
{{/wizard-subscription-container}}
diff --git a/assets/stylesheets/common/admin.scss b/assets/stylesheets/common/admin.scss
index 5e831c4153..1c50edde31 100644
--- a/assets/stylesheets/common/admin.scss
+++ b/assets/stylesheets/common/admin.scss
@@ -373,11 +373,16 @@ $error: #ef1700;
}
.input .select-kit,
- > .select-kit {
+ > .select-kit:not(.group-chooser) {
max-width: 250px !important;
min-width: 250px !important;
}
+ .group-chooser {
+ max-width: 400px !important;
+ min-width: 400px !important;
+ }
+
&.force-final {
padding: 1em;
background-color: var(--primary-very-low);
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 15a78ed3c9..864e01dec1 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -97,6 +97,9 @@ en:
time: "Time"
done: "Set Time"
clear: "Clear"
+ after_time_groups:
+ label: Time Groups
+ description: Groups directed to wizard after start time.
required: "Required"
required_label: "Users cannot skip the wizard."
prompt_completion: "Prompt"
@@ -587,6 +590,12 @@ en:
community:
label: Support
title: There is a Custom Wizard Community subscription active on this forum.
+ user:
+ label: Wizards
+ redirect:
+ label: Redirect
+ remove_label: Remove
+ remove_title: Remove wizard redirect
wizard_js:
group:
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 9fa23d5372..7e31b00fda 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -52,6 +52,7 @@ en:
after_signup: "You can only have one 'after signup' wizard at a time. %{wizard_id} has 'after signup' enabled."
after_signup_after_time: "You can't use 'after time' and 'after signup' on the same wizard."
after_time: "After time setting is invalid."
+ after_time_group: "After time group does not exist: %{group_name}"
liquid_syntax_error: "Liquid syntax error in %{attribute}: %{message}"
subscription: "%{type} %{property} usage is not supported on your subscription"
not_permitted_for_guests: "%{object_id} is not permitted when guests can access the wizard"
diff --git a/config/routes.rb b/config/routes.rb
index db42f94ff8..50cc4f4e9f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -46,4 +46,6 @@
post "admin/wizards/manager/import" => "admin_manager#import"
delete "admin/wizards/manager/destroy" => "admin_manager#destroy"
end
+
+ put "/admin/users/:id/wizards/clear_redirect" => "custom_wizard/user#clear_redirect"
end
diff --git a/lib/custom_wizard/subscription.rb b/lib/custom_wizard/subscription.rb
index e7c1c4e7bf..14b958435e 100644
--- a/lib/custom_wizard/subscription.rb
+++ b/lib/custom_wizard/subscription.rb
@@ -25,6 +25,12 @@ def self.attributes
business: ["*"],
community: ["*"],
},
+ after_time_groups: {
+ none: [],
+ standard: [],
+ business: ["*"],
+ community: [],
+ },
},
step: {
condition: {
diff --git a/lib/custom_wizard/template.rb b/lib/custom_wizard/template.rb
index 217851d207..cc77d078fb 100644
--- a/lib/custom_wizard/template.rb
+++ b/lib/custom_wizard/template.rb
@@ -176,10 +176,10 @@ def schedule_save_jobs
end
if enqueue_wizard_at
- Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard_id)
+ self.class.clear_user_wizard_redirect(wizard_id, after_time: true)
Jobs.enqueue_at(enqueue_wizard_at, :set_after_time_wizard, wizard_id: wizard_id)
elsif old_data && old_data[:after_time]
- clear_user_wizard_redirect(wizard_id, after_time: true)
+ self.class.clear_user_wizard_redirect(wizard_id, after_time: true)
end
end
end
diff --git a/lib/custom_wizard/validators/template.rb b/lib/custom_wizard/validators/template.rb
index c3202b37d4..f3bc9dd792 100644
--- a/lib/custom_wizard/validators/template.rb
+++ b/lib/custom_wizard/validators/template.rb
@@ -128,6 +128,15 @@ def validate_after_time
if invalid_time || active_time.blank? || active_time < Time.now.utc
errors.add :base, I18n.t("wizard.validation.after_time")
end
+
+ group_names = @data[:after_time_groups]
+ if group_names.present?
+ group_names.each do |group_name|
+ unless Group.exists?(name: group_name)
+ errors.add :base, I18n.t("wizard.validation.after_time_group", group_name: group_name)
+ end
+ end
+ end
end
def validate_liquid_template(object, type)
diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb
index 20f65ae6e6..3c491b21b6 100644
--- a/lib/custom_wizard/wizard.rb
+++ b/lib/custom_wizard/wizard.rb
@@ -15,6 +15,7 @@ class CustomWizard::Wizard
:multiple_submissions,
:after_time,
:after_time_scheduled,
+ :after_time_group_names,
:after_signup,
:required,
:prompt_completion,
@@ -58,6 +59,7 @@ def initialize(attrs = {}, user = nil, guest_id = nil)
@after_signup = cast_bool(attrs["after_signup"])
@after_time = cast_bool(attrs["after_time"])
@after_time_scheduled = attrs["after_time_scheduled"]
+ @after_time_group_names = attrs["after_time_groups"]
@required = cast_bool(attrs["required"])
@permitted = attrs["permitted"] || nil
@theme_id = attrs["theme_id"]
@@ -251,6 +253,10 @@ def can_access?(always_allow_admin: true)
permitted?(always_allow_admin: always_allow_admin) && can_submit?
end
+ def should_redirect?
+ can_access?(always_allow_admin: false) && after_time_target?
+ end
+
def reset
return nil unless actor_id
@@ -329,6 +335,16 @@ def remove_user_redirect
end
end
+ def after_time_groups
+ @after_time_groups ||= Group.where(name: after_time_group_names)
+ end
+
+ def after_time_target?
+ return true if after_time_group_names.blank? || !after_time_groups.exists?
+ return true if after_time_groups.joins(:users).where(users: { username: user.username }).exists?
+ false
+ end
+
def self.create(wizard_id, user = nil, guest_id = nil)
if template = CustomWizard::Template.find(wizard_id)
new(template.to_h, user, guest_id)
@@ -372,6 +388,11 @@ def self.prompt_completion(user)
end
end
+ def self.set_after_time_redirect(wizard_id, user)
+ wizard = self.create(wizard_id, user)
+ set_user_redirect(wizard_id, user) if wizard.after_time_target?
+ end
+
def self.set_user_redirect(wizard_id, user)
wizard = self.create(wizard_id, user)
diff --git a/plugin.rb b/plugin.rb
index 434f193f0d..fd22179a24 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# name: discourse-custom-wizard
# about: Forms for Discourse. Better onboarding, structured posting, data enrichment, automated actions and much more.
-# version: 2.8.13
+# version: 2.9.0
# authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George, Kaitlin Maddever, Juan Marcos Gutierrez Ramos
# url: https://github.com/paviliondev/discourse-custom-wizard
# contact_emails: development@pavilion.tech
@@ -46,6 +46,7 @@
require_relative "app/controllers/custom_wizard/admin/logs.rb"
require_relative "app/controllers/custom_wizard/admin/manager.rb"
require_relative "app/controllers/custom_wizard/admin/custom_fields.rb"
+ require_relative "app/controllers/custom_wizard/admin/user.rb"
require_relative "app/controllers/custom_wizard/wizard_client.rb"
require_relative "app/controllers/custom_wizard/wizard.rb"
require_relative "app/controllers/custom_wizard/steps.rb"
@@ -146,6 +147,7 @@
end
add_to_serializer(:current_user, :redirect_to_wizard) { object.redirect_to_wizard }
+ add_to_serializer(:admin_user_list, :redirect_to_wizard) { object.redirect_to_wizard }
on(:user_approved) do |user|
if wizard = CustomWizard::Wizard.after_signup(user)
@@ -168,7 +170,7 @@
end
wizard = CustomWizard::Wizard.create(wizard_id, current_user)
- redirect_to "/w/#{wizard_id.dasherize}" if wizard.can_access?(always_allow_admin: false)
+ redirect_to "/w/#{wizard_id.dasherize}" if wizard.should_redirect?
end
end
end
diff --git a/spec/components/custom_wizard/template_spec.rb b/spec/components/custom_wizard/template_spec.rb
index 7f71d0edd0..a3fc72831e 100644
--- a/spec/components/custom_wizard/template_spec.rb
+++ b/spec/components/custom_wizard/template_spec.rb
@@ -154,6 +154,9 @@
expect_not_enqueued_with(job: :set_after_time_wizard) do
CustomWizard::Template.save(@after_time_template)
end
+ expect(
+ UserCustomField.exists?(name: "redirect_to_wizard", value: @after_time_template[:id]),
+ ).to eq(false)
end
end
end
diff --git a/spec/jobs/set_after_time_wizard_spec.rb b/spec/jobs/set_after_time_wizard_spec.rb
index b85dd70dd1..0141076680 100644
--- a/spec/jobs/set_after_time_wizard_spec.rb
+++ b/spec/jobs/set_after_time_wizard_spec.rb
@@ -47,6 +47,29 @@
end
end
+ context "when after_time_groups is set" do
+ fab!(:group1) { Fabricate(:group) }
+ fab!(:group_user) { Fabricate(:group_user, group: group1, user: user2) }
+
+ before do
+ enable_subscription("business")
+ @after_time_template["after_time_groups"] = [group1.name]
+ CustomWizard::Template.save(@after_time_template.as_json)
+ end
+
+ it "only redirects users in the group" do
+ messages =
+ MessageBus.track_publish("/redirect_to_wizard") do
+ described_class.new.execute(wizard_id: "super_mega_fun_wizard")
+ end
+ expect(messages.first.data).to eq("super_mega_fun_wizard")
+ expect(messages.first.user_ids).to match_array([user2.id])
+ expect(
+ UserCustomField.where(name: "redirect_to_wizard", value: "super_mega_fun_wizard").length,
+ ).to eq(1)
+ end
+ end
+
context "when user has completed the wizard" do
before do
@after_time_template[:steps].each do |step|
diff --git a/spec/requests/custom_wizard/application_controller_spec.rb b/spec/requests/custom_wizard/application_controller_spec.rb
index 5e9ea3f30a..3705605b0e 100644
--- a/spec/requests/custom_wizard/application_controller_spec.rb
+++ b/spec/requests/custom_wizard/application_controller_spec.rb
@@ -78,8 +78,16 @@
end
context "when time has passed" do
- it "redirects if time has passed" do
+ def run_job!
travel_to Time.now + 4.hours
+ MessageBus.expects(:publish).at_least_once
+ Jobs::SetAfterTimeWizard.new.execute(
+ Jobs::SetAfterTimeWizard.jobs.first["args"].first.symbolize_keys,
+ )
+ end
+
+ it "redirects if time has passed" do
+ run_job!
get "/"
expect(response).to redirect_to("/w/super-mega-fun-wizard")
end
@@ -93,7 +101,7 @@
context "when user is in permitted group" do
it "redirects user" do
- travel_to Time.now + 4.hours
+ run_job!
get "/"
expect(response).to redirect_to("/w/super-mega-fun-wizard")
end
@@ -103,7 +111,7 @@
before { Group.find(13).remove(user) }
it "does not redirect user" do
- travel_to Time.now + 4.hours
+ run_job!
user.trust_level = TrustLevel[2]
user.save!
get "/"
@@ -111,7 +119,7 @@
end
it "does not redirect if user is an admin" do
- travel_to Time.now + 4.hours
+ run_job!
user.trust_level = TrustLevel[2]
user.admin = true
user.save!
@@ -134,11 +142,49 @@
end
it "does not redirect" do
- travel_to Time.now + 4.hours
+ run_job!
get "/"
expect(response).not_to redirect_to("/w/super-mega-fun-wizard")
end
end
+
+ context "when after_time_groups is set" do
+ fab!(:group)
+
+ before do
+ enable_subscription("business")
+ @template["after_time_groups"] = [group.name]
+ CustomWizard::Template.save(@template.as_json)
+ end
+
+ context "when user is in group" do
+ before { group.add(user) }
+
+ it "redirects user" do
+ run_job!
+ get "/"
+ expect(response).to redirect_to("/w/super-mega-fun-wizard")
+ end
+ end
+
+ context "when user is not in group" do
+ before { group.remove(user) }
+
+ it "does not redirect user" do
+ run_job!
+ get "/"
+ expect(response).to_not redirect_to("/w/super-mega-fun-wizard")
+ end
+
+ it "does not redirect if user is an admin" do
+ run_job!
+ user.admin = true
+ user.save!
+ get "/"
+ expect(response).to_not redirect_to("/w/super-mega-fun-wizard")
+ end
+ end
+ end
end
end
end