Track steps the user has completed, nag them to finish it.

This commit is contained in:
Robin Ward 2016-09-14 16:36:08 -04:00
parent ef84981e38
commit 29cf47cfb2
21 changed files with 290 additions and 19 deletions

View File

@ -36,7 +36,10 @@ export default Ember.Component.extend({
@computed()
shouldSee() {
return Discourse.User.currentProp('admin') && this.siteSettings.show_create_topics_notice;
const user = this.currentUser;
return user && user.get('admin') &&
this.siteSettings.show_create_topics_notice &&
!this.site.get('wizard_required');
},
@computed('enabled', 'shouldSee', 'publicTopicCount', 'publicPostCount')

View File

@ -17,6 +17,10 @@ export default Ember.Component.extend(StringBuffer, {
notices.push([I18n.t("emails_are_disabled"), 'alert-emails-disabled']);
}
if (this.site.get('wizard_required')) {
notices.push([I18n.t('wizard_required'), 'alert-wizard']);
}
if (this.currentUser && this.currentUser.get('staff') && this.siteSettings.bootstrap_mode_enabled) {
if (this.siteSettings.bootstrap_mode_min_users > 0) {
notices.push([I18n.t("bootstrap_mode_enabled", {min_users: this.siteSettings.bootstrap_mode_min_users}), 'alert-bootstrap-mode']);

View File

@ -9,6 +9,9 @@ export default Ember.Component.extend({
this.autoFocus();
},
@computed('step.index')
showQuitButton: index => index === 0,
@computed('step.displayIndex', 'wizard.totalSteps')
showNextButton: (current, total) => current < total,
@ -49,6 +52,10 @@ export default Ember.Component.extend({
},
actions: {
quit() {
document.location = "/";
},
backStep() {
if (this.get('saving')) { return; }
this.sendAction('goBack');

View File

@ -21,6 +21,13 @@
</div>
<div class='wizard-buttons'>
{{#if showQuitButton}}
<button class='wizard-btn danger' {{action "quit"}} disabled={{saving}}>
{{fa-icon "chevron-left"}}
{{i18n "wizard.quit"}}
</button>
{{/if}}
{{#if showBackButton}}
<button class='wizard-btn back' {{action "backStep"}} disabled={{saving}}>
{{fa-icon "chevron-left"}}
@ -36,7 +43,7 @@
{{/if}}
{{#if showDoneButton}}
<button class='wizard-btn done' {{action "finished"}} disabled={{saving}}>
<button class='wizard-btn done' {{action "quit"}} disabled={{saving}}>
{{fa-icon "check"}}
{{i18n "wizard.done"}}
</button>

View File

@ -461,6 +461,10 @@ class ApplicationController < ActionController::Base
raise Discourse::InvalidAccess.new unless current_user && current_user.staff?
end
def ensure_wizard_enabled
raise Discourse::InvalidAccess.new unless SiteSetting.wizard_enabled?
end
def destination_url
request.original_url unless request.original_url =~ /uploads/
end

View File

@ -4,6 +4,7 @@ require_dependency 'wizard/step_updater'
class StepsController < ApplicationController
before_filter :ensure_wizard_enabled
before_filter :ensure_logged_in
before_filter :ensure_staff

View File

@ -2,7 +2,7 @@ require_dependency 'wizard'
require_dependency 'wizard/builder'
class WizardController < ApplicationController
before_filter :ensure_wizard_enabled, only: [:index]
before_filter :ensure_logged_in
before_filter :ensure_staff

View File

@ -55,6 +55,7 @@ class UserHistory < ActiveRecord::Base
rate_limited_like: 37, # not used anymore
revoke_email: 38,
deactivate_user: 39,
wizard_step: 40
)
end

View File

@ -1,4 +1,6 @@
require_dependency 'discourse_tagging'
require_dependency 'wizard'
require_dependency 'wizard/builder'
class SiteSerializer < ApplicationSerializer
@ -20,7 +22,8 @@ class SiteSerializer < ApplicationSerializer
:can_create_tag,
:can_tag_topics,
:tags_filter_regexp,
:top_tags
:top_tags,
:wizard_required
has_many :categories, serializer: BasicCategorySerializer, embed: :objects
has_many :trust_levels, embed: :objects
@ -110,4 +113,12 @@ class SiteSerializer < ApplicationSerializer
def top_tags
Tag.top_tags
end
def wizard_required
true
end
def include_wizard_required?
Wizard::Builder.new(scope.user).build.requires_completion?
end
end

View File

@ -353,6 +353,15 @@ class StaffActionLogger
}))
end
def log_wizard_step(step, opts={})
raise Discourse::InvalidParameters.new(:step) unless step
UserHistory.create(params(opts).merge({
action: UserHistory.actions[:wizard_step],
acting_user_id: @admin.id,
context: step.id
}))
end
private
def params(opts=nil)

View File

@ -165,6 +165,7 @@ en:
topic_admin_menu: "topic admin actions"
wizard_required: "It's time to configure your forum! <a href='/wizard/' data-auto-route='true'>Start the Setup Wizard</a>!"
emails_are_disabled: "All outgoing email has been globally disabled by an administrator. No email notifications of any kind will be sent."
bootstrap_mode_enabled: "To make launching your new site easier, you are in bootstrap mode. All new users will be granted trust level 1 and have daily email digest updates enabled. This will be automatically turned off when total user count exceeds %{min_users} users."
@ -3233,6 +3234,7 @@ en:
step: "Step %{current} of %{total}"
upload: "Upload"
uploading: "Uploading..."
quit: "Perform Setup Later"
invites:
add_user: "add"

View File

@ -972,6 +972,9 @@ developer:
default: 500
client: true
hidden: true
wizard_enabled:
default: false
hidden: true
embedding:
feed_polling_enabled:

View File

@ -1,12 +1,14 @@
require_dependency 'wizard/step'
require_dependency 'wizard/field'
require_dependency 'wizard/step_updater'
class Wizard
attr_reader :start, :steps, :user
attr_reader :steps, :user
def initialize(user)
@steps = []
@user = user
@first_step = nil
end
def create_step(step_name)
@ -24,7 +26,7 @@ class Wizard
# If it's the first step
if @steps.size == 1
@start = step
@first_step = step
step.index = 0
elsif last_step.present?
last_step.next = step
@ -33,9 +35,55 @@ class Wizard
end
end
def steps_with_fields
@steps_with_fields ||= @steps.select {|s| s.has_fields? }
end
def start
completed = UserHistory.where(
action: UserHistory.actions[:wizard_step],
context: steps_with_fields.map(&:id)
).uniq.pluck(:context)
# First uncompleted step
steps_with_fields.each do |s|
return s unless completed.include?(s.id)
end
@first_step
end
def create_updater(step_id, fields)
step = @steps.find {|s| s.id == step_id.dasherize}
Wizard::StepUpdater.new(@user, step, fields)
end
def completed?
completed_steps?(steps_with_fields.map(&:id))
end
def completed_steps?(steps)
steps = [steps].flatten.uniq
completed = UserHistory.where(
action: UserHistory.actions[:wizard_step],
context: steps
).distinct.order(:context).pluck(:context)
steps.sort == completed
end
def requires_completion?
return false unless SiteSetting.wizard_enabled?
admins = User.where("admin = true and id <> ?", Discourse.system_user.id).order(:created_at)
# In development mode all admins are developers, so the logic is a bit screwy:
unless Rails.env.development?
admins = admins.select {|a| !Guardian.new(a).is_developer? }
end
admins.present? && admins.first == @user && !completed? && (Topic.count < 15)
end
end

View File

@ -6,6 +6,8 @@ class Wizard
end
def build
return @wizard unless SiteSetting.wizard_enabled? && @wizard.user.try(:staff?)
@wizard.append_step('locale') do |step|
languages = step.add_field(id: 'default_locale',
type: 'dropdown',

View File

@ -15,6 +15,10 @@ class Wizard
field
end
def has_fields?
@fields.present?
end
def on_update(&block)
@updater = block
end

View File

@ -12,7 +12,12 @@ class Wizard
end
def update
@step.updater.call(self) if @step.updater.present?
@step.updater.call(self) if @step.present? && @step.updater.present?
if success?
logger = StaffActionLogger.new(@current_user)
logger.log_wizard_step(@step)
end
end
def success?

View File

@ -4,15 +4,19 @@ require_dependency 'wizard/builder'
require_dependency 'wizard/step_updater'
describe Wizard::StepUpdater do
before do
SiteSetting.wizard_enabled = true
end
let(:user) { Fabricate(:admin) }
let(:wizard) { Wizard::Builder.new(user).build }
context "locale" do
it "does not require refresh when the language stays the same" do
updater = wizard.create_updater('locale', default_locale: 'en')
updater.update
expect(updater.refresh_required?).to eq(false)
expect(wizard.completed_steps?('locale')).to eq(true)
end
it "updates the locale and requires refresh when it does change" do
@ -20,6 +24,7 @@ describe Wizard::StepUpdater do
updater.update
expect(SiteSetting.default_locale).to eq('ru')
expect(updater.refresh_required?).to eq(true)
expect(wizard.completed_steps?('locale')).to eq(true)
end
end
@ -30,6 +35,7 @@ describe Wizard::StepUpdater do
expect(updater.success?).to eq(true)
expect(SiteSetting.title).to eq("new forum title")
expect(SiteSetting.site_description).to eq("neat place")
expect(wizard.completed_steps?('forum-title')).to eq(true)
end
context "privacy settings" do
@ -39,6 +45,7 @@ describe Wizard::StepUpdater do
expect(updater.success?).to eq(true)
expect(SiteSetting.login_required?).to eq(false)
expect(SiteSetting.invite_only?).to eq(false)
expect(wizard.completed_steps?('privacy')).to eq(true)
end
it "updates to private correctly" do
@ -47,6 +54,7 @@ describe Wizard::StepUpdater do
expect(updater.success?).to eq(true)
expect(SiteSetting.login_required?).to eq(true)
expect(SiteSetting.invite_only?).to eq(true)
expect(wizard.completed_steps?('privacy')).to eq(true)
end
end
@ -62,6 +70,7 @@ describe Wizard::StepUpdater do
expect(SiteSetting.contact_email).to eq("eviltrout@example.com")
expect(SiteSetting.contact_url).to eq("http://example.com/custom-contact-url")
expect(SiteSetting.site_contact_username).to eq(user.username)
expect(wizard.completed_steps?('contact')).to eq(true)
end
it "doesn't update when there are errors" do
@ -71,6 +80,7 @@ describe Wizard::StepUpdater do
updater.update
expect(updater).to_not be_success
expect(updater.errors).to be_present
expect(wizard.completed_steps?('contact')).to eq(false)
end
end
@ -109,6 +119,8 @@ describe Wizard::StepUpdater do
updater.update
raw = Post.where(topic_id: SiteSetting.tos_topic_id, post_number: 1).pluck(:raw).first
expect(raw).to eq("company_domain - company_full_name - company_short_name template")
expect(wizard.completed_steps?('corporate')).to eq(true)
end
end
@ -120,6 +132,7 @@ describe Wizard::StepUpdater do
updater = wizard.create_updater('colors', theme_id: 'dark')
updater.update
expect(updater.success?).to eq(true)
expect(wizard.completed_steps?('colors')).to eq(true)
color_scheme.reload
expect(color_scheme).to be_enabled
@ -131,6 +144,7 @@ describe Wizard::StepUpdater do
updater = wizard.create_updater('colors', theme_id: 'dark')
updater.update
expect(updater.success?).to eq(true)
expect(wizard.completed_steps?('colors')).to eq(true)
color_scheme = ColorScheme.where(via_wizard: true).first
expect(color_scheme).to be_present
@ -150,6 +164,7 @@ describe Wizard::StepUpdater do
updater.update
expect(updater).to be_success
expect(wizard.completed_steps?('logos')).to eq(true)
expect(SiteSetting.logo_url).to eq('/uploads/logo.png')
expect(SiteSetting.logo_small_url).to eq('/uploads/logo-small.png')
expect(SiteSetting.favicon_url).to eq('/uploads/favicon.png')
@ -158,7 +173,6 @@ describe Wizard::StepUpdater do
end
context "invites step" do
let(:invites) {
return [{ email: 'regular@example.com', role: 'regular'},
{ email: 'moderator@example.com', role: 'moderator'}]
@ -169,6 +183,7 @@ describe Wizard::StepUpdater do
updater.update
expect(updater).to be_success
expect(wizard.completed_steps?('invites')).to eq(true)
expect(Invite.where(email: 'regular@example.com')).to be_present
expect(Invite.where(email: 'moderator@example.com')).to be_present

View File

@ -0,0 +1,30 @@
require 'rails_helper'
require 'wizard'
require 'wizard/builder'
describe Wizard::Builder do
let(:moderator) { Fabricate.build(:moderator) }
it "returns a wizard with steps when enabled" do
SiteSetting.wizard_enabled = true
wizard = Wizard::Builder.new(moderator).build
expect(wizard).to be_present
expect(wizard.steps).to be_present
end
it "returns a wizard without steps when enabled, but not staff" do
wizard = Wizard::Builder.new(Fabricate.build(:user)).build
expect(wizard).to be_present
expect(wizard.steps).to be_blank
end
it "returns a wizard without steps when disabled" do
SiteSetting.wizard_enabled = false
wizard = Wizard::Builder.new(moderator).build
expect(wizard).to be_present
expect(wizard.steps).to be_blank
end
end

View File

@ -2,18 +2,21 @@ require 'rails_helper'
require 'wizard'
describe Wizard do
before do
SiteSetting.wizard_enabled = true
end
let(:user) { Fabricate.build(:user) }
let(:wizard) { Wizard.new(user) }
it "has default values" do
expect(wizard.start).to be_blank
expect(wizard.steps).to be_empty
expect(wizard.user).to be_present
context "defaults" do
it "has default values" do
wizard = Wizard.new(Fabricate.build(:moderator))
expect(wizard.steps).to be_empty
expect(wizard.user).to be_present
end
end
describe "append_step" do
let(:user) { Fabricate.build(:moderator) }
let(:wizard) { Wizard.new(user) }
let(:step1) { wizard.create_step('first-step') }
let(:step2) { wizard.create_step('second-step') }
@ -54,4 +57,96 @@ describe Wizard do
end
end
describe "completed?" do
let(:user) { Fabricate.build(:moderator) }
let(:wizard) { Wizard.new(user) }
it "is complete when all steps with fields have logs" do
wizard.append_step('first') do |step|
step.add_field(id: 'element', type: 'text')
end
wizard.append_step('second') do |step|
step.add_field(id: 'another_element', type: 'text')
end
wizard.append_step('finished')
expect(wizard.start.id).to eq('first')
expect(wizard.completed_steps?('first')).to eq(false)
expect(wizard.completed_steps?('second')).to eq(false)
expect(wizard.completed?).to eq(false)
updater = wizard.create_updater('first', element: 'test')
updater.update
expect(wizard.start.id).to eq('second')
expect(wizard.completed_steps?('first')).to eq(true)
expect(wizard.completed?).to eq(false)
updater = wizard.create_updater('second', element: 'test')
updater.update
expect(wizard.completed_steps?('first')).to eq(true)
expect(wizard.completed_steps?('second')).to eq(true)
expect(wizard.completed_steps?('finished')).to eq(false)
expect(wizard.completed?).to eq(true)
# Once you've completed the wizard start at the beginning
expect(wizard.start.id).to eq('first')
end
end
describe "#requires_completion?" do
def build_simple(user)
wizard = Wizard.new(user)
wizard.append_step('simple') do |step|
step.add_field(id: 'name', type: 'text')
end
wizard
end
it "is false for anonymous" do
expect(build_simple(nil).requires_completion?).to eq(false)
end
it "is false for regular users" do
expect(build_simple(Fabricate.build(:user)).requires_completion?).to eq(false)
end
it "is false for a developer" do
developer = Fabricate(:admin)
Developer.create!(user_id: developer.id)
expect(build_simple(developer).requires_completion?).to eq(false)
end
it "it's false when the wizard is disabled" do
SiteSetting.wizard_enabled = false
admin = Fabricate(:admin)
expect(build_simple(admin).requires_completion?).to eq(false)
end
it "it's true for the first admin" do
admin = Fabricate(:admin)
expect(build_simple(admin).requires_completion?).to eq(true)
second_admin = Fabricate(:admin)
expect(build_simple(second_admin).requires_completion?).to eq(false)
end
it "is false for staff when complete" do
wizard = build_simple(Fabricate(:admin))
updater = wizard.create_updater('simple', name: 'Evil Trout')
updater.update
expect(wizard.requires_completion?).to eq(false)
# It's also false for another user
wizard = build_simple(Fabricate(:admin))
expect(wizard.requires_completion?).to eq(false)
end
end
end

View File

@ -2,6 +2,10 @@ require 'rails_helper'
describe StepsController do
before do
SiteSetting.wizard_enabled = true
end
it 'needs you to be logged in' do
expect {
xhr :put, :update, id: 'made-up-id', fields: { forum_title: "updated title" }
@ -19,6 +23,12 @@ describe StepsController do
log_in(:admin)
end
it "raises an error if the wizard is disabled" do
SiteSetting.wizard_enabled = false
xhr :put, :update, id: 'contact', fields: { contact_email: "eviltrout@example.com" }
expect(response).to be_forbidden
end
it "updates properly if you are staff" do
xhr :put, :update, id: 'contact', fields: { contact_email: "eviltrout@example.com" }
expect(response).to be_success

View File

@ -2,9 +2,13 @@ require 'rails_helper'
describe WizardController do
context 'index' do
context 'wizard enabled' do
render_views
before do
SiteSetting.wizard_enabled = true
end
it 'needs you to be logged in' do
expect { xhr :get, :index }.to raise_error(Discourse::NotLoggedIn)
end
@ -15,6 +19,13 @@ describe WizardController do
expect(response).to be_forbidden
end
it "raises an error if the wizard is disabled" do
SiteSetting.wizard_enabled = false
log_in(:admin)
xhr :get, :index
expect(response).to be_forbidden
end
it "renders the wizard if you are an admin" do
log_in(:admin)
xhr :get, :index
@ -27,7 +38,6 @@ describe WizardController do
expect(response).to be_success
expect(::JSON.parse(response.body).has_key?('wizard')).to eq(true)
end
end
end