Custom Wizard Plugin

The Custom Wizard Plugin allows you to make custom wizards for your forum. Custom wizards can provide information, take user input and perform actions based on that input. You can think of it as Google Forms or Gravity Forms for Discourse.

You will automatically see an example of it in action when you sign up for a new account on my sandbox. The feature request and bug report wizards linked below are also made with this plugin.

The plugin builds on top of the Setup wizard in Discourse, abstracting various aspects of it into a configurable component or setting. The Discourse Setup wizard is its own Ember app. This plugin extends that app in various ways to make it customizable by admins via a new Wizards route in the Discourse admin panel.

GitHub-Mark-32px Get the code

:raising_hand_woman: Request a feature

:bug: Report a bug

:heart: Donate to development

:man_technologist: Maintainers: @angus


You need an account on my sandbox to use the Feature Request and Bug Report wizards.

Not sure how to install a plugin? Follow the steps here.


Custom Wizards can:

  • Display text or images in a structured format. Text can be taken from translation files or entered via the admin panel.

  • Take user input via text boxes, textareas, dropdowns, or radio buttons.

  • Perform actions based on user input. The actions currently available are:

    • Update user profile fields.

    • Create a new topic.

    • Create a new message.

Custom Wizards are each given a distinct url in the /w/ namespace. You can automatically redirect users to wizards:

  • After they create an account, or accept an invite (e.g. an introduction to your community). If they have accepted an invite to a topic they will still be automatically redirected to that topic after they complete the wizard.

  • After a certain time (e.g. when you update terms and conditions). When the specified time is reached all users (including those currently logged in / active) will be redirected to the wizard.

Custom Wizards can be:

  • Single or multiple submission.

  • Required or optional.

There is also an option to turn off the functionality that saves user input to the db (but still allows it to be used in Actions), which may apply in special cases.

The configuration should be self explanatory, so I won’t attempt to describe it in detail here, but to give you a sense of what’s involved, this is what the configuration for Step 2 of the Welcome wizard that you see after signing up an account on my sandbox looks like.



The Wizard Step itself looks like this.

You can also add your own step handler logic from a separate plugin if you have custom logic you need to perform on user input when the user clicks “Next” or “Submit”.

From your plugin.rb add.

CustomWizard::Builder.add_step_handler('wizard_id') do |builder|
  # your logic.
end

You can see what is available via the builder object here.

You can also inject additional assets into custom wizards from a separate plugin. For example the Locations Plugin has these lines in its plugin.rb

if defined?(CustomWizard) == 'constant' && CustomWizard.class == Module
  CustomWizard::Field.add_assets('location', 'discourse-locations', ['components', 'helpers', 'lib', 'stylesheets'])
end

And has a wizard-specific component in /assets/javascripts/wizard that wraps the normal location-form component so it can be used in a wizard.

The end result being the location form in Step 3 of the Welcome Wizard in my sandbox.

Any custom component can be added to a custom wizard in this fashion.


A note of caution. This is a complex plugin. I have tested it a fair bit, but there is a lot of functionality here and it will take a little while to iron it out. I suggest you use it for simple use cases at this stage.

88 Likes

Custom Wizard & Locations Plugin

Hi there,

I was recently requested to find a way to ease the template-based process to add a point to the map, i.e., creating a topic with a location and some structure. At first I studied the possibility to use a direct link to topic creation in the form: https://discourse.example.com/new-topic?category=shared+resources&tags=new-resource%2Cneed-review. This works great, but does not allow to pass, e.g, an URL as title so that it’s automatically linked to the topic.

So, I thought about making a wizard to do this, asking for a name and an url.

“Pasted Link” as topic title

Part 1: crafting an URL

I would have been happy with a simple wizard that shows a form for a name (required) and an URL (optional), so that I could craft the new-topic URL using the following algorithm:

https://discourse.example.com/new-topic?category=shared+resources&title=%s&tags=... % (url.blank? ? name : url)

Then the internal Discourse mechanism would create the topic accordingly. But this is not available since the Wizard does not support URL actions yet. (See first screenshot below.)

Part 2: casting a spell to create the topic

