feat: Provision captain accounts automatically (#10168)

- Provision accounts on Chatwoot cloud automatically if the feature is enabled
This commit is contained in:
Sojan Jose
2024-09-26 19:21:29 -07:00
committed by GitHub
parent d107d0adec
commit 4a7a0427e9
7 changed files with 136 additions and 31 deletions

View File

@@ -41,7 +41,7 @@ class Integrations::App
when 'linear'
account.feature_enabled?('linear_integration')
when 'captain'
account.feature_enabled?('captain_integration') && ENV['CAPTAIN_API_URL'].present?
account.feature_enabled?('captain_integration') && InstallationConfig.find_by(name: 'CAPTAIN_APP_URL').present?
else
true
end

View File

@@ -18,7 +18,9 @@ class Integrations::Hook < ApplicationRecord
include Reauthorizable
attr_readonly :app_id, :account_id, :inbox_id, :hook_type
before_validation :ensure_captain_config_present, on: :create
before_validation :ensure_hook_type
validates :account_id, presence: true
validates :app_id, presence: true
validates :inbox_id, presence: true, if: -> { hook_type == 'inbox' }
@@ -62,6 +64,30 @@ class Integrations::Hook < ApplicationRecord
private
def ensure_captain_config_present
return if app_id != 'captain'
# Already configured, skip this
return if settings['access_token'].present?
ensure_captain_is_enabled
fetch_and_set_captain_settings
end
def ensure_captain_is_enabled
raise 'Captain is not enabled' unless Integrations::App.find(id: 'captain').active?(account)
end
def fetch_and_set_captain_settings
captain_response = ChatwootHub.get_captain_settings(account)
raise "Failed to get captain settings: #{captain_response.body}" unless captain_response.success?
captain_settings = JSON.parse(captain_response.body)
settings['account_email'] = captain_settings['account_email']
settings['account_id'] = captain_settings['captain_account_id'].to_s
settings['access_token'] = captain_settings['access_token']
settings['assistant_id'] = captain_settings['assistant_id'].to_s
end
def ensure_hook_type
self.hook_type = app.params[:hook_type] if app.present?
end

View File

@@ -28,30 +28,6 @@ captain:
"additionalProperties": false,
}
settings_form_schema: [
{
"label": "Access Token",
"type": "text",
"name": "access_token",
"validation": "required",
},
{
"label": "Account ID",
"type": "text",
"name": "account_id",
"validation": "required",
},
{
"label": "Account Email",
"type": "text",
"name": "account_email",
"validation": "required",
},
{
"label": "Assistant Id",
"type": "text",
"name": "assistant_id",
"validation": "required",
},
{
"label": "Inbox Ids",
"type": "text",

View File

@@ -1,3 +1,4 @@
# TODO: lets use HTTParty instead of RestClient
class ChatwootHub
BASE_URL = ENV.fetch('CHATWOOT_HUB_URL', 'https://hub.2.chatwoot.com')
PING_URL = "#{BASE_URL}/ping".freeze
@@ -5,6 +6,7 @@ class ChatwootHub
PUSH_NOTIFICATION_URL = "#{BASE_URL}/send_push".freeze
EVENTS_URL = "#{BASE_URL}/events".freeze
BILLING_URL = "#{BASE_URL}/billing".freeze
CAPTAIN_ACCOUNTS_URL = "#{BASE_URL}/instance_captain_accounts".freeze
def self.installation_identifier
identifier = InstallationConfig.find_by(name: 'INSTALLATION_IDENTIFIER')&.value
@@ -44,16 +46,20 @@ class ChatwootHub
def self.instance_metrics
{
accounts_count: Account.count,
users_count: User.count,
inboxes_count: Inbox.count,
conversations_count: Conversation.count,
incoming_messages_count: Message.incoming.count,
outgoing_messages_count: Message.outgoing.count,
accounts_count: fetch_count(Account),
users_count: fetch_count(User),
inboxes_count: fetch_count(Inbox),
conversations_count: fetch_count(Conversation),
incoming_messages_count: fetch_count(Message.incoming),
outgoing_messages_count: fetch_count(Message.outgoing),
additional_information: {}
}
end
def self.fetch_count(model)
model.last&.id || 0
end
def self.sync_with_hub
begin
info = instance_config
@@ -86,6 +92,17 @@ class ChatwootHub
ChatwootExceptionTracker.new(e).capture_exception
end
def self.get_captain_settings(account)
info = {
installation_identifier: installation_identifier,
chatwoot_account_id: account.id,
account_name: account.name
}
HTTParty.post(CAPTAIN_ACCOUNTS_URL,
body: info.to_json,
headers: { 'Content-Type' => 'application/json', 'Accept' => 'application/json' })
end
def self.emit_event(event_name, event_data)
return if ENV['DISABLE_TELEMETRY']

View File

@@ -69,4 +69,24 @@ describe ChatwootHub do
end
end
end
context 'when fetching captain settings' do
it 'returns the captain settings' do
account = create(:account)
stub_request(:post, ChatwootHub::CAPTAIN_ACCOUNTS_URL).with(
body: { installation_identifier: described_class.installation_identifier, chatwoot_account_id: account.id, account_name: account.name }
).to_return(
body: { account_email: 'test@test.com', account_id: '123', access_token: '123', assistant_id: '123' }.to_json
)
expect(described_class.get_captain_settings(account).body).to eq(
{
account_email: 'test@test.com',
account_id: '123',
access_token: '123',
assistant_id: '123'
}.to_json
)
end
end
end

View File

@@ -66,6 +66,28 @@ RSpec.describe Integrations::App do
end
end
context 'when the app is captain' do
let(:app_name) { 'captain' }
it 'returns false is the captain feature is not enabled' do
expect(app.active?(account)).to be false
end
it 'returns false if the captain app url is not present' do
account.enable_features('captain_integration')
account.save!
expect(InstallationConfig.find_by(name: 'CAPTAIN_APP_URL')).to be_nil
expect(app.active?(account)).to be false
end
it 'returns true if the captain feature is enabled and the captain app url is present' do
account.enable_features('captain_integration')
account.save!
InstallationConfig.where(name: 'CAPTAIN_APP_URL').first_or_create(value: 'https://app.chatwoot.com')
expect(app.active?(account)).to be true
end
end
context 'when other apps are queried' do
let(:app_name) { 'webhook' }

View File

@@ -51,4 +51,48 @@ RSpec.describe Integrations::Hook do
expect(openai_double).to have_received(:perform)
end
end
describe 'creating a captain hook' do
let(:account) { create(:account) }
let(:inbox) { create(:inbox, account: account) }
let(:settings) { { inbox_ids: inbox.id } }
it 'raises an error if captain is not enabled' do
expect { create(:integrations_hook, app_id: 'captain', account: account, settings: settings) }.to raise_error('Captain is not enabled')
end
context 'when captain is enabled' do
before do
account.enable_features('captain_integration')
account.save!
InstallationConfig.where(name: 'CAPTAIN_APP_URL').first_or_create(value: 'https://app.chatwoot.com')
stub_request(:post, ChatwootHub::CAPTAIN_ACCOUNTS_URL).to_return(body: {
account_email: 'test@example.com',
captain_account_id: 1,
access_token: 'access_token',
assistant_id: 1
}.to_json)
end
it 'populates the settings with captain settings' do
hook = create(:integrations_hook, app_id: 'captain', account: account, settings: settings)
expect(hook.settings['account_email']).to be_present
expect(hook.settings['assistant_id']).to be_present
expect(hook.settings['access_token']).to be_present
expect(hook.settings['assistant_id']).to be_present
end
it 'raises an error if the request to captain hub fails' do
stub_request(:post, ChatwootHub::CAPTAIN_ACCOUNTS_URL).to_return(
status: 500,
body: {
error: 'Failed to get captain settings'
}.to_json
)
expect do
create(:integrations_hook, app_id: 'captain', account: account, settings: settings)
end.to raise_error('Failed to get captain settings: {"error":"Failed to get captain settings"}')
end
end
end
end