feat: Add Installation onboarding flow (#1640)

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
Sojan Jose 2021-01-17 14:07:18 +05:30 committed by GitHub
parent a5c3c4301c
commit 14eefe3824
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 250 additions and 39 deletions

View file

@ -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

View file

@ -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.",

View file

@ -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,

View file

@ -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

View 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

View file

@ -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);
}
}

View 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>

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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

View 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