First I used the w{url} to set the topic title, but when the field is empty (e.g., because the resource pointed at does not have a web site), the topic does not get a title at all. Making the url field mandatory would block some legitimate additions. A way to use a required form value as a fallback (in my case w{name}) in case the first choice is empty would be great.

Required field as topic title

I chose to postpone the linked topic until it becomes possible to edit the topic and associate a link, and went for using a required field to ensure the new topic has a title.

Then of course I needed to populate the topic’s first post, which is nicely supported by the post creator.
I was happy to see that I could use a location field in my wizard. But I met a couple of issues:

  1. the displayed location form is a bit confusing, and does not match the one from the Locations plugin that is available from editing a topic’s location via the button. It would be great to use that one for consistency.
  2. said location can be output in the post itself via w{location} but it shows the JSON object, not a link to the map, and worse: it does not actually set location to the topic.

This might be related to Locations Plugin

  • Is there a way to replace the multiple location fields with a simple free form address + country selector (and set this to a default value)?
  • Is there a way to redesign the location input to use a combination of address search and map interaction?
  • Integrating Wizard and Locations better would help. I remember that I checked a box regarding the neighborhood that might affect how the form is displayed. I also remember that depending on the reverse geo-location provider the operations might change. In any case, I’d be available to help around.

Current Wizard

I end up with a 3-step wizard:

Topic Title

Topic Location

  • Note that the wizard starts with an error message: actually, this is more like guidance, and should probably not appear as red. It also makes it quite complicated to go through.

Topic Description

  • Here, the composer field does not match the regular composer, which is a bit confusing. If there’s a way to use the native Discourse composer that would be terrific.
  • The composer is too small, especially with regard to the blank space used. Making it resizable could help.

Way Forward

I’m not sure this is entirely related to the Wizard Plugin, as Locations are also involved. Feel free to move or split the discussion accordingly.

My take without looking at the code is that adding an action to generate a link might be easier than to tackle other options mentioned above and would totally satisfy my immediate need.

Thank you @angus for your awesome work! Using the wizard plugin has been a very nice experience so far.

Edit: display bug on mobile

I forgot to mention that the second step of the wizard is blocked on mobile because the “Back” and “Next” buttons appear below another part of the form. They should get a bump in the z-index on CSS.

1 Like

Thanks for the detailed rundown :+1:

Could you explain the problem a bit more? What do you mean by “ease the template-based process to add a point to the map”?

I’m not entirely sure if I understand what you’re trying to achieve, but I think you could tackle this problem by mapping the “url” field directly to featured_link which is the topic property that adds a featured link to a topic. e.g.

It matches one version of that form. I will add the ability to customise the Locations Form in the Custom Wizard Location field this coming week.

Yeah, displaying locations in the body of posts is not supported, either via the Custom Wizard or just in the Locations plugin itself. I don’t plan to add support for that anytime soon.

In add fields you need to map topic.custom_fields.location to the location field. I just tested this and it’s working. There was a small issue (which I just pushed a fix for) that arose if you entered something in the ‘Custom Field’ box and then removed it, but it wouldn’t have affected standard custom_field mapping.

Somewhat confusingly the placeholder for the entry of a custom value was “Custom Field”. I’ve changed that to “Custom Value”: add custom value placeholder · angusmcleod/discourse-custom-wizard@1a0a4fd · GitHub

Could you send a screenshot of this? Thanks.

I was using a topic template including comments (configured in the category), but people would get confused about what to do with all this, leaving most of the comment inline and requiring moderator edition to clean up. The template’s intention was to help people entering the correct location for the topic, but it didn’t work. Hence the idea to use a wizard instead.

Aha! I didn’t know that one. Indeed it should work!

Great! Exactly what I need.

I must say I have no clue how to take a screenshot from an Android.

@angus, as always, your help is most precious. Kudos!

I’m going to test and report.

1 Like

@angus, it works like a charm! Here’s an example of wizard-added resource on the map:

Now, I tried to add tags but failed:

2018-10-08-001738

Is there a documentation somewhere about “topic fields”? Maybe the tags need to exist in the first place… The error was confusing: the wizard window displays a red rectangle with <!DOCTYPE html> inside.

Note: in the screenshot, the custom topic fields work perfectly to add a featured link and location on the map.

1 Like

Could you try it with pre-existant tags? That would help narrow it down.

