hubspot-api-ruby 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce2ef58e430507f2d3573dab50231ebdc9d5af9479d678d1f2a063d350e4d10f
4
- data.tar.gz: 33fc9cbba6bdb66a30c6477a4fda5c31284cbc277993ec1ede750a20c08ab8a2
3
+ metadata.gz: 1c10bc271fecc20b749272010f29ca4d5e667872c39ed5ecd68a2cfd564b8311
4
+ data.tar.gz: c4b4459027deb4700a09876fd9d4e46a450753f26c83e982383f15121379571f
5
5
  SHA512:
6
- metadata.gz: '06921a719c97ddf74779098da5dfc3b7160716f96a998df6454394f0fc1261b7a7ef104f139d926921a455081f921d4ba07846612d34e3c7b74ac8a1c1358108'
7
- data.tar.gz: 48ee5aa85a01c9e83e87d7512d4a4471489092f50fea603fcea2d7e941d3f70461e4b43f6eeb696d5971776c31f9e60eb97631516ce68e3ffbdc43877c762f97
6
+ metadata.gz: a7d72b4aef781a7ba1857859d1572e7df94ba10635ebd13687012e9b2895bc1d0b7e16e19b6ce48cbc5fd736137eab144d7bed289a5c70607a576421404a779e
7
+ data.tar.gz: 160173f51c195de7214027940d471ee999fdf7df351a05f36654ba669aa50091c1e3d16cd7e0da1d6fe355398bedeec60777a92615cbd9bd63ce84b07924172f
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "hubspot-api-ruby"
3
- s.version = "0.16.0"
3
+ s.version = "0.17.0"
4
4
  s.require_paths = ["lib"]
5
5
  s.authors = ["Jonathan"]
6
6
  s.email = ["jonathan@hoggo.com"]
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
15
15
  "changelog_uri" => "https://github.com/captaincontrat/hubspot-api-ruby/blob/master/History.md"
16
16
  }
17
17
 
18
- s.required_ruby_version = ">= 2.7"
18
+ s.required_ruby_version = ">= 3.1"
19
19
 
20
20
  # Add runtime dependencies here
21
21
  s.add_runtime_dependency "activesupport", ">= 4.2.2"
@@ -17,6 +17,18 @@ class Hubspot::Association
17
17
  },
18
18
  "Contact" => {
19
19
  "Deal" => 4
20
+ },
21
+ "Ticket" => {
22
+ "Contact" => 16,
23
+ "Deal" => 28,
24
+ "Company" => 339,
25
+ "Task" => 229
26
+ },
27
+ "Task" => {
28
+ "Contact" => 204,
29
+ "Deal" => 216,
30
+ "Company" => 192,
31
+ "Ticket" => 230
20
32
  }
21
33
  }.freeze
22
34
 
@@ -10,30 +10,33 @@ module Hubspot
10
10
  handle_response(response).parsed_response
11
11
  end
12
12
 
13
- def post_json(path, opts)
14
- no_parse = opts[:params].delete(:no_parse) { false }
13
+ def post_json(path, options)
14
+ modification_query(:post, path, options)
15
+ end
15
16
 
16
- url = generate_url(path, opts[:params])
17
- response = post(
18
- url,
19
- body: opts[:body].to_json,
20
- headers: { 'Content-Type' => 'application/json' },
21
- format: :json,
22
- read_timeout: read_timeout(opts),
23
- open_timeout: open_timeout(opts)
24
- )
17
+ def put_json(path, options)
18
+ modification_query(:put, path, options)
19
+ end
20
+
21
+ def patch_json(path, options)
22
+ modification_query(:patch, path, options)
23
+ end
25
24
 
25
+ def delete_json(path, opts)
26
+ url = generate_url(path, opts)
27
+ response = delete(url, format: :json, read_timeout: read_timeout(opts), open_timeout: open_timeout(opts))
26
28
  log_request_and_response url, response, opts[:body]
27
- handle_response(response).yield_self do |r|
28
- no_parse ? r : r.parsed_response
29
- end
29
+ handle_response(response)
30
30
  end
31
31
 
32
- def put_json(path, options)
32
+ protected
33
+
34
+ def modification_query(verb, path, options)
33
35
  no_parse = options[:params].delete(:no_parse) { false }
34
36
  url = generate_url(path, options[:params])
35
37
 
