feat: account onboarding with clearbit (#8857)

* feat: add clearbit lookup

* chore: fix typo in .env.example

* refactor: split lookup to reduce cognitive complexity

* feat: add more fields to lookup

* feat: extend accounts controller

* feat: save extra data to custom_attributes

* feat: allow v2 update with custom_attributes

* feat: add update route

* refactor: reduce complexity

* feat: move update to v1 controller

* test: add locale test

* feat: remove update from routes

* test: update API for custom attributes

* test: all custom attributes

* fix: v2 tests

* test: enterprise accounts controller

* fix: clearbit payload

* fix: with modified env

* feat: allow custom attributes updates to profile

* refactor: reduce complexity

* feat: allow clearbit api key in installation config

* refactor: move clearbit to internal

* feat: allow clearbit

* chore: add display_title for June

* feat: allow more internal options

* refactor: use globalconfig to fetch clearbit token

* test: move response body to a factory

* refactor: update ops

* chore: remove clearbit from .env.example

* chore: apply suggestions from code review

Co-authored-by: sojan-official <sojan@chatwoot.com>

---------

Co-authored-by: sojan-official <sojan@chatwoot.com>
This commit is contained in:
Shivam Mishra
2024-02-12 23:21:42 +05:30
committed by GitHub
parent fc6a22b072
commit 657843960c
14 changed files with 367 additions and 33 deletions

View File

@@ -189,7 +189,10 @@ RSpec.describe 'Accounts API', type: :request do
locale: 'en',
domain: 'example.com',
support_email: 'care@example.com',
auto_resolve_duration: 40
auto_resolve_duration: 40,
timezone: 'Asia/Kolkata',
industry: 'Technology',
company_size: '1-10'
}
it 'modifies an account' do
@@ -204,6 +207,10 @@ RSpec.describe 'Accounts API', type: :request do
expect(account.reload.domain).to eq(params[:domain])
expect(account.reload.support_email).to eq(params[:support_email])
expect(account.reload.auto_resolve_duration).to eq(params[:auto_resolve_duration])
%w[timezone industry company_size].each do |attribute|
expect(account.reload.custom_attributes[attribute]).to eq(params[attribute.to_sym])
end
end
it 'Throws error 422' do

View File

@@ -57,6 +57,18 @@ RSpec.describe 'Profile API', type: :request do
expect(agent.name).to eq('test')
end
it 'updates custom attributes' do
put '/api/v1/profile',
params: { profile: { phone_number: '+123456789' } },
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
agent.reload
expect(agent.custom_attributes['phone_number']).to eq('+123456789')
end
it 'updates the message_signature' do
put '/api/v1/profile',
params: { profile: { name: 'test', message_signature: 'Thanks\nMy Signature' } },

View File

@@ -17,7 +17,7 @@ RSpec.describe 'Accounts API', type: :request do
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do
allow(account_builder).to receive(:perform).and_return([user, account])
params = { email: email, user: nil, password: 'Password1!' }
params = { email: email, user: nil, locale: nil, password: 'Password1!' }
post api_v2_accounts_url,
params: params,
@@ -37,7 +37,7 @@ RSpec.describe 'Accounts API', type: :request do
allow(ChatwootCaptcha).to receive(:new).and_return(captcha)
allow(captcha).to receive(:valid?).and_return(true)
params = { email: email, user: nil, password: 'Password1!', h_captcha_client_response: '123' }
params = { email: email, user: nil, password: 'Password1!', locale: nil, h_captcha_client_response: '123' }
post api_v2_accounts_url,
params: params,
@@ -53,7 +53,7 @@ RSpec.describe 'Accounts API', type: :request do
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do
allow(account_builder).to receive(:perform).and_return(nil)
params = { email: nil, user: nil }
params = { email: nil, user: nil, locale: nil }
post api_v2_accounts_url,
params: params,
@@ -89,7 +89,7 @@ RSpec.describe 'Accounts API', type: :request do
allow(AccountBuilder).to receive(:new).and_return(account_builder)
allow(account_builder).to receive(:perform).and_return([user, account])
params = { email: email, user: nil, password: 'Password1!' }
params = { email: email, user: nil, password: 'Password1!', locale: nil }
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'api_only' do
post api_v2_accounts_url,
params: params,

View File