I tried tags, topic.tags, topic.tags.name, all with a single existing tag: no error, no tag.

A topic has_many :topic_tags and has_many :tags, through: :topic_tags, so I guess it would require calling create on the association with the tags ids.

Hi @angus ,

Similar to Custom Wizard Plugin @SystemZ’s question.

I have users that have accepted the Customised User Field “Privacy Policy” and some that have not (form another platform import).
With your Wizard plugin, could I show the wizard to those who have not accepted the field yet? so if a user has accepted it already s/he wont see it but those who haven’t will be prompted to accept it?

Thanks.

A.

If you need only popup for GDPR or similar stuff, I think that this plugin is a little overkill.

I can recommend my friend’s plugin written specially for just this one purpose:

I use it in production for some time.

1 Like

thanks for this,

Yes, I only need it for GDPR purposes.

Would this one check if user has accepted Customised user field and then ask to accept if not ticked? if it has been accepted already then nothing happens…

I have to ask IT to install it and want to make sure that any plugin is the closest thing to what we need before asking them…

A.

Hey @testingsoftware did you take a look at the simple GDPR helper plugin? Does it do what you’re looking for? Or are you still interested in using this plugin?

Hi @Angus,

I think I’ll use this plugin as it gives more options to add some nice features in the future (ie. asking for feedback and record it in a category for Staff only and more…)

Something I cannot figure out is how to add a date to a field (I guess to a Custom Field in Actions).
Say I want to send newsletters for a year, I would create a Profile Field “send me newsletter for 12 months” and record the acceptance date so I can stop sending it 12 months from that date. How can I record the date when this option has been accepted?

A.

OK so after pressing at the same time volume down and the power button on Android to obtain a screenshot…

Here you can see that the location selection box overflows the wizard box. Noted that this is on mobile but I saw an occurrence on desktop as well with lots of results. (Try “Les Halles”, “Paris”)

I guess it’s fine for the mobile version of the wizard to scroll, and adding an overflow-y somewhere should fix the problem on desktop.

This is unrelated to my previous post: a user with Internet Explorer (does that still exist?) reports that the wizard is not showing up:

The user tried to access the wizard while being logged out, then retried after he logged in, to obtain the above result: maybe some cache? I tried to reproduce with Firefox to no avail.

Meanwhile, I found that returning to the wizard after closing it automatically comes back to the last visited step. Is there a way to force coming back to the first step? Even if there’s already data there, it might be useful to review it and click ‘next’.

I just realized that if the wizard is set to trigger for all new sign-ups, it will probably trigger regardless of whether people are signing up via an invite link or via “ordinary” sign up, right?

At least in my use case, that is likely to lead to some confusion so I’d like to be able to distinguish between wizards for both types of sign-up… :grimacing:

1 Like

Well, every record in the db has a created_at date. So you use that. But there isn’t an easy way to surface this in the Admin UI atm as far as I know.

@gingerman @jaron Looking at this again, it should be possible?

The ability to return back to the correct wizard context after registering as a new user is critical in the above usecases for better user experience and conversions.

Enable the “After Signup” setting and all new users will get kicked into the wizard.

Try setting this up and let me know what specifically you’re missing.

This is actually connected to an issue with Discourse core CSS. I’ve submitted a PR:

Could you check if there are any relevant console logs for the IE user?

(IE and MySpace?? Is this guy a time traveller from 2005?)

hm yeah. I’ll take a look at this on the weekend.

t is the way that postgres represents boolean values. Core Discourse stores “Checkbox” values (which you’re using here) as strings, which is why it shows up as a “true” / “false” string in the Admin UI.

I could store those values as strings as well, but arguably “Checkbox” user fields should be typecast to boolean in core Discourse and then the display in Admin should convert true / false into “True” / “False”. @david What do you think?

Correct.

Could you explain the use case a bit more?

1 Like

Hi @angus, great plugin - as usual!

I think I’ve come across a bug - or at least an inconsistency, and I have a feature request.

tl;dr Bug? - using the CustomWizard::Builder.add_step_handler() feature with a wizard that has a dropdown doesn’t appear to send a value through for the first (default) option in the dropdown. Once the default value is changed, other dropdown values work. Feature request - Can we have support for Datetime fields?

Context

