hubspot_v3 0.1.0 → 1.0.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: d55ee9f31364c57b484162bf1e574a5412e9f207072480c51025f4b624e1bc66
4
- data.tar.gz: 6ca13e0d86d8229f188d27b655453271f93e99938d78c36d14231def5bc207b5
3
+ metadata.gz: 831c706b12ca929919b9521451a44350194678d709e200fa5be2344142248fbd
4
+ data.tar.gz: acc7373fdf69e5d5265c4b5288187920f3cddbc4fd4014be1ad846dcd331ce75
5
5
  SHA512:
6
- metadata.gz: 48b74f1e180b916db063dc5c3f1669d4d4bc3f1c38f64969d6bc3b73035337ea3d1e3b66ba4ab44add9b2e71cd72637645d77b5f22d86e51d195240e42521af0
7
- data.tar.gz: cb09957b5b996fb56de04cce204d12dda956524c0dc4df73b5a3569c795a193648f7c9968594508d9d55dfcf9e09def66691ca4f76b2d36903045295861b773f
6
+ metadata.gz: c44773f8b1eb6f67e7013692c0ba301ff350426c30e339ef6eb36d441d7f54176a9a5487ff3432e21bc2652758ef8a85001e96daa45a70d2b3363d7c196edc35
7
+ data.tar.gz: cfe059ca979b2b6153ee18c793a8fd440b714c8be7a68c8811e6ff3ce3cb77c668d61dd59d3f20c3d0370f39bac9fea816ea6c9364d5eced63f28ebb2521b1a5
data/Gemfile.lock CHANGED
@@ -31,6 +31,7 @@ GEM
31
31
  rspec-support (3.10.2)
32
32
 
33
33
  PLATFORMS
34
+ arm64-darwin-21
34
35
  x86_64-linux
35
36
 
36
37
  DEPENDENCIES
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # HubspotV3
2
2
 
3
- Ruby gem wrapper around Hubspot API V3
3
+ Ruby gem wrapper around Hubspot CRM API V3
4
4
 
