mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 20:18:08 +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