Not essential to reproduce the bug - just sharing the background as I think it’s an interesting use of Discourse.

Background

I work in a school with kids that have social, emotional and mental health challenges. As a staff, we need a way to stay in touch and to discuss important issues relating to the children. I am obviously pushing Discourse for this purpose and so far it has been well received.

In carrying out our work we have a number of paper-based systems that record details of the children’s behaviour and I am investigating if I can use Discourse and the polls feature to replace the very inefficient paper systems. I have a meeting with my boss on Tuesday to do a demo and try to win her over.

One of these paper systems is what we call the Points Card where the child can earn a maximum of 21 points during the day across 7 sessions against 3 categories of behaviour - work, language and safety 3 x 7 = 21.

These 7 sessions are chronologically contiguous, start at 8:15 in the morning and run to 3 pm. After each session, the adult that has worked with the child decides how many (if any) points (Work, Language, Safety) the child has earned.

We manually record these points on paper (on a Points Card, duh!) and then we write a small narrative in a book for context - who was the teacher?, what was the subject?, was there a consequence for poor behaviour?, etc.

I have modelled this in Discourse as follows.

We have a parent category called GG Points.

Inside this category, there are 11 subcategories - one for each SEMH child we work with. The categories are named after the child.

I have enabled events from your Event Plugin in these categories so I can get an Agenda or Calendar for each child. Each topic within these categories is also marked to automatically close after 24 hours.

Inside the child category, I then create 7 topics - one for each time period during the day that the child can earn points for.

Within each of these topics I create 4 Discourse polls to record the points for that session:

And finally, at the end of the thread, staff members are then able to add any additional narrative to help us understand the behaviour by replying and adding posts to the topic.

I’ve written a small routine in (stand-alone) Ruby using the API gem that creates the categories and posts. The problem I then faced was how to kick-off the routine for a given date or date range.

I initially thought I would run a batch script that would create the points card for the 11 children for that day. This seemed inefficient so I wanted to be able to specify each individual child and a date range so looked at trying to develop a plugin but Ember and the front-end UI are a little beyond my current skills and available time.

So, as these points card topics need to be created every day, I had decided that I would create a recurring sidekiq job, convert my stand-alone code to a headless (no UI) plugin that would be run once a day and sit back and relax.

But then I saw that your Custom Wizard Plugin allows us to run plugin code after each step or when the wizard is submitted. I decided that your Wizard can be my UI. I will also need a UI solution when I start reporting on this data and it feels like this could be the perfect solution.

Anyway, back to that bug and feature request.

Bug?

There could be two bugs or it may be one that represents itself in two places (or no bugs and I could be misunderstanding something!).

Bug? 1

So I’ve created a wizard with one step step_1 and with a single field children_to_create which is a dropdown with custom choices.

I have a plugin shell (generated by the rails g plugin SchoolPoints) called SchoolPoints with the following code in plugin.rb within the class SchoolPoints::ActionsController < ::ApplicationController section.

  class SchoolPoints::ActionsController < ::ApplicationController
    requires_plugin PLUGIN_NAME

    before_action :ensure_logged_in

    def list
      render json: success_json
    end

    CustomWizard::Builder.add_step_handler('select_children') do |builder|
      Rails.logger.info("************************************")
      Rails.logger.info("ARRIVED IN THE PLUGIN")
      Rails.logger.info("************************************")
      Rails.logger.info(builder.updater.fields.to_h)
      Rails.logger.info("************************************")
      # your logic.
    end
  end

If I go to the URL for the wizard and, without doing anything and with the “All Children” default option of the pulldown highlighted, I press “Done”, the wizard hits my plugin code and leaves this in the logs.

