mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-30 18:47:51 +00:00 
			
		
		
		
	feat: Add Installation onboarding flow (#1640)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
		| @@ -18,7 +18,7 @@ FORCE_SSL=false | |||||||
| # true : default option, allows sign ups | # true : default option, allows sign ups | ||||||
| # false : disables all the end points related to sign ups | # false : disables all the end points related to sign ups | ||||||
| # api_only: disables the UI for signup, but you can create sign ups via the account apis | # api_only: disables the UI for signup, but you can create sign ups via the account apis | ||||||
| ENABLE_ACCOUNT_SIGNUP=true | ENABLE_ACCOUNT_SIGNUP=false | ||||||
|  |  | ||||||
| # Redis config | # Redis config | ||||||
| REDIS_URL=redis://redis:6379 | REDIS_URL=redis://redis:6379 | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								app.json
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								app.json
									
									
									
									
									
								
							| @@ -11,7 +11,10 @@ | |||||||
|     "rails", |     "rails", | ||||||
|     "vue" |     "vue" | ||||||
|   ], |   ], | ||||||
|   "success_url": "/app/login", |   "success_url": "/", | ||||||
|  |   "scripts": { | ||||||
|  |     "postdeploy": "bundle exec rake db:seed" | ||||||
|  |   }, | ||||||
|   "env": { |   "env": { | ||||||
|     "SECRET_TOKEN": { |     "SECRET_TOKEN": { | ||||||
|       "description": "A secret key for verifying the integrity of signed cookies.", |       "description": "A secret key for verifying the integrity of signed cookies.", | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
|  |  | ||||||
| class AccountBuilder | class AccountBuilder | ||||||
|   include CustomExceptions::Account |   include CustomExceptions::Account | ||||||
|   pattr_initialize [:account_name!, :email!, :confirmed!, :user, :user_full_name] |   pattr_initialize [:account_name!, :email!, :confirmed!, :user, :user_full_name, :user_password] | ||||||
|  |  | ||||||
|   def perform |   def perform | ||||||
|     if @user.nil? |     if @user.nil? | ||||||
| @@ -26,7 +26,7 @@ class AccountBuilder | |||||||
|     if address.valid? # && !address.disposable? |     if address.valid? # && !address.disposable? | ||||||
|       true |       true | ||||||
|     else |     else | ||||||
|       raise InvalidEmail.new(valid: address.valid?) # , disposable: address.disposable?}) |       raise InvalidEmail.new(valid: address.valid?) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
| @@ -61,7 +61,7 @@ class AccountBuilder | |||||||
|   end |   end | ||||||
|  |  | ||||||
|   def create_user |   def create_user | ||||||
|     password = SecureRandom.alphanumeric(12) |     password = user_password || SecureRandom.alphanumeric(12) | ||||||
|  |  | ||||||
|     @user = User.new(email: @email, |     @user = User.new(email: @email, | ||||||
|                      password: password, |                      password: password, | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ class DashboardController < ActionController::Base | |||||||
|  |  | ||||||
|   before_action :set_global_config |   before_action :set_global_config | ||||||
|   around_action :switch_locale |   around_action :switch_locale | ||||||
|  |   before_action :ensure_installation_onboarding, only: [:index] | ||||||
|  |  | ||||||
|   layout 'vueapp' |   layout 'vueapp' | ||||||
|  |  | ||||||
| @@ -24,4 +25,8 @@ class DashboardController < ActionController::Base | |||||||
|       APP_VERSION: Chatwoot.config[:version] |       APP_VERSION: Chatwoot.config[:version] | ||||||
|     ) |     ) | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  |   def ensure_installation_onboarding | ||||||
|  |     redirect_to '/installation/onboarding' if ::Redis::Alfred.get(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING) | ||||||
|  |   end | ||||||
| end | end | ||||||
|   | |||||||
							
								
								
									
										36
									
								
								app/controllers/installation/onboarding_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								app/controllers/installation/onboarding_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | class Installation::OnboardingController < ApplicationController | ||||||
|  |   before_action :ensure_installation_onboarding | ||||||
|  |  | ||||||
|  |   def index; end | ||||||
|  |  | ||||||
|  |   def create | ||||||
|  |     begin | ||||||
|  |       AccountBuilder.new( | ||||||
|  |         account_name: onboarding_params.dig(:user, :company), | ||||||
|  |         user_full_name: onboarding_params.dig(:user, :name), | ||||||
|  |         email: onboarding_params.dig(:user, :email), | ||||||
|  |         user_password: params.dig(:user, :password), | ||||||
|  |         confirmed: true | ||||||
|  |       ).perform | ||||||
|  |     rescue StandardError => e | ||||||
|  |       redirect_to '/', flash: { error: e.message } and return | ||||||
|  |     end | ||||||
|  |     finish_onboarding | ||||||
|  |     redirect_to '/' | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   private | ||||||
|  |  | ||||||
|  |   def onboarding_params | ||||||
|  |     params.permit(:subscribe_to_updates, user: [:name, :company, :email]) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def finish_onboarding | ||||||
|  |     ::Redis::Alfred.delete(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING) | ||||||
|  |     ChatwootHub.register_instance(onboarding_params) if onboarding_params[:subscribe_to_updates] | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def ensure_installation_onboarding | ||||||
|  |     redirect_to '/' unless ::Redis::Alfred.get(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING) | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -1,15 +1,36 @@ | |||||||
| @import '../variables'; | @import '../variables'; | ||||||
|  |  | ||||||
| .superadmin-body { | .superadmin-body { | ||||||
|   background: $color-background; |   background: var(--color-background); | ||||||
|  |  | ||||||
|  |   .hero--title { | ||||||
|  |     font-size: var(--font-size-mega); | ||||||
|  |     font-weight: var(--font-weight-light); | ||||||
|  |     margin-top: var(--space-large); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| .alert-box { | .alert-box { | ||||||
|   background-color: $alert-color; |   background-color: var(--r-500); | ||||||
|   border-radius: 5px; |   border-radius: 5px; | ||||||
|   color: $color-white; |   color: var(--color-white); | ||||||
|   font-size: 14px; |   font-size: 14px; | ||||||
|   margin-bottom: 14px; |   margin-bottom: 14px; | ||||||
|   padding: 10px; |   padding: 10px; | ||||||
|   text-align: center; |   text-align: center; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .update-subscription--checkbox { | ||||||
|  |   display: flex; | ||||||
|  |  | ||||||
|  |   input { | ||||||
|  |     line-height: 1.5; | ||||||
|  |     margin-right: var(--space-one); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   div { | ||||||
|  |     font-size: var(--font-size-small); | ||||||
|  |     line-height: 1.5; | ||||||
|  |     margin-bottom: var(--space-normal); | ||||||
|  |   } | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										59
									
								
								app/views/installation/onboarding/index.html.erb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/views/installation/onboarding/index.html.erb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  |   <head> | ||||||
|  |     <title>SuperAdmin | Chatwoot</title> | ||||||
|  |     <%= javascript_pack_tag 'superadmin' %> | ||||||
|  |     <%= stylesheet_pack_tag 'superadmin' %> | ||||||
|  |   </head> | ||||||
|  |   <body data-gr-c-s-loaded="true"> | ||||||
|  |     <div id="app" class="superadmin-body app-wrapper app-root"> | ||||||
|  |       <div class="medium column login"> | ||||||
|  |         <div class="text-center medium-12 login__hero align-self-top"> | ||||||
|  |           <img | ||||||
|  |             src="/brand-assets/logo.svg" | ||||||
|  |             alt="Chatwoot logo" | ||||||
|  |             class="hero__logo" | ||||||
|  |           /> | ||||||
|  |           <h2 class="hero--title"> | ||||||
|  |             Howdy, Welcome to Chatwoot 👋 | ||||||
|  |           </h2> | ||||||
|  |         </div> | ||||||
|  |         <div class="row align-center"> | ||||||
|  |           <div class="small-12 medium-4 column"> | ||||||
|  |             <%= form_tag('/installation/onboarding', class: 'login-box column align-self-top') do %> | ||||||
|  |               <div class="column log-in-form"> | ||||||
|  |                 <% if flash[:error].present? %> | ||||||
|  |                 <div data-alert class="alert-box warning"><%= flash[:error] %></div> | ||||||
|  |                 <% end %> | ||||||
|  |                 <label> | ||||||
|  |                   <span>Name</span> | ||||||
|  |                   <%= text_field :user, :name, placeholder: "Enter your full name. eg: Bruce Wayne", required: true %> | ||||||
|  |                 </label> | ||||||
|  |                 <label> | ||||||
|  |                   <span>Company Name</span> | ||||||
|  |                   <%= text_field :user, :company, placeholder: "Enter an account name. eg: Wayne Enterprises", required: true %> | ||||||
|  |                 </label> | ||||||
|  |                 <label> | ||||||
|  |                   <span>Work Email</span> | ||||||
|  |                   <%= email_field :user, :email, placeholder: "Enter your work email address. eg: bruce@wayne.enterprises", required: true %> | ||||||
|  |                 </label> | ||||||
|  |                 <label> | ||||||
|  |                   <span>Password</span> | ||||||
|  |                   <%= password_field :user, :password, placeholder: "Enter a password with 6 characters or more.", required: true %> | ||||||
|  |                 </label> | ||||||
|  |                 <div class="update-subscription--checkbox"> | ||||||
|  |                   <%= check_box_tag "subscribe_to_updates", 'true', true %> | ||||||
|  |                   <div for="subscribe_to_updates"> | ||||||
|  |                   Subscribe to release notes, newsletters & product feedback surveys. | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |                 <button type="submit" class="button nice large expanded"> | ||||||
|  |                   Finish Setup | ||||||
|  |                 </button> | ||||||
|  |               </div> | ||||||
|  |             <% end %> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
| @@ -230,6 +230,11 @@ Rails.application.routes.draw do | |||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  |   namespace :installation do | ||||||
|  |     get 'onboarding', to: 'onboarding#index' | ||||||
|  |     post 'onboarding', to: 'onboarding#create' | ||||||
|  |   end | ||||||
|  |  | ||||||
|   # --------------------------------------------------------------------- |   # --------------------------------------------------------------------- | ||||||
|   # Routes for swagger docs |   # Routes for swagger docs | ||||||
|   get '/swagger/*path', to: 'swagger#respond' |   get '/swagger/*path', to: 'swagger#respond' | ||||||
|   | |||||||
							
								
								
									
										69
									
								
								db/seeds.rb
									
									
									
									
									
								
							
							
						
						
									
										69
									
								
								db/seeds.rb
									
									
									
									
									
								
							| @@ -2,39 +2,48 @@ | |||||||
| GlobalConfig.clear_cache | GlobalConfig.clear_cache | ||||||
| ConfigLoader.new.process | ConfigLoader.new.process | ||||||
|  |  | ||||||
| account = Account.create!( | ## Seeds productions | ||||||
|   name: 'Acme Inc', | if Rails.env.production? | ||||||
|   domain: 'support.chatwoot.com', |   # Setup Onboarding flow | ||||||
|   support_email: ENV.fetch('MAILER_SENDER_EMAIL', 'accounts@chatwoot.com') |   ::Redis::Alfred.set(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING, true) | ||||||
| ) | end | ||||||
|  |  | ||||||
| user = User.new(name: 'John', email: 'john@acme.inc', password: '123456') | ## Seeds for Local Development | ||||||
| user.skip_confirmation! | unless Rails.env.production? | ||||||
| user.save! |   SuperAdmin.create!(email: 'john@acme.inc', password: '123456') | ||||||
|  |  | ||||||
| SuperAdmin.create!(email: 'john@acme.inc', password: '123456') unless Rails.env.production? |   account = Account.create!( | ||||||
|  |     name: 'Acme Inc', | ||||||
|  |     domain: 'support.chatwoot.com', | ||||||
|  |     support_email: ENV.fetch('MAILER_SENDER_EMAIL', 'accounts@chatwoot.com') | ||||||
|  |   ) | ||||||
|  |  | ||||||
| AccountUser.create!( |   user = User.new(name: 'John', email: 'john@acme.inc', password: '123456') | ||||||
|   account_id: account.id, |   user.skip_confirmation! | ||||||
|   user_id: user.id, |   user.save! | ||||||
|   role: :administrator |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| web_widget = Channel::WebWidget.create!(account: account, website_url: 'https://acme.inc') |   AccountUser.create!( | ||||||
|  |     account_id: account.id, | ||||||
|  |     user_id: user.id, | ||||||
|  |     role: :administrator | ||||||
|  |   ) | ||||||
|  |  | ||||||
| inbox = Inbox.create!(channel: web_widget, account: account, name: 'Acme Support') |   web_widget = Channel::WebWidget.create!(account: account, website_url: 'https://acme.inc') | ||||||
| InboxMember.create!(user: user, inbox: inbox) |  | ||||||
|  |  | ||||||
| contact = Contact.create!(name: 'jane', email: 'jane@example.com', phone_number: '0000', account: account) |   inbox = Inbox.create!(channel: web_widget, account: account, name: 'Acme Support') | ||||||
| contact_inbox = ContactInbox.create!(inbox: inbox, contact: contact, source_id: user.id) |   InboxMember.create!(user: user, inbox: inbox) | ||||||
| conversation = Conversation.create!( |  | ||||||
|   account: account, |   contact = Contact.create!(name: 'jane', email: 'jane@example.com', phone_number: '0000', account: account) | ||||||
|   inbox: inbox, |   contact_inbox = ContactInbox.create!(inbox: inbox, contact: contact, source_id: user.id) | ||||||
|   status: :open, |   conversation = Conversation.create!( | ||||||
|   assignee: user, |     account: account, | ||||||
|   contact: contact, |     inbox: inbox, | ||||||
|   contact_inbox: contact_inbox, |     status: :open, | ||||||
|   additional_attributes: {} |     assignee: user, | ||||||
| ) |     contact: contact, | ||||||
| Message.create!(content: 'Hello', account: account, inbox: inbox, conversation: conversation, message_type: :incoming) |     contact_inbox: contact_inbox, | ||||||
| CannedResponse.create!(account: account, short_code: 'start', content: 'Hello welcome to chatwoot.') |     additional_attributes: {} | ||||||
|  |   ) | ||||||
|  |   Message.create!(content: 'Hello', account: account, inbox: inbox, conversation: conversation, message_type: :incoming) | ||||||
|  |   CannedResponse.create!(account: account, short_code: 'start', content: 'Hello welcome to chatwoot.') | ||||||
|  | end | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| class ChatwootHub | class ChatwootHub | ||||||
|   BASE_URL = 'https://hub.chatwoot.com'.freeze |   BASE_URL = ENV['CHATWOOT_HUB_URL'] || 'https://hub.chatwoot.com' | ||||||
|  |  | ||||||
|   def self.instance_config |   def self.instance_config | ||||||
|     { |     { | ||||||
| @@ -19,4 +19,12 @@ class ChatwootHub | |||||||
|     end |     end | ||||||
|     version |     version | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  |   def self.register_instance(info) | ||||||
|  |     RestClient.post("#{BASE_URL}/register_instance", info.merge(instance_config).to_json, { content_type: :json, accept: :json }) | ||||||
|  |   rescue *ExceptionList::REST_CLIENT_EXCEPTIONS, *ExceptionList::URI_EXCEPTIONS => e | ||||||
|  |     Rails.logger.info "Exception: #{e.message}" | ||||||
|  |   rescue StandardError => e | ||||||
|  |     Raven.capture_exception(e) | ||||||
|  |   end | ||||||
| end | end | ||||||
|   | |||||||
| @@ -27,5 +27,6 @@ module Redis::RedisKeys | |||||||
|   REAUTHORIZATION_REQUIRED =  'REAUTHORIZATION_REQUIRED:%<obj_type>s:%<obj_id>d'.freeze |   REAUTHORIZATION_REQUIRED =  'REAUTHORIZATION_REQUIRED:%<obj_type>s:%<obj_id>d'.freeze | ||||||
|  |  | ||||||
|   ## Internal Installation related keys |   ## Internal Installation related keys | ||||||
|  |   CHATWOOT_INSTALLATION_ONBOARDING = 'CHATWOOT_INSTALLATION_ONBOARDING'.freeze | ||||||
|   LATEST_CHATWOOT_VERSION = 'LATEST_CHATWOOT_VERSION'.freeze |   LATEST_CHATWOOT_VERSION = 'LATEST_CHATWOOT_VERSION'.freeze | ||||||
| end | end | ||||||
|   | |||||||
							
								
								
									
										64
									
								
								spec/controllers/installation/onboarding_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								spec/controllers/installation/onboarding_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | require 'rails_helper' | ||||||
|  |  | ||||||
|  | RSpec.describe 'Installation::Onboarding API', type: :request do | ||||||
|  |   let(:super_admin) { create(:super_admin) } | ||||||
|  |  | ||||||
|  |   describe 'GET /installation/onboarding' do | ||||||
|  |     context 'when CHATWOOT_INSTALLATION_ONBOARDING redis key is not set' do | ||||||
|  |       it 'redirects back' do | ||||||
|  |         expect(::Redis::Alfred.get(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING)).to eq nil | ||||||
|  |         get '/installation/onboarding' | ||||||
|  |         expect(response).to have_http_status(:redirect) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     context 'when CHATWOOT_INSTALLATION_ONBOARDING redis key is set' do | ||||||
|  |       it 'returns onboarding page' do | ||||||
|  |         ::Redis::Alfred.set(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING, true) | ||||||
|  |         get '/installation/onboarding' | ||||||
|  |         expect(response).to have_http_status(:success) | ||||||
|  |         ::Redis::Alfred.delete(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   describe 'POST /installation/onboarding' do | ||||||
|  |     let(:account_builder) { instance_double('account_builder') } | ||||||
|  |  | ||||||
|  |     before do | ||||||
|  |       allow(AccountBuilder).to receive(:new).and_return(account_builder) | ||||||
|  |       allow(account_builder).to receive(:perform).and_return(true) | ||||||
|  |       allow(ChatwootHub).to receive(:register_instance).and_return(true) | ||||||
|  |       ::Redis::Alfred.set(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING, true) | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     after do | ||||||
|  |       ::Redis::Alfred.delete(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING) | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     context 'when onboarding successfull' do | ||||||
|  |       it 'deletes the redis key' do | ||||||
|  |         post '/installation/onboarding', params: { user: {} } | ||||||
|  |         expect(::Redis::Alfred.get(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING)).to eq nil | ||||||
|  |       end | ||||||
|  |  | ||||||
|  |       it 'will not call register instance when checkboxes are unchecked' do | ||||||
|  |         post '/installation/onboarding', params: { user: {} } | ||||||
|  |         expect(ChatwootHub).not_to have_received(:register_instance) | ||||||
|  |       end | ||||||
|  |  | ||||||
|  |       it 'will call register instance when checkboxes are checked' do | ||||||
|  |         post '/installation/onboarding', params: { user: {}, subscribe_to_updates: 1 } | ||||||
|  |         expect(ChatwootHub).to have_received(:register_instance) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     context 'when onboarding is not successfull' do | ||||||
|  |       it ' does not deletes the redis key' do | ||||||
|  |         allow(AccountBuilder).to receive(:new).and_raise('error') | ||||||
|  |         post '/installation/onboarding', params: { user: {} } | ||||||
|  |         expect(::Redis::Alfred.get(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING)).not_to eq nil | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
		Reference in New Issue
	
	Block a user
	 Sojan Jose
					Sojan Jose