Skip to content

Commit

Permalink
feat(hubspot): Add hubspot integration models (#2588)
Browse files Browse the repository at this point in the history
## Roadmap Task

👉  https://getlago.canny.io/feature-requests/p/integration-with-hubspot

## Context

Integration with HubSpot to import customers, subscriptions and
invoices.

## Description

Add `HubspotIntegration` model and modify existing models where
necessary to support hubspot.
  • Loading branch information
ivannovosad authored Sep 17, 2024
1 parent 61e723e commit 169ce17
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 1 deletion.
2 changes: 2 additions & 0 deletions app/models/integrations/base_integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ def self.integration_type(type)
'Integrations::AnrokIntegration'
when 'xero'
'Integrations::XeroIntegration'
when 'hubspot'
'Integrations::HubspotIntegration'
else
raise(NotImplementedError)
end
Expand Down
36 changes: 36 additions & 0 deletions app/models/integrations/hubspot_integration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

module Integrations
class HubspotIntegration < BaseIntegration
validates :connection_id, :private_app_token, :default_targeted_object, presence: true

settings_accessors :default_targeted_object, :sync_subscriptions, :sync_invoices
secrets_accessors :connection_id, :private_app_token

TARGETED_OBJECTS = %w[Companies Contacts]
end
end

# == Schema Information
#
# Table name: integrations
#
# id :uuid not null, primary key
# code :string not null
# name :string not null
# secrets :string
# settings :jsonb not null
# type :string not null
# created_at :datetime not null
# updated_at :datetime not null
# organization_id :uuid not null
#
# Indexes
#
# index_integrations_on_code_and_organization_id (code,organization_id) UNIQUE
# index_integrations_on_organization_id (organization_id)
#
# Foreign Keys
#
# fk_rails_... (organization_id => organizations.id)
#
3 changes: 2 additions & 1 deletion app/models/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Organization < ApplicationRecord
has_many :gocardless_payment_providers, class_name: 'PaymentProviders::GocardlessProvider'
has_many :adyen_payment_providers, class_name: 'PaymentProviders::AdyenProvider'

has_many :hubspot_integrations, class_name: 'Integrations::HubspotIntegration'
has_many :netsuite_integrations, class_name: 'Integrations::NetsuiteIntegration'
has_many :xero_integrations, class_name: 'Integrations::XeroIntegration'

Expand All @@ -50,7 +51,7 @@ class Organization < ApplicationRecord
:per_organization
].freeze

INTEGRATIONS = %w[netsuite okta anrok xero progressive_billing dunning].freeze
INTEGRATIONS = %w[netsuite okta anrok xero progressive_billing dunning hubspot].freeze
PREMIUM_INTEGRATIONS = INTEGRATIONS - %w[anrok]

enum document_numbering: DOCUMENT_NUMBERINGS
Expand Down
2 changes: 2 additions & 0 deletions schema.graphql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions spec/factories/integrations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,19 @@
{connection_id: SecureRandom.uuid}.to_json
end
end

factory :hubspot_integration, class: 'Integrations::HubspotIntegration' do
organization
type { 'Integrations::HubspotIntegration' }
code { 'hubspot' }
name { 'Hubspot Integration' }

settings do
{default_targeted_object: 'Companies', sync_subscriptions: true, sync_invoices: true}
end

secrets do
{connection_id: SecureRandom.uuid, private_app_token: SecureRandom.uuid}.to_json
end
end
end
38 changes: 38 additions & 0 deletions spec/models/integrations/base_integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,42 @@
it { expect(integration.get_from_settings(nil)).to be_nil }
it { expect(integration.get_from_settings('foo')).to be_nil }
end

describe '.integration_type' do
context 'when type is netsuite' do
it 'returns the correct class name' do
expect(described_class.integration_type('netsuite')).to eq('Integrations::NetsuiteIntegration')
end
end

context 'when type is okta' do
it 'returns the correct class name' do
expect(described_class.integration_type('okta')).to eq('Integrations::OktaIntegration')
end
end

context 'when type is anrok' do
it 'returns the correct class name' do
expect(described_class.integration_type('anrok')).to eq('Integrations::AnrokIntegration')
end
end

context 'when type is xero' do
it 'returns the correct class name' do
expect(described_class.integration_type('xero')).to eq('Integrations::XeroIntegration')
end
end

context 'when type is hubspot' do
it 'returns the correct class name' do
expect(described_class.integration_type('hubspot')).to eq('Integrations::HubspotIntegration')
end
end

context 'when type is unknown' do
it 'raises a NotImplementedError' do
expect { described_class.integration_type('unknown') }.to raise_error(NotImplementedError)
end
end
end
end
53 changes: 53 additions & 0 deletions spec/models/integrations/hubspot_integration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Integrations::HubspotIntegration, type: :model do
subject(:hubspot_integration) { build(:hubspot_integration) }

it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_presence_of(:connection_id) }
it { is_expected.to validate_presence_of(:private_app_token) }
it { is_expected.to validate_presence_of(:default_targeted_object) }

describe 'validations' do
it 'validates uniqueness of the code' do
expect(hubspot_integration).to validate_uniqueness_of(:code).scoped_to(:organization_id)
end
end

describe '#connection_id' do
it 'assigns and retrieve a secret pair' do
hubspot_integration.connection_id = 'connection_id'
expect(hubspot_integration.connection_id).to eq('connection_id')
end
end

describe '#private_app_token' do
it 'assigns and retrieve a secret pair' do
hubspot_integration.private_app_token = 'secret_token'
expect(hubspot_integration.private_app_token).to eq('secret_token')
end
end

describe '#default_targeted_object' do
it 'assigns and retrieve a setting' do
hubspot_integration.default_targeted_object = 'Companies'
expect(hubspot_integration.default_targeted_object).to eq('Companies')
end
end

describe '#sync_invoices' do
it 'assigns and retrieve a setting' do
hubspot_integration.sync_invoices = true
expect(hubspot_integration.sync_invoices).to eq(true)
end
end

describe '#sync_subscriptions' do
it 'assigns and retrieve a setting' do
hubspot_integration.sync_subscriptions = true
expect(hubspot_integration.sync_subscriptions).to eq(true)
end
end
end
1 change: 1 addition & 0 deletions spec/models/organization_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

it { is_expected.to have_many(:webhook_endpoints) }
it { is_expected.to have_many(:webhooks).through(:webhook_endpoints) }
it { is_expected.to have_many(:hubspot_integrations) }
it { is_expected.to have_many(:netsuite_integrations) }
it { is_expected.to have_many(:xero_integrations) }
it { is_expected.to have_many(:data_exports) }
Expand Down

0 comments on commit 169ce17

Please sign in to comment.