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 +4 -4
- data/Gemfile.lock +1 -0
- data/README.md +149 -4
- data/hubspot_v3.gemspec +3 -3
- data/lib/hubspot_v3/config.rb +9 -3
- data/lib/hubspot_v3/mock_contract.rb +105 -2
- data/lib/hubspot_v3/version.rb +1 -1
- data/lib/hubspot_v3.rb +41 -2
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 831c706b12ca929919b9521451a44350194678d709e200fa5be2344142248fbd
|
4
|
+
data.tar.gz: acc7373fdf69e5d5265c4b5288187920f3cddbc4fd4014be1ad846dcd331ce75
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c44773f8b1eb6f67e7013692c0ba301ff350426c30e339ef6eb36d441d7f54176a9a5487ff3432e21bc2652758ef8a85001e96daa45a70d2b3363d7c196edc35
|
7
|
+
data.tar.gz: cfe059ca979b2b6153ee18c793a8fd440b714c8be7a68c8811e6ff3ce3cb77c668d61dd59d3f20c3d0370f39bac9fea816ea6c9364d5eced63f28ebb2521b1a5
|
data/Gemfile.lock
CHANGED
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
|
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
|
-
|
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 = ["
|
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
|
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")
|
data/lib/hubspot_v3/config.rb
CHANGED
@@ -1,9 +1,15 @@
|
|
1
1
|
module HubspotV3
|
2
2
|
class Config
|
3
|
-
attr_writer :
|
3
|
+
attr_writer :token, :contract
|
4
4
|
|
5
|
-
def
|
6
|
-
@
|
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
|
87
|
-
|
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
|
data/lib/hubspot_v3/version.rb
CHANGED
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}
|
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:
|
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:
|
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-
|
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
|
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
|
-
-
|
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: []
|