From 14eefe38245b73a3f8a2c8579d56f62efefd849e Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Sun, 17 Jan 2021 14:07:18 +0530 Subject: [PATCH] feat: Add Installation onboarding flow (#1640) Co-authored-by: Pranav Raj S --- .env.example | 2 +- app.json | 5 +- app/builders/account_builder.rb | 6 +- app/controllers/dashboard_controller.rb | 5 ++ .../installation/onboarding_controller.rb | 36 ++++++++++ .../assets/scss/super_admin/index.scss | 27 +++++++- .../installation/onboarding/index.html.erb | 59 ++++++++++++++++ config/routes.rb | 5 ++ db/seeds.rb | 69 +++++++++++-------- lib/chatwoot_hub.rb | 10 ++- lib/redis/redis_keys.rb | 1 + .../onboarding_controller_spec.rb | 64 +++++++++++++++++ 12 files changed, 250 insertions(+), 39 deletions(-) create mode 100644 app/controllers/installation/onboarding_controller.rb create mode 100644 app/views/installation/onboarding/index.html.erb create mode 100644 spec/controllers/installation/onboarding_controller_spec.rb diff --git a/.env.example b/.env.example index eeb61f00f..7c5299bad 100644 --- a/.env.example +++ b/.env.example @@ -18,7 +18,7 @@ FORCE_SSL=false # true : default option, allows 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 -ENABLE_ACCOUNT_SIGNUP=true +ENABLE_ACCOUNT_SIGNUP=false # Redis config REDIS_URL=redis://redis:6379 diff --git a/app.json b/app.json index b321e9b89..af1a496a4 100644 --- a/app.json +++ b/app.json @@ -11,7 +11,10 @@ "rails", "vue" ], - "success_url": "/app/login", + "success_url": "/", + "scripts": { + "postdeploy": "bundle exec rake db:seed" + }, "env": { "SECRET_TOKEN": { "description": "A secret key for verifying the integrity of signed cookies.", diff --git a/app/builders/account_builder.rb b/app/builders/account_builder.rb index 807f499e1..f7f94fada 100644 --- a/app/builders/account_builder.rb +++ b/app/builders/account_builder.rb @@ -2,7 +2,7 @@ class AccountBuilder 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 if @user.nil? @@ -26,7 +26,7 @@ class AccountBuilder if address.valid? # && !address.disposable? true else - raise InvalidEmail.new(valid: address.valid?) # , disposable: address.disposable?}) + raise InvalidEmail.new(valid: address.valid?) end end @@ -61,7 +61,7 @@ class AccountBuilder end def create_user - password = SecureRandom.alphanumeric(12) + password = user_password || SecureRandom.alphanumeric(12) @user = User.new(email: @email, password: password, diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index ddf8382f8..9ed9f7f47 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -3,6 +3,7 @@ class DashboardController < ActionController::Base before_action :set_global_config around_action :switch_locale + before_action :ensure_installation_onboarding, only: [:index] layout 'vueapp' @@ -24,4 +25,8 @@ class DashboardController < ActionController::Base APP_VERSION: Chatwoot.config[:version] ) end + + def ensure_installation_onboarding + redirect_to '/installation/onboarding' if ::Redis::Alfred.get(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING) + end end diff --git a/app/controllers/installation/onboarding_controller.rb b/app/controllers/installation/onboarding_controller.rb new file mode 100644 index 000000000..a6f04238b --- /dev/null +++ b/app/controllers/installation/onboarding_controller.rb @@ -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 diff --git a/app/javascript/dashboard/assets/scss/super_admin/index.scss b/app/javascript/dashboard/assets/scss/super_admin/index.scss index a646bf5f9..205a10f70 100644 --- a/app/javascript/dashboard/assets/scss/super_admin/index.scss +++ b/app/javascript/dashboard/assets/scss/super_admin/index.scss @@ -1,15 +1,36 @@ @import '../variables'; .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 { - background-color: $alert-color; + background-color: var(--r-500); border-radius: 5px; - color: $color-white; + color: var(--color-white); font-size: 14px; margin-bottom: 14px; padding: 10px; 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); + } +} diff --git a/app/views/installation/onboarding/index.html.erb b/app/views/installation/onboarding/index.html.erb new file mode 100644 index 000000000..8287714cd --- /dev/null +++ b/app/views/installation/onboarding/index.html.erb @@ -0,0 +1,59 @@ + + + + SuperAdmin | Chatwoot + <%= javascript_pack_tag 'superadmin' %> + <%= stylesheet_pack_tag 'superadmin' %> + + +
+ + + diff --git a/config/routes.rb b/config/routes.rb index 7d1715dea..06b8a4487 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -230,6 +230,11 @@ Rails.application.routes.draw do end end + namespace :installation do + get 'onboarding', to: 'onboarding#index' + post 'onboarding', to: 'onboarding#create' + end + # --------------------------------------------------------------------- # Routes for swagger docs get '/swagger/*path', to: 'swagger#respond' diff --git a/db/seeds.rb b/db/seeds.rb index 3b1395004..e3422e62c 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -2,39 +2,48 @@ GlobalConfig.clear_cache ConfigLoader.new.process -account = Account.create!( - name: 'Acme Inc', - domain: 'support.chatwoot.com', - support_email: ENV.fetch('MAILER_SENDER_EMAIL', 'accounts@chatwoot.com') -) +## Seeds productions +if Rails.env.production? + # Setup Onboarding flow + ::Redis::Alfred.set(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING, true) +end -user = User.new(name: 'John', email: 'john@acme.inc', password: '123456') -user.skip_confirmation! -user.save! +## Seeds for Local Development +unless Rails.env.production? + 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!( - account_id: account.id, - user_id: user.id, - role: :administrator -) + user = User.new(name: 'John', email: 'john@acme.inc', password: '123456') + user.skip_confirmation! + user.save! -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') -InboxMember.create!(user: user, inbox: inbox) + web_widget = Channel::WebWidget.create!(account: account, website_url: 'https://acme.inc') -contact = Contact.create!(name: 'jane', email: 'jane@example.com', phone_number: '0000', account: account) -contact_inbox = ContactInbox.create!(inbox: inbox, contact: contact, source_id: user.id) -conversation = Conversation.create!( - account: account, - inbox: inbox, - status: :open, - assignee: user, - contact: contact, - contact_inbox: contact_inbox, - 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.') + inbox = Inbox.create!(channel: web_widget, account: account, name: 'Acme Support') + InboxMember.create!(user: user, inbox: inbox) + + contact = Contact.create!(name: 'jane', email: 'jane@example.com', phone_number: '0000', account: account) + contact_inbox = ContactInbox.create!(inbox: inbox, contact: contact, source_id: user.id) + conversation = Conversation.create!( + account: account, + inbox: inbox, + status: :open, + assignee: user, + contact: contact, + contact_inbox: contact_inbox, + 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 diff --git a/lib/chatwoot_hub.rb b/lib/chatwoot_hub.rb index 69e95ad9f..ef500e57b 100644 --- a/lib/chatwoot_hub.rb +++ b/lib/chatwoot_hub.rb @@ -1,5 +1,5 @@ class ChatwootHub - BASE_URL = 'https://hub.chatwoot.com'.freeze + BASE_URL = ENV['CHATWOOT_HUB_URL'] || 'https://hub.chatwoot.com' def self.instance_config { @@ -19,4 +19,12 @@ class ChatwootHub end version 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 diff --git a/lib/redis/redis_keys.rb b/lib/redis/redis_keys.rb index cef29705c..13c24cdb4 100644 --- a/lib/redis/redis_keys.rb +++ b/lib/redis/redis_keys.rb @@ -27,5 +27,6 @@ module Redis::RedisKeys REAUTHORIZATION_REQUIRED = 'REAUTHORIZATION_REQUIRED:%s:%d'.freeze ## Internal Installation related keys + CHATWOOT_INSTALLATION_ONBOARDING = 'CHATWOOT_INSTALLATION_ONBOARDING'.freeze LATEST_CHATWOOT_VERSION = 'LATEST_CHATWOOT_VERSION'.freeze end diff --git a/spec/controllers/installation/onboarding_controller_spec.rb b/spec/controllers/installation/onboarding_controller_spec.rb new file mode 100644 index 000000000..7b1416616 --- /dev/null +++ b/spec/controllers/installation/onboarding_controller_spec.rb @@ -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