36
- response = put(
38
+ response = send(
39
+ verb,
37
40
  url,
38
41
  body: options[:body].to_json,
39
42
  headers: { "Content-Type" => "application/json" },
@@ -48,15 +51,6 @@ module Hubspot
48
51
  end
49
52
  end
50
53
 
51
- def delete_json(path, opts)
52
- url = generate_url(path, opts)
53
- response = delete(url, format: :json, read_timeout: read_timeout(opts), open_timeout: open_timeout(opts))
54
- log_request_and_response url, response, opts[:body]
55
- handle_response(response)
56
- end
57
-
58
- protected
59
-
60
54
  def read_timeout(opts = {})
61
55
  opts.delete(:read_timeout) || Hubspot::Config.read_timeout
62
56
  end
@@ -0,0 +1,46 @@
1
+ require 'hubspot/utils'
2
+
3
+ module Hubspot
4
+ #
5
+ # HubSpot Tasks API
6
+ #
7
+ # {https://developers.hubspot.com/beta-docs/guides/api/crm/engagements/tasks}
8
+ #
9
+ class Task
10
+ TASKS_PATH = '/crm/v3/objects/tasks'
11
+ TASK_PATH = '/crm/v3/objects/tasks/:task_id'
12
+ DEFAULT_TASK_FIELDS = 'hs_timestamp,hs_task_body,hubspot_owner_id,hs_task_subject,hs_task_status,hs_task_priority,'\
13
+ 'hs_task_type,hs_task_reminders'
14
+
15
+ attr_reader :properties, :id
16
+
17
+ def initialize(response_hash)
18
+ @id = response_hash['id']
19
+ @properties = response_hash['properties'].deep_symbolize_keys
20
+ end
21
+
22
+ class << self
23
+ def create!(params = {}, ticket_id: nil)
24
+ associations_hash = { 'associations' => [] }
25
+ if ticket_id.present?
26
+ associations_hash['associations'] << {
27
+ "to": { "id": ticket_id },
28
+ "types": [{ "associationCategory": 'HUBSPOT_DEFINED',
29
+ "associationTypeId": Hubspot::Association::ASSOCIATION_DEFINITIONS['Task']['Ticket'] }]
30
+ }
31
+ end
32
+ properties = { hs_task_status: 'NOT_STARTED', hs_task_type: 'TODO' }.merge(params)
33
+ post_data = associations_hash.merge({ properties: properties })
34
+
35
+ response = Hubspot::Connection.post_json(TASKS_PATH, params: {}, body: post_data)
36
+ new(response)
37
+ end
38
+
39
+ def find(task_id, properties = DEFAULT_TASK_FIELDS)
40
+ response = Hubspot::Connection.get_json(TASK_PATH, { task_id: task_id,
41
+ properties: properties })
42
+ new(response)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,62 @@
1
+ require 'hubspot/utils'
2
+
3
+ module Hubspot
4
+ #
5
+ # HubSpot Tickets API
6
+ #
7
+ # {https://developers.hubspot.com/beta-docs/guides/api/crm/objects/tickets}
8
+ #
9
+ class Ticket
10
+ TICKETS_PATH = '/crm/v3/objects/tickets'
11
+ TICKET_PATH = '/crm/v3/objects/tickets/:ticket_id'
12
+
13
+ attr_reader :properties, :id
14
+
15
+ def initialize(response_hash)
16
+ @id = response_hash['id']
17
+ @properties = response_hash['properties'].deep_symbolize_keys
18
+ end
19
+
20
+ class << self
21
+ def create!(params = {}, contact_id: nil, company_id: nil, deal_id: nil)
22
+ associations_hash = { 'associations' => [] }
23
+ if contact_id.present?
24
+ associations_hash['associations'] << {
25
+ "to": { "id": contact_id },
26
+ "types": [{ "associationCategory": 'HUBSPOT_DEFINED',
27
+ "associationTypeId": Hubspot::Association::ASSOCIATION_DEFINITIONS['Ticket']['Contact'] }]
28
+ }
29
+ end
30
+ if company_id.present?
31
+ associations_hash['associations'] << {
32
+ "to": { "id": company_id },
33
+ "types": [{ "associationCategory": 'HUBSPOT_DEFINED',
34
+ "associationTypeId": Hubspot::Association::ASSOCIATION_DEFINITIONS['Ticket']['Company'] }]
35
+ }
36
+ end
37
+ if deal_id.present?
38
+ associations_hash['associations'] << {
39
+ "to": { "id": deal_id },
40
+ "types": [{ "associationCategory": 'HUBSPOT_DEFINED',
41
+ "associationTypeId": Hubspot::Association::ASSOCIATION_DEFINITIONS['Ticket']['Deal'] }]
42
+ }
43
+ end
44
+ post_data = associations_hash.merge({ properties: params })
45
+
46
+ response = Hubspot::Connection.post_json(TICKETS_PATH, params: {}, body: post_data)
47
+ new(response)
48
+ end
49
+
50
+ def update!(id, properties = {})
51
+ request = { properties: properties }
52
+ response = Hubspot::Connection.patch_json(TICKET_PATH, params: { ticket_id: id }, body: request)
53
+ new(response)
54
+ end
55
+
56
+ def find(ticket_id)
57
+ response = Hubspot::Connection.get_json(TICKET_PATH, { ticket_id: ticket_id })
58
+ new(response)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,10 @@
1
+ module Hubspot
2
+ class TicketProperties < Properties
3
+ CREATE_PROPERTY_PATH = '/crm/v3/properties/ticket'
4
+ class << self
5
+ def create!(params = {})
6
+ superclass.create!(CREATE_PROPERTY_PATH, params)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -29,6 +29,9 @@ require 'hubspot/subscription'
29
29
  require 'hubspot/oauth'
30
30
  require 'hubspot/file'
31
31
  require 'hubspot/meeting'
32
+ require 'hubspot/ticket'
33
+ require 'hubspot/ticket_properties'
34
+ require 'hubspot/task'
32
35
 
33
36
  module Hubspot
34
37
  def self.configure(config={})
@@ -5,6 +5,6 @@ FactoryBot.define do
5
5
 
6
6
  firstname { Faker::Name.first_name }
7
7
  lastname { Faker::Name.last_name }
8
- email { Faker::Internet.safe_email(name: "#{Time.new.to_i.to_s[-5..-1]}#{(0..3).map { (65 + rand(26)).chr }.join}") }
8
+ email { Faker::Internet.email(name: "#{Time.new.to_i.to_s[-5..-1]}#{(0..3).map { (65 + rand(26)).chr }.join}") }
9
9
  end
10
10
  end
@@ -84,6 +84,56 @@ describe Hubspot::Connection do
84
84
  end
85
85
  end
86
86
 
87
+ describe ".patch_json" do
88
+ it "issues a PATCH request and returns the parsed body" do
89
+ path = "/some/path"
90
+ update_options = { params: {}, body: {} }
91
+
92
+ stub_request(:patch, "https://api.hubapi.com/some/path").to_return(status: 200, body: JSON.generate(vid: 123))
93
+
94
+ response = Hubspot::Connection.patch_json(path, update_options)
95
+
96
+ assert_requested(
97
+ :patch,
98
+ "https://api.hubapi.com/some/path",
99
+ {
100
+ body: "{}",
101
+ headers: { "Content-Type" => "application/json" },
102
+ }
103
+ )
104
+ expect(response).to eq({ "vid" => 123 })
105
+ end
106
+
107
+ it "logs information about the request and response" do
108
+ path = "/some/path"
109
+ update_options = { params: {}, body: {} }
110
+
111
+ logger = stub_logger
112
+
113
+ stub_request(:patch, "https://api.hubapi.com/some/path").to_return(status: 200,
114
+ body: JSON.generate("response body"))
115
+
116
+ Hubspot::Connection.patch_json(path, update_options)
117
+
118
+ expect(logger).to have_received(:info).with(<<~MSG)
119
+ Hubspot: https://api.hubapi.com/some/path.
120
+ Body: {}.
121
+ Response: 200 "response body"
122
+ MSG
123
+ end
124
+
125
+ it "raises when the request fails" do
126
+ path = "/some/path"
127
+ update_options = { params: {}, body: {} }
128
+
129
+ stub_request(:patch, "https://api.hubapi.com/some/path").to_return(status: 401)
130
+
131
+ expect {
132
+ Hubspot::Connection.patch_json(path, update_options)
133
+ }.to raise_error(Hubspot::RequestError)
134
+ end
135
+ end
136
+
87
137
  context 'private methods' do
88
138
  describe ".generate_url" do
89
139
  let(:path) { "/test/:email/profile" }
@@ -42,7 +42,7 @@ RSpec.describe Hubspot::Contact do
42
42
  context 'without properties' do
43
43
  cassette
44
44
 
45
- let(:email) { Faker::Internet.safe_email(name: "#{(0..3).map { (65 + rand(26)).chr }.join}#{Time.new.to_i.to_s[-5..-1]}") }
45
+ let(:email) { Faker::Internet.email(name: "#{(0..3).map { (65 + rand(26)).chr }.join}#{Time.new.to_i.to_s[-5..-1]}") }
46
46
  subject { described_class.create email }
47
47
 
48
48
  it 'creates a new contact' do
@@ -54,7 +54,7 @@ RSpec.describe Hubspot::Contact do
54
54
  context 'with properties' do
55
55
  cassette
56
56
 
57
- let(:email) { Faker::Internet.safe_email(name: "#{(0..3).map { (65 + rand(26)).chr }.join}#{Time.new.to_i.to_s[-5..-1]}") }
57
+ let(:email) { Faker::Internet.email(name: "#{(0..3).map { (65 + rand(26)).chr }.join}#{Time.new.to_i.to_s[-5..-1]}") }
58
58
  let(:firstname) { "Allison" }
59
59
  let(:properties) { { firstname: firstname } }
60
60
 
@@ -0,0 +1,44 @@
1
+ RSpec.describe Hubspot::Task do
2
+ describe 'create!' do
3
+ subject(:new_task) do
4
+ params = {
5
+ hs_task_body: 'i am a task',
6
+ hs_task_subject: 'title of task',
7
+ hs_timestamp: DateTime.now.strftime('%Q')
8
+ }
9
+ described_class.create!(params, ticket_id: 16_174_569_112)
10
+ end
11
+
12
+ it 'creates a new task with valid properties' do
13
+ VCR.use_cassette 'task' do
14
+ expect(new_task.id).not_to be_nil
15
+ expect(new_task.properties[:hs_task_status]).to eq('NOT_STARTED')
16
+ expect(new_task.properties[:hs_task_subject]).to eq('title of task')
17
+ expect(new_task.properties[:hs_body_preview]).to eq('i am a task')
18
+ end
19
+ end
20
+ end
21
+
22
+ describe 'find' do
23
+ let(:task_id) { 64_075_014_222 }
24
+
25
+ subject(:existing_task) { described_class.find(task_id, 'hs_task_subject,hs_task_status') }
26
+
27
+ it 'gets existing task' do
28
+ VCR.use_cassette 'task_find' do
29
+ expect(existing_task.id).not_to be_nil
30
+ expect(existing_task.properties[:hs_task_subject]).to eq('title of task')
31
+ end
32
+ end
33
+
34
+ context 'when task does not exist' do
35
+ let(:task_id) { 996_174_569_112 }
36
+
37
+ it 'returns nil' do
38
+ VCR.use_cassette 'task_find' do
39
+ expect { existing_task }.to raise_error Hubspot::NotFoundError
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,41 @@
1
+ describe Hubspot::TicketProperties do
2
+ describe '.create' do
3
+ context 'with all valid parameters' do
4
+ let(:params) do
5
+ {
6
+ 'name' => 'my_new_property',
7
+ 'label' => 'This is my new property',
8
+ 'description' => 'How much money do you have?',
9
+ 'groupName' => 'ticketinformation',
10
+ 'type' => 'string',
11
+ 'fieldType' => 'text',
12
+ 'hidden' => false,
13
+ 'deleted' => false,
14
+ 'displayOrder' => 0,
15
+ 'formField' => true,
16
+ 'readOnlyValue' => false,
17
+ 'readOnlyDefinition' => false,
18
+ 'mutableDefinitionNotDeletable' => false,
19
+ 'calculated' => false,
20
+ 'externalOptions' => false,
21
+ 'displayMode' => 'current_value'
22
+ }
23
+ end
24
+
25
+ it 'should return the valid parameters' do
26
+ VCR.use_cassette 'ticket_create_property' do
27
+ response = Hubspot::TicketProperties.create!(params)
28
+ expect(Hubspot::TicketProperties.same?(params, response.compact.except('options'))).to be true
29
+ end
30
+ end
31
+ end
32
+
33
+ context 'with invalid parameters' do
34
+ it 'should return nil' do
35
+ VCR.use_cassette 'ticket_fail_to_create_property' do
36
+ expect(Hubspot::TicketProperties.create!({})).to be(nil)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,63 @@
1
+ RSpec.describe Hubspot::Ticket do
2
+ describe 'create!' do
3
+ subject(:new_ticket) do
4
+ params = {
5
+ hs_pipeline: '0',
6
+ hs_pipeline_stage: '1',
7
+ hs_ticket_priority: 'MEDIUM',
8
+ subject: 'test ticket'
9
+ }
10
+ described_class.create!(params, contact_id: 75_761_595_194, company_id: 25_571_271_600, deal_id: 28_806_796_888)
11
+ end
12
+
13
+ it 'creates a new ticket with valid properties' do
14
+ VCR.use_cassette 'ticket' do
15
+ expect(new_ticket.id).not_to be_nil
16
+ expect(new_ticket.properties[:subject]).to eq('test ticket')
17
+ end
18
+ end
19
+ end
20
+
21
+ describe 'find' do
22
+ let(:ticket_id) { 16_174_569_112 }
23
+
24
+ subject(:existing_ticket) { described_class.find(ticket_id) }
25
+
26
+ it 'gets existing ticket' do
27
+ VCR.use_cassette 'ticket_find' do
28
+ expect(existing_ticket.id).not_to be_nil
29
+ expect(existing_ticket.properties[:subject]).to eq('test ticket')
30
+ end
31
+ end
32
+
33
+ context 'when ticket does not exist' do
34
+ let(:ticket_id) { 996_174_569_112 }
35
+
36
+ it 'returns nil' do
37
+ VCR.use_cassette 'ticket_find' do
38
+ expect { existing_ticket }.to raise_error Hubspot::NotFoundError
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ describe 'update!' do
45
+ let(:ticket_id) { 16_174_569_112 }
46
+ let(:properties) do
47
+ {
48
+ hs_ticket_priority: 'HIGH',
49
+ subject: 'New name'
50
+ }
51
+ end
52
+
53
+ subject(:update_ticket) { described_class.update!(ticket_id, properties) }
54
+
55
+ it 'updates existing ticket, returns the updated entity' do
56
+ VCR.use_cassette 'ticket_update' do
57
+ ticket = update_ticket
58
+ ticket.properties[:subject] = 'New name'
59
+ ticket.properties[:subject] = 'HIGH'
60
+ end
61
+ end
62
+ end
63
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hubspot-api-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.0
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-18 00:00:00.000000000 Z
11
+ date: 2024-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -292,6 +292,9 @@ files:
292
292
  - lib/hubspot/railtie.rb
293
293
  - lib/hubspot/resource.rb
294
294
  - lib/hubspot/subscription.rb
295
+ - lib/hubspot/task.rb
296
+ - lib/hubspot/ticket.rb
297
+ - lib/hubspot/ticket_properties.rb
295
298
  - lib/hubspot/topic.rb
296
299
  - lib/hubspot/utils.rb
297
300
  - spec/factories/companies.rb
@@ -319,6 +322,9 @@ files:
319
322
  - spec/lib/hubspot/owner_spec.rb
320
323
  - spec/lib/hubspot/properties_spec.rb
321
324
  - spec/lib/hubspot/resource_spec.rb
325
+ - spec/lib/hubspot/task_spec.rb
326
+ - spec/lib/hubspot/ticket_properties_spec.rb
327
+ - spec/lib/hubspot/ticket_spec.rb
322
328
  - spec/lib/hubspot/utils_spec.rb
323
329
  - spec/shared_examples/saveable_resource.rb
324
330
  - spec/shared_examples/updateable_resource.rb
@@ -331,7 +337,7 @@ licenses:
331
337
  - MIT
332
338
  metadata:
333
339
  changelog_uri: https://github.com/captaincontrat/hubspot-api-ruby/blob/master/History.md
334
- post_install_message:
340
+ post_install_message:
335
341
  rdoc_options: []
336
342
  require_paths:
337
343
  - lib
@@ -339,15 +345,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
339
345
  requirements:
340
346
  - - ">="
341
347
  - !ruby/object:Gem::Version
342
- version: '2.7'
348
+ version: '3.1'
343
349
  required_rubygems_version: !ruby/object:Gem::Requirement
344
350
  requirements:
345
351
  - - ">="
346
352
  - !ruby/object:Gem::Version
347
353
  version: '0'
348
354
  requirements: []
349
- rubygems_version: 3.1.2
350
- signing_key:
355
+ rubygems_version: 3.5.3
356
+ signing_key:
351
357
  specification_version: 4
352
358
  summary: hubspot-api-ruby is a wrapper for the HubSpot REST API
353
359
  test_files: []