diff --git a/app/models/integrations/app.rb b/app/models/integrations/app.rb index 9629bdd84..5f83eec03 100644 --- a/app/models/integrations/app.rb +++ b/app/models/integrations/app.rb @@ -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 diff --git a/app/models/integrations/hook.rb b/app/models/integrations/hook.rb index f523af4a5..9bc64803a 100644 --- a/app/models/integrations/hook.rb +++ b/app/models/integrations/hook.rb @@ -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 diff --git a/config/integration/apps.yml b/config/integration/apps.yml index 24c19595c..9b722927d 100644 --- a/config/integration/apps.yml +++ b/config/integration/apps.yml @@ -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", diff --git a/lib/chatwoot_hub.rb b/lib/chatwoot_hub.rb index 22addcacb..0d99becd3 100644 --- a/lib/chatwoot_hub.rb +++ b/lib/chatwoot_hub.rb @@ -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'] diff --git a/spec/lib/chatwoot_hub_spec.rb b/spec/lib/chatwoot_hub_spec.rb index b5e0da4dd..0f53971af 100644 --- a/spec/lib/chatwoot_hub_spec.rb +++ b/spec/lib/chatwoot_hub_spec.rb @@ -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 diff --git a/spec/models/integrations/app_spec.rb b/spec/models/integrations/app_spec.rb index b9652ff89..f32823f6c 100644 --- a/spec/models/integrations/app_spec.rb +++ b/spec/models/integrations/app_spec.rb @@ -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' } diff --git a/spec/models/integrations/hook_spec.rb b/spec/models/integrations/hook_spec.rb index aecb2981c..1d68c4a50 100644 --- a/spec/models/integrations/hook_spec.rb +++ b/spec/models/integrations/hook_spec.rb @@ -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