@@ -0,0 +1,72 @@
require 'rails_helper'
RSpec.describe Enterprise::Api::V2::AccountsController, type: :request do
let(:email) { Faker::Internet.email }
let(:user) { create(:user) }
let(:account) { create(:account) }
let(:clearbit_data) do
{
name: 'John Doe',
company_name: 'Acme Inc',
industry: 'Software',
company_size: '51-200',
timezone: 'America/Los_Angeles',
logo: 'https://logo.clearbit.com/acme.com'
}
end
before do
allow(Enterprise::ClearbitLookupService).to receive(:lookup).and_return(clearbit_data)
end
describe 'POST /api/v1/accounts' do
let(:account_builder) { double }
let(:account) { create(:account) }
let(:user) { create(:user, email: email, account: account) }
before do
allow(AccountBuilder).to receive(:new).and_return(account_builder)
end
it 'fetches data from clearbit and updates user and account info' do
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do
allow(account_builder).to receive(:perform).and_return([user, account])
params = { email: email, user: nil, locale: nil, password: 'Password1!' }
post api_v2_accounts_url,
params: params,
as: :json
expect(AccountBuilder).to have_received(:new).with(params.except(:password).merge(user_password: params[:password]))
expect(account_builder).to have_received(:perform)
expect(Enterprise::ClearbitLookupService).to have_received(:lookup).with(email)
custom_attributes = account.custom_attributes
expect(account.name).to eq('Acme Inc')
expect(custom_attributes['industry']).to eq('Software')
expect(custom_attributes['company_size']).to eq('51-200')
expect(custom_attributes['timezone']).to eq('America/Los_Angeles')
end
end
it 'handles errors when fetching data from clearbit' do
with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do
allow(account_builder).to receive(:perform).and_return([user, account])
allow(Enterprise::ClearbitLookupService).to receive(:lookup).and_raise(StandardError)
params = { email: email, user: nil, locale: nil, password: 'Password1!' }
post api_v2_accounts_url,
params: params,
as: :json
expect(AccountBuilder).to have_received(:new).with(params.except(:password).merge(user_password: params[:password]))
expect(account_builder).to have_received(:perform)
expect(Enterprise::ClearbitLookupService).to have_received(:lookup).with(email)
expect(response).to have_http_status(:success)
end
end
end
end

View File

@@ -0,0 +1,60 @@
require 'rails_helper'
RSpec.describe Enterprise::ClearbitLookupService do
describe '.lookup' do
let(:email) { 'test@example.com' }
let(:api_key) { 'clearbit_api_key' }
let(:clearbit_endpoint) { described_class::CLEARBIT_ENDPOINT }
let(:response_body) { build(:clearbit_combined_response) }
context 'when Clearbit is enabled' do
before do
stub_request(:get, "#{clearbit_endpoint}?email=#{email}")
.with(headers: { 'Authorization' => "Bearer #{api_key}" })
.to_return(status: 200, body: response_body, headers: { 'content-type' => ['application/json'] })
end
context 'when the API is working as expected' do
it 'returns the person and company information' do
with_modified_env CLEARBIT_API_KEY: api_key do
result = described_class.lookup(email)
expect(result).to eq({
:avatar => 'https://example.com/avatar.png',
:company_name => 'Doe Inc.',
:company_size => '1-10',
:industry => 'Software',
:logo => nil,
:name => 'John Doe',
:timezone => 'Asia/Kolkata'
})
end
end
end
context 'when the API returns an error' do
before do
stub_request(:get, "#{clearbit_endpoint}?email=#{email}")
.with(headers: { 'Authorization' => "Bearer #{api_key}" })
.to_return(status: 404, body: '', headers: {})
end
it 'logs the error and returns nil' do
with_modified_env CLEARBIT_API_KEY: api_key do
expect(Rails.logger).to receive(:error)
expect(described_class.lookup(email)).to be_nil
end
end
end
end
context 'when Clearbit is not enabled' do
it 'returns nil without making an API call' do
with_modified_env CLEARBIT_API_KEY: nil do
expect(Net::HTTP).not_to receive(:start)
expect(described_class.lookup(email)).to be_nil
end
end
end
end
end

View File

@@ -0,0 +1,29 @@
# spec/factories/response_bodies.rb
FactoryBot.define do
factory :clearbit_combined_response, class: Hash do
skip_create
initialize_with do
{
'person' => {
'name' => {
'fullName' => 'John Doe'
},
'avatar' => 'https://example.com/avatar.png'
},
'company' => {
'name' => 'Doe Inc.',
'timeZone' => 'Asia/Kolkata',
'category' => {
'sector' => 'Technology',
'industryGroup' => 'Software',
'industry' => 'Software'
},
'metrics' => {
'employees' => '1-10'
}
}
}.to_json
end
end
end