mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 12:08:01 +00:00
feat: Provision captain accounts automatically (#10168)
- Provision accounts on Chatwoot cloud automatically if the feature is enabled
This commit is contained in:
@@ -41,7 +41,7 @@ class Integrations::App
|
|||||||
when 'linear'
|
when 'linear'
|
||||||
account.feature_enabled?('linear_integration')
|
account.feature_enabled?('linear_integration')
|
||||||
when 'captain'
|
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
|
else
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ class Integrations::Hook < ApplicationRecord
|
|||||||
include Reauthorizable
|
include Reauthorizable
|
||||||
|
|
||||||
attr_readonly :app_id, :account_id, :inbox_id, :hook_type
|
attr_readonly :app_id, :account_id, :inbox_id, :hook_type
|
||||||
|
before_validation :ensure_captain_config_present, on: :create
|
||||||
before_validation :ensure_hook_type
|
before_validation :ensure_hook_type
|
||||||
|
|
||||||
validates :account_id, presence: true
|
validates :account_id, presence: true
|
||||||
validates :app_id, presence: true
|
validates :app_id, presence: true
|
||||||
validates :inbox_id, presence: true, if: -> { hook_type == 'inbox' }
|
validates :inbox_id, presence: true, if: -> { hook_type == 'inbox' }
|
||||||
@@ -62,6 +64,30 @@ class Integrations::Hook < ApplicationRecord
|
|||||||
|
|
||||||
private
|
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
|
def ensure_hook_type
|
||||||
self.hook_type = app.params[:hook_type] if app.present?
|
self.hook_type = app.params[:hook_type] if app.present?
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -28,30 +28,6 @@ captain:
|
|||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
}
|
}
|
||||||
settings_form_schema: [
|
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",
|
"label": "Inbox Ids",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# TODO: lets use HTTParty instead of RestClient
|
||||||
class ChatwootHub
|
class ChatwootHub
|
||||||
BASE_URL = ENV.fetch('CHATWOOT_HUB_URL', 'https://hub.2.chatwoot.com')
|
BASE_URL = ENV.fetch('CHATWOOT_HUB_URL', 'https://hub.2.chatwoot.com')
|
||||||
PING_URL = "#{BASE_URL}/ping".freeze
|
PING_URL = "#{BASE_URL}/ping".freeze
|
||||||
@@ -5,6 +6,7 @@ class ChatwootHub
|
|||||||
PUSH_NOTIFICATION_URL = "#{BASE_URL}/send_push".freeze
|
PUSH_NOTIFICATION_URL = "#{BASE_URL}/send_push".freeze
|
||||||
EVENTS_URL = "#{BASE_URL}/events".freeze
|
EVENTS_URL = "#{BASE_URL}/events".freeze
|
||||||
BILLING_URL = "#{BASE_URL}/billing".freeze
|
BILLING_URL = "#{BASE_URL}/billing".freeze
|
||||||
|
CAPTAIN_ACCOUNTS_URL = "#{BASE_URL}/instance_captain_accounts".freeze
|
||||||
|
|
||||||
def self.installation_identifier
|
def self.installation_identifier
|
||||||
identifier = InstallationConfig.find_by(name: 'INSTALLATION_IDENTIFIER')&.value
|
identifier = InstallationConfig.find_by(name: 'INSTALLATION_IDENTIFIER')&.value
|
||||||
@@ -44,16 +46,20 @@ class ChatwootHub
|
|||||||
|
|
||||||
def self.instance_metrics
|
def self.instance_metrics
|
||||||
{
|
{
|
||||||
accounts_count: Account.count,
|
accounts_count: fetch_count(Account),
|
||||||
users_count: User.count,
|
users_count: fetch_count(User),
|
||||||
inboxes_count: Inbox.count,
|
inboxes_count: fetch_count(Inbox),
|
||||||
conversations_count: Conversation.count,
|
conversations_count: fetch_count(Conversation),
|
||||||
incoming_messages_count: Message.incoming.count,
|
incoming_messages_count: fetch_count(Message.incoming),
|
||||||
outgoing_messages_count: Message.outgoing.count,
|
outgoing_messages_count: fetch_count(Message.outgoing),
|
||||||
additional_information: {}
|
additional_information: {}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.fetch_count(model)
|
||||||
|
model.last&.id || 0
|
||||||
|
end
|
||||||
|
|
||||||
def self.sync_with_hub
|
def self.sync_with_hub
|
||||||
begin
|
begin
|
||||||
info = instance_config
|
info = instance_config
|
||||||
@@ -86,6 +92,17 @@ class ChatwootHub
|
|||||||
ChatwootExceptionTracker.new(e).capture_exception
|
ChatwootExceptionTracker.new(e).capture_exception
|
||||||
end
|
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)
|
def self.emit_event(event_name, event_data)
|
||||||
return if ENV['DISABLE_TELEMETRY']
|
return if ENV['DISABLE_TELEMETRY']
|
||||||
|
|
||||||
|
|||||||
@@ -69,4 +69,24 @@ describe ChatwootHub do
|
|||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@@ -66,6 +66,28 @@ RSpec.describe Integrations::App do
|
|||||||
end
|
end
|
||||||
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
|
context 'when other apps are queried' do
|
||||||
let(:app_name) { 'webhook' }
|
let(:app_name) { 'webhook' }
|
||||||
|
|
||||||
|
|||||||
@@ -51,4 +51,48 @@ RSpec.describe Integrations::Hook do
|
|||||||
expect(openai_double).to have_received(:perform)
|
expect(openai_double).to have_received(:perform)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user