feat: Add Installation onboarding flow (#1640)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
parent
a5c3c4301c
commit
14eefe3824
12 changed files with 250 additions and 39 deletions
|
@ -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
|
||||
|
|
5
app.json
5
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.",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
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';
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
|
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
|
||||
|
||||
namespace :installation do
|
||||
get 'onboarding', to: 'onboarding#index'
|
||||
post 'onboarding', to: 'onboarding#create'
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Routes for swagger docs
|
||||
get '/swagger/*path', to: 'swagger#respond'
|
||||
|
|
69
db/seeds.rb
69
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -27,5 +27,6 @@ module Redis::RedisKeys
|
|||
REAUTHORIZATION_REQUIRED = 'REAUTHORIZATION_REQUIRED:%<obj_type>s:%<obj_id>d'.freeze
|
||||
|
||||
## Internal Installation related keys
|
||||
CHATWOOT_INSTALLATION_ONBOARDING = 'CHATWOOT_INSTALLATION_ONBOARDING'.freeze
|
||||
LATEST_CHATWOOT_VERSION = 'LATEST_CHATWOOT_VERSION'.freeze
|
||||
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
|
Loading…
Reference in a new issue