I, [2018-10-25T20:21:13.588540 #25377]  INFO -- : ************************************
I, [2018-10-25T20:21:13.590755 #25377]  INFO -- : ARRIVED IN THE PLUGIN
I, [2018-10-25T20:21:13.593568 #25377]  INFO -- : ************************************
I, [2018-10-25T20:22:34.089509 #25377]  INFO -- : {"children_to_create"=>""}

i.e. there is no value for the children_to_create key in builder.updater.fields.

I was expecting to see the value of “All Children

However, if I change the value of the dropdown to any of the other options I get what I would expect…

I, [2018-10-25T20:32:36.908950 #27315]  INFO -- : ************************************
I, [2018-10-25T20:32:36.911916 #27315]  INFO -- : ARRIVED IN THE PLUGIN
I, [2018-10-25T20:32:36.915255 #27315]  INFO -- : ************************************
I, [2018-10-25T20:32:49.784835 #27315]  INFO -- : {"children_to_create"=>"Child 2"}

Finally, if I open the wizard URL, change the dropdown option from the default “All Children” and then change it back to “All Children”, hit “Done” I get…

I, [2018-10-25T20:35:26.709581 #28196]  INFO -- : ************************************
I, [2018-10-25T20:35:26.712480 #28196]  INFO -- : ARRIVED IN THE PLUGIN
I, [2018-10-25T20:35:26.715338 #28196]  INFO -- : ************************************
I, [2018-10-25T20:35:35.495961 #28196]  INFO -- : {"children_to_create"=>"All Children"}

i.e. what I would expect to see.

Bug? 2

Finally, if I set the required flag to true for the field…

image

…visit the URL for the wizard and, without changing the dropdown from the default “All Children” and I hit “Done” then the wizard does nothing. i.e. the “Done” button UI shows that it is being pressed but the wizard does not advance.

Now I’m guessing that Bug? 2 is a function of Bug? 1, i.e. that because the field value is only set once the dropbox value changes, then the required flag means that the field validation fails?

Let me know if you need any further information to investigate.

Feature Request

Finally, as was discussed above, would it be possible to add a Date/Datetime-aware field to the wizard?

Ideally, in the UI I’d like to see a calendar / clock picker like your event plugin and being able to specify a date range (from-to) would be wonderful. Or, at least being able to call on validation such that Date2 >= Date1.

As ever, your work is invaluable to the Discourse community.

Thanks!

2 Likes

Another feature request - sorry! but I think this would be useful to anyone developing on a development system before deploying to the live system. It’s a nice-to-have as I have found a workaround.

Import and export of wizards.

What is needed?

An export button at the Wizard level that simply returns a JSON file to the browser containing the definitions of the wizard/steps/fields.

An import button would be used to create a wizard from a previously exported JSON file.

On import, the option needs to be there to create a new wizard from the imported definition or to update an existing wizard instead.

The justification

I am creating wizards on my dev machine and using the add_step_handler feature in a series of plugins that I am developing. Your wizard is my plugin UI.

The workflow to push my plugin code from my development box and then pull the code to the live system is halted whilst I recreate the wizard on my live system - exactly as it was.

My wizards currently don’t have multiple stages and have few fields, but they feature dropdowns with many choices. The values of these choices are passed to my code and have have been reproduced exactly as they form the hash key lookup in my code. A typo would stop the code from working for that option.

As my wizards get more complex or use multi-stages, I imagine this could be a real problem.

Approach and current workaround

I see from the code that to save the wizard, you’re actually serialising it to JSON and putting that in the PluginStore so it looks like the infrastructure is there already.

I guess that you might have to implement versioning of the exported file in case a big future changes expect new keys that are not present in older files?

Knowing that you’re storing JSON allowed me to come up with a workaround. I create the wizard on the live system, dump the PluginStore table using the Data Explorer, copy the JSON and then manually insert that into my dev system’s PluginStore.

Perhaps my plugin code should create the wizards it needs by inserting rows into the custom_wizard plugin store? It would give me control but it sounds like a recipe for breaking things TBH.

I’d offer a pull request but I have no experience with the ember and the plugin UI hooks.

1 Like

I just noticed that I’m not being notified about new users joining the forum (they are asked some wizard questions after sign-up and the answers are supposed to be PMed to me).

I figured out that this was due to min trust to send messages being set to 1 (the default). So the wizard couldn’t send the message on behalf of the new (TL0) user.

I’m not sure if there is anything to be improved here, so I mainly want to mention this to anyone using the wizard to generate PMs (perhaps mention this in the OP? Or perhaps even mention it in the wizard interface when the send_pm action is selected?)

1 Like

There seems to be a bug in the plugin. I have a required field in my new user wizard but people are signing up (i.e. completing the wizard after sign-up) without filling it in.

In case it matters, here are my wizard settings: