Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2022 refresh #6

Merged
merged 9 commits into from
Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .env.test

This file was deleted.

15 changes: 6 additions & 9 deletions .github/workflows/actions.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
name: tests

on:
pull_request:
branches:
- master
push:
branches:
- master

on: push
jobs:
test:
runs-on: ubuntu-latest
Expand All @@ -16,9 +8,11 @@ jobs:
ruby:
- '2.7.x'
- '3.0.x'
- '3.1.x'
active_support:
- 'active_support_6.0.x'
- 'active_support_6.1.x'
- 'active_support_7.0.x'
steps:
- name: Checkout
uses: actions/checkout@v1
Expand All @@ -34,4 +28,7 @@ jobs:
gem install bundler
bundle install --jobs 4 --retry 3
- name: Test
env:
HUBSPOT_PORTAL_ID: ${{ secrets.HUBSPOT_PORTAL_ID }}
HUBSPOT_HAPI_KEY: ${{ secrets.HUBSPOT_HAPI_KEY }}
run: RUBYOPT='-W:deprecated' bundle exec rspec
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ tmp
*.sublime-workspace

# Ignore local environment variables
/.env
/.env*

# Byebug history
.byebug_history
5 changes: 5 additions & 0 deletions gemfiles/active_support_7.0.x.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true
source 'https://rubygems.org'

gem "activesupport", "~> 7.0.3"
gemspec path: '../'
132 changes: 79 additions & 53 deletions lib/hubspot/association.rb
Original file line number Diff line number Diff line change
@@ -1,80 +1,106 @@
class Hubspot::Association
COMPANY_TO_CONTACT = 2
DEAL_TO_CONTACT = 3
CONTACT_TO_DEAL = 4
DEAL_TO_COMPANY = 5
COMPANY_TO_DEAL = 6
DEFINITION_TARGET_TO_CLASS = {
2 => Hubspot::Contact,
3 => Hubspot::Contact,
4 => Hubspot::Deal,
5 => Hubspot::Company,
6 => Hubspot::Deal
OBJECT_TARGET_TO_CLASS = {
"Contact" => Hubspot::Contact,
"Deal" => Hubspot::Deal,
"Company" => Hubspot::Company
}.freeze

BATCH_CREATE_PATH = '/crm-associations/v1/associations/create-batch'
BATCH_DELETE_PATH = '/crm-associations/v1/associations/delete-batch'
ASSOCIATIONS_PATH = '/crm-associations/v1/associations/:resource_id/HUBSPOT_DEFINED/:definition_id'
ASSOCIATION_DEFINITIONS = {
"Company" => {
"Contact" => 2,
"Deal" => 6,
"Company" => 13
},
"Deal" => {
"Company" => 5,
"Contact" => 3
},
"Contact" => {
"Deal" => 4
}
}.freeze

class << self
def create(from_id, to_id, definition_id)
batch_create([{ from_id: from_id, to_id: to_id, definition_id: definition_id }])
def create(object_type, object_id, to_object_type, to_object_id)
batch_create(object_type, to_object_type, [{from_id: object_id, to_id: to_object_id}])
end

# Make multiple associations in a single API call
# {https://developers.hubspot.com/docs/methods/crm-associations/batch-associate-objects}
# {https://developers.hubspot.com/docs/api/crm/associations}
# usage:
# Hubspot::Association.batch_create([{ from_id: 1, to_id: 2, definition_id: Hubspot::Association::COMPANY_TO_CONTACT }])
def batch_create(associations)
request = associations.map { |assocation| build_association_body(assocation) }
Hubspot::Connection.put_json(BATCH_CREATE_PATH, params: { no_parse: true }, body: request).success?
# Hubspot::Association.batch_create("Company", "Contact", [{from_id: 1, to_id: 2}]])
def batch_create(from_object_type, to_object_type, associations)
definition_id = ASSOCIATION_DEFINITIONS.dig(from_object_type, to_object_type)
request = { inputs: associations.map { |assocation| build_create_association_body(assocation, definition_id) } }
response = Hubspot::Connection.post_json("/crm/v4/associations/#{from_object_type}/#{to_object_type}/batch/create", params: { no_parse: true }, body: request)
return false if response.parsed_response["errors"].present?

response.success?
end

def delete(from_id, to_id, definition_id)
batch_delete([{from_id: from_id, to_id: to_id, definition_id: definition_id}])
def delete(object_type, object_id, to_object_type, to_object_id)
batch_delete(object_type, to_object_type, [{from_id: object_id, to_id: to_object_id}])
end

# Remove multiple associations in a single API call
# {https://developers.hubspot.com/docs/methods/crm-associations/batch-delete-associations}
# {https://developers.hubspot.com/docs/api/crm/associations}
# usage:
# Hubspot::Association.batch_delete([{ from_id: 1, to_id: 2, definition_id: Hubspot::Association::COMPANY_TO_CONTACT }])
def batch_delete(associations)
request = associations.map { |assocation| build_association_body(assocation) }
Hubspot::Connection.put_json(BATCH_DELETE_PATH, params: { no_parse: true }, body: request).success?
# Hubspot::Association.batch_delete("Company", "Contact", [{ from_id: 1, to_id: 2}])
def batch_delete(from_object_type, to_object_type, associations)
request = { inputs: build_delete_associations_body(associations) }
Hubspot::Connection.post_json("/crm/v4/associations/#{from_object_type}/#{to_object_type}/batch/archive", params: { no_parse: true }, body: request).success?
end

# Retrieve all associated resources given a source (resource_id) and a kind (definition_id)
# Example: if resource_id is a deal, using DEAL_TO_CONTACT will find every contact associated with the deal
# {https://developers.hubspot.com/docs/methods/crm-associations/get-associations}
# Warning: it will make N+M queries, where
# N is the number of PagedCollection requests necessary to get all ids,
# and M is the number of results, each resulting in a find
# usage:
# Hubspot::Association.all(42, Hubspot::Association::DEAL_TO_CONTACT)
def all(resource_id, definition_id)
opts = { resource_id: resource_id, definition_id: definition_id }
klass = DEFINITION_TARGET_TO_CLASS[definition_id]
raise(Hubspot::InvalidParams, 'Definition not supported') unless klass.present?
# Retrieve all associated resources given a source (object_type and object_id) and a relation type (to_object_type)
# {https://developers.hubspot.com/docs/api/crm/associations}
# Warning: it will return at most 1000 objects and make up to 1001 queries
# Hubspot::Association.all("Company", 42, "Contact")
def all(object_type, object_id, to_object_type)
klass = OBJECT_TARGET_TO_CLASS[to_object_type]
raise(Hubspot::InvalidParams, 'Object type not supported') unless klass.present?

collection = Hubspot::PagedCollection.new(opts) do |options, offset, limit|
params = options.merge(offset: offset, limit: limit)
response = Hubspot::Connection.get_json(ASSOCIATIONS_PATH, params)

resources = response['results'].map { |result| klass.find(result) }
[resources, response['offset'], response['has-more']]
end
collection.resources
response = Hubspot::Connection.get_json("/crm/v4/objects/#{object_type}/#{object_id}/associations/#{to_object_type}", {})
response['results'].map { |result| klass.find(result["toObjectId"]) }
end

private

def build_association_body(assocation)
def build_create_association_body(association, definition_id)
{
fromObjectId: assocation[:from_id],
toObjectId: assocation[:to_id],
category: 'HUBSPOT_DEFINED',
definitionId: assocation[:definition_id]
from: {
id: association[:from_id]
},
to: {
id: association[:to_id]
},
types: [
{
associationCategory: "HUBSPOT_DEFINED",
associationTypeId: definition_id
}
]
}
end

def build_delete_associations_body(associations)
normalized_associations = associations.inject({}) do |memo, association|
memo[association[:from_id]] ||= []
memo[association[:from_id]] << association[:to_id]
memo
end

normalized_associations.map do |from_id, to_ids|
{
from: {
id: from_id
},
to: to_ids.map do |to_id|
{
id: to_id
}
end
}
end
end
end
end
4 changes: 2 additions & 2 deletions lib/hubspot/company.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ def recently_modified(opts = {})
end

def add_contact(id, contact_id)
Hubspot::Association.create(id, contact_id, Hubspot::Association::COMPANY_TO_CONTACT)
Hubspot::Association.create("Company", id, "Contact", contact_id)
end

def remove_contact(id, contact_id)
Hubspot::Association.delete(id, contact_id, Hubspot::Association::COMPANY_TO_CONTACT)
Hubspot::Association.delete("Company", id, "Contact", contact_id)
end

def batch_update(companies, opts = {})
Expand Down
26 changes: 12 additions & 14 deletions lib/hubspot/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def get_json(path, opts)
url = generate_url(path, opts)
response = get(url, format: :json, read_timeout: read_timeout(opts), open_timeout: open_timeout(opts))
log_request_and_response url, response
handle_response(response)
handle_response(response).parsed_response
end

def post_json(path, opts)
Expand All @@ -24,9 +24,9 @@ def post_json(path, opts)
)

log_request_and_response url, response, opts[:body]
raise(Hubspot::RequestError.new(response)) unless response.success?

no_parse ? response : response.parsed_response
handle_response(response).yield_self do |r|
no_parse ? r : r.parsed_response
end
end

def put_json(path, options)
Expand All @@ -43,17 +43,16 @@ def put_json(path, options)
)

log_request_and_response(url, response, options[:body])
raise(Hubspot::RequestError.new(response)) unless response.success?

no_parse ? response : response.parsed_response
handle_response(response).yield_self do |r|
no_parse ? r : r.parsed_response
end
end

def delete_json(path, opts)
url = generate_url(path, opts)
response = delete(url, format: :json, read_timeout: read_timeout(opts), open_timeout: open_timeout(opts))
log_request_and_response url, response, opts[:body]
raise(Hubspot::RequestError.new(response)) unless response.success?
response
handle_response(response)
end

protected
Expand All @@ -67,11 +66,10 @@ def open_timeout(opts = {})
end

def handle_response(response)
if response.success?
response.parsed_response
else
raise(Hubspot::RequestError.new(response))
end
return response if response.success?

raise(Hubspot::NotFoundError.new(response)) if response.not_found?
raise(Hubspot::RequestError.new(response))
end

def log_request_and_response(uri, response, body=nil)
Expand Down
7 changes: 0 additions & 7 deletions lib/hubspot/contact_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ class ContactList
RECENT_CONTACTS_PATH = LIST_PATH + '/contacts/recent'
ADD_CONTACT_PATH = LIST_PATH + '/add'
REMOVE_CONTACT_PATH = LIST_PATH + '/remove'
REFRESH_PATH = LIST_PATH + '/refresh'

class << self
# {http://developers.hubspot.com/docs/methods/lists/create_list}
Expand Down Expand Up @@ -92,12 +91,6 @@ def contacts(opts={})
end
end

# {http://developers.hubspot.com/docs/methods/lists/refresh_list}
def refresh
response = Hubspot::Connection.post_json(REFRESH_PATH, params: { list_id: @id, no_parse: true }, body: {})
Paul-Yves marked this conversation as resolved.
Show resolved Hide resolved
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

faut virer la constante non ?
Pourquoi on dégage ça en fait ? 🤔 Bon je trouve pas de doc j'imagine que ça n'existe pas.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(tiens ça fait répétition avec le commentaire de Paul-Yves 😅 )

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oui l'endpoint n'existe plus à priori ...

response.code == 204
end

# {http://developers.hubspot.com/docs/methods/lists/add_contact_to_list}
def add(contacts)
contact_ids = [contacts].flatten.uniq.compact.map(&:id)
Expand Down
48 changes: 34 additions & 14 deletions lib/hubspot/deal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,27 +64,47 @@ def update!(id, properties = {})
# Usage
# Hubspot::Deal.associate!(45146940, [32], [52])
def associate!(deal_id, company_ids=[], vids=[])
associations = company_ids.map do |id|
{ from_id: deal_id, to_id: id, definition_id: Hubspot::Association::DEAL_TO_COMPANY }
company_associations = associations = company_ids.map do |id|
{ from_id: deal_id, to_id: id }
end
associations += vids.map do |id|
{ from_id: deal_id, to_id: id, definition_id: Hubspot::Association::DEAL_TO_CONTACT }

contact_associations = vids.map do |id|
{ from_id: deal_id, to_id: id}
end

results = []
if company_associations.any?
results << HubSpot::Association.batch_create("Deal", "Company", company_associations)
end
Hubspot::Association.batch_create(associations)
if contact_associations.any?
results << HubSpot::Association.batch_create("Deal", "Contact", contact_associations)
end

results.all?
end

# Didssociate a deal with a contact or company
# {https://developers.hubspot.com/docs/methods/deals/delete_association}
# Usage
# Hubspot::Deal.dissociate!(45146940, [32], [52])
def dissociate!(deal_id, company_ids=[], vids=[])
associations = company_ids.map do |id|
{ from_id: deal_id, to_id: id, definition_id: Hubspot::Association::DEAL_TO_COMPANY }
company_associations = company_ids.map do |id|
{ from_id: deal_id, to_id: id }
end
associations += vids.map do |id|
{ from_id: deal_id, to_id: id, definition_id: Hubspot::Association::DEAL_TO_CONTACT }

contact_associations = vids.map do |id|
{ from_id: deal_id, to_id: id }
end

results = []
if company_associations.any?
results << HubSpot::Association.batch_delete("Deal", "Company", company_associations)
end
Hubspot::Association.batch_delete(associations)
if contact_associations.any?
results << HubSpot::Association.batch_delete("Deal", "Contact", contact_associations)
end

results.all?
end

def find(deal_id)
Expand Down Expand Up @@ -134,12 +154,12 @@ def find_by_contact(contact)
# @param object [Hubspot::Contact || Hubspot::Company] a contact or company
# @return [Array] Array of Hubspot::Deal records
def find_by_association(object)
definition = case object
when Hubspot::Company then Hubspot::Association::COMPANY_TO_DEAL
when Hubspot::Contact then Hubspot::Association::CONTACT_TO_DEAL
to_object_type = case object
when Hubspot::Company then "Company"
when Hubspot::Contact then "Contact"
else raise(Hubspot::InvalidParams, 'Instance type not supported')
end
Hubspot::Association.all(object.id, definition)
Hubspot::Association.all(to_object_type, object.id, "Deal")
end
end

Expand Down
2 changes: 2 additions & 0 deletions lib/hubspot/exceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ def initialize(response, message=nil)
end
end

class NotFoundError < RequestError; end

class ConfigurationError < StandardError; end
class MissingInterpolation < StandardError; end
class ContactExistsError < RequestError; end
Expand Down
2 changes: 1 addition & 1 deletion lib/hubspot/properties.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def valid_property_params(params)

def valid_group_params(params)
return {} if params.blank?
result = params.slice(*PROPERTY_SPECS[:group_field_names])
result = params.with_indifferent_access.slice(*PROPERTY_SPECS[:group_field_names])
result['properties'] = valid_property_params(result['properties']) unless result['properties'].blank?
result
end
Expand Down
Loading