5
5
  Currently this gem focuses on **Batch** update/create/search of Contacts. More info in [source code](https://github.com/Pobble/hubspot_v3/blob/master/lib/hubspot_v3.rb)
6
6
 
@@ -13,7 +13,8 @@ killing those limits quite quickly.
13
13
 
14
14
  ## Other solutions out there
15
15
 
16
- Gem currently covers only features that are needed for our use cases, however this repo/gem is open for any Pull Requests with additional features.
16
+ Gem currently covers only features that are needed for our use cases (CRM Contacts & Companies),
17
+ however this repo/gem is open for any Pull Requests with additional features.
17
18
 
18
19
  If you need other features and wish not to contribute to this gem there are 2 existing Hubspot gems out there:
19
20
 
@@ -36,10 +37,18 @@ And then execute:
36
37
 
37
38
  ## Usage
38
39
 
39
- ### set API key
40
+ ### set App Token (API key)
41
+
42
+ > **NOTE**: Starting November 30, 2022, HubSpot API keys will no longer be able to be used as an
43
+ > authentication method to access HubSpot APIs [source](https://developers.hubspot.com/changelog/upcoming-api-key-sunset)
44
+
45
+ This means you cannot use Hubspot API KEY (a.k.a hapikey) to authenticate but rather create Hubspot Private App and use it's auth token
46
+ ([how to setup Hubspot private app](https://developers.hubspot.com/docs/api/private-apps#make-api-calls-with-your-app-s-access-token))
47
+
40
48
 
41
49
  ```
42
- HubspotV3.config.apikey = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
50
+ # Hubspot private app token (It's not the same as API KEY)
51
+ HubspotV3.config.token = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
43
52
  ```
44
53
 
45
54
  ### Contacts - Search
@@ -184,6 +193,121 @@ bodyhash = {
184
193
  HubspotV3.contacts_update(bodyhash)
185
194
  ```
186
195
 
196
+ ### Companies - Search
197
+
198
+ ```
199
+ bodyhash = {
200
+ "filterGroups":[
201
+ {
202
+ "filters": [
203
+ {
204
+ "propertyName": "name",
205
+ "operator": "EQ",
206
+ "value": "ACME Company"
207
+ }
208
+ ]
209
+ }
210
+ ]
211
+ }
212
+ HubspotV3.companies_search(bodyhash)
213
+ ```
214
+
215
+ ### Companies - Search by id
216
+
217
+ ```
218
+ HubspotV3.companies_search_by_ids(['9582682125'])
219
+ #=> [
220
+ # {
221
+ # "id"=>"9582682125",
222
+ # "properties"=> {
223
+ # "createdate"=>"2022-09-13T15:06:03.116Z",
224
+ # "domain"=>nil,
225
+ # "hs_lastmodifieddate"=>"2022-09-13T15:20:48.331Z",
226
+ # "hs_object_id"=>"9582682125",
227
+ # "name"=>"ACME Company"},
228
+ # "createdAt"=>"2022-09-13T15:06:03.116Z",
229
+ # "updatedAt"=>"2022-09-13T15:20:48.331Z",
230
+ # "archived"=>false
231
+ # }
232
+ #]
233
+
234
+ HubspotV3.companies_search_by_ids(['66666'])
235
+ #=> []
236
+
237
+ ```
238
+
239
+
240
+ * Full list of search filters and operators can be found in [official hubspot docs](https://developers.hubspot.com/docs/api/crm/companies)
241
+
242
+ ### Companies - Batch Create
243
+
244
+ ```
245
+ bodyhash = {
246
+ "inputs": [
247
+ {
248
+ "properties": {
249
+ "name": "ACME Corporation"
250
+ }
251
+ },
252
+ {
253
+ "city": "Cambridge",
254
+ "domain": "biglytics.net",
255
+ "industry": "Technology",
256
+ "name": "Biglytics",
257
+ "phone": "(877) 929-0687",
258
+ "state": "Massachusetts"
259
+ }
260
+ ]
261
+ }
262
+
263
+ begin
264
+ HubspotV3.companies_create(bodyhash)
265
+ rescue HubspotV3::RequestFailedError => e
266
+ puts e.message
267
+ # => 409 - some error reason (I never encounterd an error when creating company)
268
+
269
+ httparty_response_object = e.httparty_response
270
+ # => #<HTTParty::Response:0x1d920 parsed_response={"status"=>"error"...
271
+ end
272
+ ```
273
+
274
+ return value:
275
+
276
+ ```
277
+ [
278
+ {
279
+ "id"=>"9674616673",
280
+ "properties"=> {
281
+ ...
282
+ }
283
+ ...
284
+ },
285
+ {
286
+ "id"=>"9674616674",
287
+ "properties"=> {
288
+ ...
289
+ }
290
+ ...
291
+ }
292
+ ]
293
+ ```
294
+
295
+ ### Companies - Batch Update
296
+
297
+ ```
298
+ bodyhash = {
299
+ "inputs": [
300
+ {
301
+ "id": "9582682125",
302
+ "properties": {
303
+ "name": "ACME Company"
304
+ }
305
+ }
306
+ ]
307
+ }
308
+ HubspotV3.companies_update(bodyhash)
309
+ ```
310
+
187
311
  ## Test your app
188
312
 
189
313
  You can use http interceptor like [webmock](https://github.com/bblimke/webmock), [vcr](https://github.com/vcr/vcr).
@@ -218,7 +342,28 @@ HubspotV3::MockContract.contacts_search_by_emails_mapped(["hello@pobble.com", "n
218
342
 
219
343
  > More info on how to use [Contract tests](https://blog.eq8.eu/article/explicit-contracts-for-rails-http-api-usecase.html)
220
344
 
345
+ ## Troubleshooting
346
+
347
+ #### Error - Cannot deserialize value of type
348
+ ```
349
+ `post': 400 - Invalid input JSON on line 1, column 1: Cannot deserialize value of type `com.hubspot.apiutils.core.models.batch.BatchInput$Json<com.hubspot.inbounddb.publicobject.core.v2.SimplePublicObjectBatchInput>` from Array value (token `JsonToken.START_ARRAY`) (HubspotV3::RequestFailedError)
350
+ ```
221
351
 
352
+ You probably forgot to wrap your batch call body hash in `inputs`.
353
+
354
+ E.g.:
355
+
356
+ instead of
357
+
358
+ ```
359
+ HubspotV3.companies_update([{"id"=>"1234", "properties"=> {"city" => "Cambridge"}}])
360
+ ```
361
+
362
+ you need to do:
363
+
364
+ ```
365
+ HubspotV3.companies_update("inputs" => [{"id"=>"1234", "properties"=> {"city" => "Cambridge"}}])
366
+ ```
222
367
 
223
368
  ## Development
224
369
 
data/hubspot_v3.gemspec CHANGED
@@ -6,10 +6,10 @@ Gem::Specification.new do |spec|
6
6
  spec.name = "hubspot_v3"
7
7
  spec.version = HubspotV3::VERSION
8
8
  spec.authors = ["Tomas Valent"]
9
- spec.email = ["equivalent@eq8.eu"]
9
+ spec.email = ["tomas.valent@gmail.com"]
10
10
 
11
- spec.summary = "Hubspot API v3 Ruby gem"
12
- spec.description = "Ruby wrapper around Hubspot API v3 with simple implementation and batch endpoints support"
11
+ spec.summary = "Hubspot CRM API (v3) Ruby gem"
12
+ spec.description = "Ruby wrapper around Hubspot CRM API v3 with simple implementation around batch endpoints and private apps token support"
13
13
  spec.homepage = "https://github.com/Pobble/hubspot_v3"
14
14
  spec.license = "MIT"
15
15
  spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
@@ -1,9 +1,15 @@
1
1
  module HubspotV3
2
2
  class Config
3
- attr_writer :apikey, :contract
3
+ attr_writer :token, :contract
4
4
 
5
- def apikey
6
- @apikey || raise('Hubspot API key is not set. Set it with HubspotV3.config.apikey="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"')
5
+ def token
6
+ @token || raise('Hubspot API key is not set. Set it with HubspotV3.config.token="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"')
7
+ end
8
+
9
+ def apikey=(*)
10
+ raise 'Hubspot API keys are deprecated. Don\'t use HubspotV3.config.apikey="hubspotApiKeyNoLongerWorks".' +
11
+ ' Make sure you set up Hubspot private app and set the token for this gem' +
12
+ ' with HubspotV3.config.token="xxMyHubspotPrivateAppTokenxx"'
7
13
  end
8
14
  end
9
15
 
@@ -83,8 +83,103 @@ module HubspotV3
83
83
  HubspotV3::Helpers.map_search_by_email(contacts_search_by_emails(emails_ary))
84
84
  end
85
85
 
86
- def _calculate_id(email)
87
- email.bytes.sum # sum of asci values of the email string
86
+ def contacts_search(bodyhash)
87
+ puts _general_batch_search_should_be_overridden_msg('contacts_search')
88
+ raise "HubspotV3::MockContract.contacts_search should be stubbed or overridden"
89
+ end
90
+
91
+ def companies_create(bodyhash)
92
+ inputs = _fetch_inputs(bodyhash)
93
+ inputs.map do |input|
94
+ properties = _fetch_input_properties(input)
95
+
96
+ #note: Hubspot API doesn't really require name, but for sake of this
97
+ # contract functionality we need properties.name
98
+ name = properties.fetch('name') { raise KeyError.new("Item in Inputs hash must contain key 'properties.name' - hash['inputs'][0]['properties']['name']") }
99
+
100
+ id = _calculate_id(name) + 1_000_000
101
+
102
+ default_properties = {
103
+ "createdate"=>"2022-09-13T15:06:03.116Z",
104
+ "hs_lastmodifieddate"=>"2022-09-13T15:06:03.116Z",
105
+ "hs_object_id"=>id.to_s,
106
+ "hs_pipeline"=>"companies-lifecycle-pipeline",
107
+ "lifecyclestage"=>"lead",
108
+ "name"=>name
109
+ }
110
+
111
+ properties = default_properties.merge(properties)
112
+
113
+ {
114
+ "id"=>id.to_s,
115
+ "properties"=> properties,
116
+ "createdAt"=>"2022-09-13T15:06:03.116Z",
117
+ "updatedAt"=>"2022-09-13T15:06:03.116Z",
118
+ "archived"=>false
119
+ }
120
+ end
121
+ end
122
+
123
+ def companies_update(bodyhash)
124
+ inputs = _fetch_inputs(bodyhash)
125
+
126
+ inputs.map do |input|
127
+ id = _fetch_input_id(input)
128
+ properties = _fetch_input_properties(input)
129
+
130
+ default_properties = {
131
+ "hs_lastmodifieddate"=>"2022-09-13T15:06:33.116Z",
132
+ "createdate"=>"2022-09-13T15:06:03.116Z",
133
+ "hs_object_id"=>id.to_s,
134
+ "hs_pipeline"=>"companies-lifecycle-pipeline",
135
+ "lifecyclestage"=>"lead"
136
+ }
137
+ properties = default_properties.merge(properties)
138
+
139
+ {
140
+ "id"=>id.to_s,
141
+ "properties"=>properties,
142
+ "createdAt"=>"2022-09-13T15:06:03.116Z",
143
+ "updatedAt"=>"2022-09-13T15:06:33.116Z",
144
+ "archived"=>false
145
+ }
146
+ end
147
+ end
148
+
149
+ def companies_search_by_ids(ids)
150
+ raise 'argument must be an Array' unless ids.is_a?(Array)
151
+ raise 'Array must include only String ids' if ids.select { |x| ! x.is_a?(String) }.any?
152
+
153
+ resp = ids.map do |id|
154
+ if id.match(/666666/)
155
+ # this represents not found records
156
+ nil
157
+ else
158
+ {
159
+ "id"=>id,
160
+ "properties"=>{
161
+ "createdate"=>"2022-09-13T15:06:03.116Z",
162
+ "domain"=>nil,
163
+ "hs_lastmodifieddate"=>"2022-09-13T15:20:48.331Z",
164
+ "hs_object_id"=>id,
165
+ "name"=>"ACME Company #{id}"},
166
+ "createdAt"=>"2022-09-13T15:06:03.116Z",
167
+ "updatedAt"=>"2022-09-13T15:20:48.331Z",
168
+ "archived"=>false
169
+ }
170
+ end
171
+ end
172
+
173
+ resp.compact
174
+ end
175
+
176
+ def companies_search(*)
177
+ puts _general_batch_search_should_be_overridden_msg('companies_search')
178
+ raise "HubspotV3::MockContract.companies_search should be stubbed or overridden"
179
+ end
180
+
181
+ def _calculate_id(whatever)
182
+ whatever.bytes.sum # sum of asci values of the email string
88
183
  end
89
184
 
90
185
  def _sanitize_email_as_hubspot_would(email)
@@ -103,5 +198,13 @@ module HubspotV3
103
198
  def _fetch_input_id(input)
104
199
  input.fetch('id') { raise KeyError.new("Item in Inputs hash must contain key 'id' - hash['inputs'][0]['id']") }
105
200
  end
201
+
202
+ def _general_batch_search_should_be_overridden_msg(name)
203
+ "General search queries are not covered by test contract and should be customly" +
204
+ "\noverridden based on your usecase. E.g.:" +
205
+ "\n expect(HubspotV3::MockContract)" +
206
+ "\n .to receive(:#{name})" +
207
+ "\n .and_return([{'id'=>'12345', 'properties'=>{ }])\n\n"
208
+ end
106
209
  end
107
210
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HubspotV3
4
- VERSION = "0.1.0"
4
+ VERSION = "1.0.0"
5
5
  end
data/lib/hubspot_v3.rb CHANGED
@@ -19,6 +19,9 @@ module HubspotV3
19
19
  CONTACTS_SEARCH='/crm/v3/objects/contacts/search'
20
20
  CONTACTS_CREATE='/crm/v3/objects/contacts/batch/create'
21
21
  CONTACTS_UPDATE='/crm/v3/objects/contacts/batch/update'
22
+ COMPANIES_SEARCH='/crm/v3/objects/companies/search'
23
+ COMPANIES_CREATE='/crm/v3/objects/companies/batch/create'
24
+ COMPANIES_UPDATE='/crm/v3/objects/companies/batch/update'
22
25
 
23
26
  def self.contacts_create(bodyhash)
24
27
  post(CONTACTS_CREATE, bodyhash)
@@ -53,14 +56,43 @@ module HubspotV3
53
56
  HubspotV3::Helpers.map_search_by_email(contacts_search_by_emails(emails_ary))
54
57
  end
55
58
 
59
+ def self.companies_create(bodyhash)
60
+ post(COMPANIES_CREATE, bodyhash)
61
+ end
62
+
63
+ def self.companies_update(bodyhash)
64
+ post(COMPANIES_UPDATE, bodyhash)
65
+ end
66
+
67
+ def self.companies_search(bodyhash)
68
+ post(COMPANIES_SEARCH, bodyhash)
69
+ end
70
+
71
+ def self.companies_search_by_ids(hubspot_object_ids_ary)
72
+ filters_group_ary = hubspot_object_ids_ary.map do |e|
73
+ {
74
+ "filters": [
75
+ {
76
+ "propertyName": "hs_object_id",
77
+ "operator": "EQ",
78
+ "value": e
79
+ }
80
+ ]
81
+ }
82
+ end
83
+
84
+ bodyhash = { "filterGroups": filters_group_ary }
85
+ companies_search(bodyhash)
86
+ end
87
+
56
88
  def self.url(path)
57
- "#{API_URL}#{path}?hapikey=#{config.apikey}"
89
+ "#{API_URL}#{path}"
58
90
  end
59
91
 
60
92
  def self.post(path, bodyhash)
61
93
  res = HTTParty.post(url(path), {
62
94
  body: bodyhash.to_json,
63
- headers: {'Content-Type' => 'application/json'}
95
+ headers: headers
64
96
  })
65
97
  case res.code
66
98
  when 200, 201
@@ -69,4 +101,11 @@ module HubspotV3
69
101
  raise HubspotV3::RequestFailedError.new("#{res.code} - #{res.parsed_response['message']}", res)
70
102
  end
71
103
  end
104
+
105
+ def self.headers
106
+ {
107
+ 'Content-Type' => 'application/json',
108
+ 'Authorization': "Bearer #{config.token}"
109
+ }
110
+ end
72
111
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hubspot_v3
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomas Valent
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-27 00:00:00.000000000 Z
11
+ date: 2022-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -24,10 +24,10 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.2'
27
- description: Ruby wrapper around Hubspot API v3 with simple implementation and batch
28
- endpoints support
27
+ description: Ruby wrapper around Hubspot CRM API v3 with simple implementation around
28
+ batch endpoints and private apps token support
29
29
  email:
30
- - equivalent@eq8.eu
30
+ - tomas.valent@gmail.com
31
31
  executables: []
32
32
  extensions: []
33
33
  extra_rdoc_files: []
@@ -72,5 +72,5 @@ requirements: []
72
72
  rubygems_version: 3.3.7
73
73
  signing_key:
74
74
  specification_version: 4
75
- summary: Hubspot API v3 Ruby gem
75
+ summary: Hubspot CRM API (v3) Ruby gem
76
76
  test_files: []