hubscreen 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/hubscreen.gemspec +1 -1
- data/lib/hubscreen.rb +1 -0
- data/lib/hubscreen/api_request.rb +5 -1
- data/lib/hubscreen/contact.rb +217 -0
- data/lib/hubscreen/request.rb +4 -0
- data/lib/hubscreen/response.rb +1 -1
- data/lib/hubscreen/utils.rb +125 -0
- data/lib/hubscreen/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6466da0961fcb2137144c6b5dbda8accb5a6479d
|
4
|
+
data.tar.gz: 9c812e7003d7855d52db04130d2be6f8b83f2b29
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4afbb29d4b4775f37cec9259f0a87b99d4fa954906d9e3b9c9970812bb4ff85507a1dc7419aaf5195ca080997e9bbaf248993ddeb9b6c21489100dad8002f0ff
|
7
|
+
data.tar.gz: a566bcb452938d3888480eac7e86b96dcc981ce9314bb95ac0ec7f955526dad8e68e903c61c9abe5acb35507f1b44d3da8a9eddd0d14ef62acdd22e734b016a6
|
data/hubscreen.gemspec
CHANGED
@@ -40,7 +40,7 @@ Gem::Specification.new do |spec|
|
|
40
40
|
|
41
41
|
#Gem Dependancies
|
42
42
|
spec.add_dependency(%q<activesupport>, [">= 4"])
|
43
|
-
spec.add_dependency('faraday', '>= 0.
|
43
|
+
spec.add_dependency('faraday', '>= 0.9')
|
44
44
|
spec.add_dependency('multi_json', '>= 1')
|
45
45
|
spec.add_dependency('recursive-open-struct', '>= 1.0')
|
46
46
|
|
data/lib/hubscreen.rb
CHANGED
@@ -127,16 +127,20 @@ module Hubscreen
|
|
127
127
|
|
128
128
|
def configure_request(request: nil, params: nil, headers: nil, body: nil)
|
129
129
|
if request
|
130
|
+
request.options.params_encoder = Faraday::FlatParamsEncoder
|
130
131
|
request.params.merge!(params) if params
|
131
132
|
request.headers['Content-Type'] = 'application/json'
|
132
133
|
request.headers.merge!(headers) if headers
|
133
134
|
request.body = body if body
|
134
|
-
request.options.timeout = self.timeout
|
135
|
+
request.options.timeout = self.timeout
|
135
136
|
end
|
136
137
|
end
|
137
138
|
|
139
|
+
# Note REST Client has been modified for Flat Parameter Encoding to support Hubspot's batch search functionality.
|
140
|
+
# This however limit's Faraday's ability to accept nested parameters
|
138
141
|
def rest_client
|
139
142
|
client = Faraday.new(self.api_url, proxy: self.proxy) do |faraday|
|
143
|
+
|
140
144
|
faraday.response :raise_error
|
141
145
|
faraday.adapter adapter
|
142
146
|
if @request_builder.debug
|
data/lib/hubscreen/contact.rb
CHANGED
@@ -6,6 +6,9 @@ module Hubscreen
|
|
6
6
|
# {https://developers.hubspot.com/docs/methods/contacts/contacts-overview}
|
7
7
|
#
|
8
8
|
# This is a convenience object for single Hubspot contact returned by the API
|
9
|
+
#
|
10
|
+
# This is a convenience object for handling or retrieving single Hubspot contact returned by the API
|
11
|
+
# These helpers may not be updated as fast as the API. Use at your own risk
|
9
12
|
|
10
13
|
class Contact < Hubscreen::Response
|
11
14
|
CONTACT_KEYS = [:properties,
|
@@ -15,6 +18,15 @@ module Hubscreen
|
|
15
18
|
:last_name,
|
16
19
|
:hubspot_owner_id]
|
17
20
|
|
21
|
+
CONTACT_ROOT = "v1/contact"
|
22
|
+
FIND_BY_VID_PATH = "#{CONTACT_ROOT}/vid" #Note full endpoint is GET /contacts/v1/contact/vid/:contact_id/profile
|
23
|
+
FIND_BY_EMAIL_PATH = "#{CONTACT_ROOT}/email" #Note full endpoint is GET /contacts/v1/contact/email/:contact_email/profile
|
24
|
+
CREATE_OR_UPDATE_PATH = '#{CONTACT_ROOT}/createOrUpdate/email' #Note full endpoint is POST /contacts/v1/contact/createOrUpdate/email/:contact_email
|
25
|
+
UPDATE_PATH = "#{CONTACT_ROOT}/vid" #Note full endpoint is POST /contacts/v1/contact/vid/:contact_id/profile
|
26
|
+
UPDATE_BATCH_PATH = "#{CONTACT_ROOT}/batch" # POST /contacts/v1/contact/batch/
|
27
|
+
FIND_BY_EMAIL_BATCH_PATH = "#{CONTACT_ROOT}/emails/batch" # Note full endpoint is GET /contacts/v1/contact/emails/batch/:contact_emails
|
28
|
+
|
29
|
+
|
18
30
|
attr_accessor *CONTACT_KEYS
|
19
31
|
|
20
32
|
def initialize(response)
|
@@ -37,6 +49,211 @@ module Hubscreen
|
|
37
49
|
"<Hubscreen::Contact vid:#{@vid}, email:'#{@email}', first_name:'#{@first_name}', last_name:'#{@last_name}', company:'#{@company}', hubspot_owner_id:'#{@hubspot_owner_id}', properties:<Not Shown>, raw_response:<Not Shown>, raw_hash:<Not Shown>>"
|
38
50
|
end
|
39
51
|
|
52
|
+
# Convenience Methods
|
53
|
+
class << self
|
54
|
+
|
55
|
+
# Find Contact by vid
|
56
|
+
#
|
57
|
+
# If Hubspot cannot find the contact, will return the 404 response. Always check for a 200 status code before proceeding
|
58
|
+
def find(vid)
|
59
|
+
begin
|
60
|
+
Hubscreen::Request.new.contacts(FIND_BY_VID_PATH,vid).profile.get.contact
|
61
|
+
rescue Hubscreen::RequestError => e
|
62
|
+
failure_response = Response.new(e.response)
|
63
|
+
failure_response.status_code = e.response[:status_code]
|
64
|
+
return failure_response
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Find Contact by email
|
69
|
+
#
|
70
|
+
# If Hubspot cannot find the contact, will return the 404 response. Always check for a 200 status code before proceeding
|
71
|
+
def find_by_email(email)
|
72
|
+
begin
|
73
|
+
Hubscreen::Request.new.contacts(FIND_BY_EMAIL_PATH,email).profile.get.contact
|
74
|
+
rescue Hubscreen::RequestError => e
|
75
|
+
failure_response = Response.new(e.response)
|
76
|
+
failure_response.status_code = e.response[:status_code]
|
77
|
+
return failure_response
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Create New Contact
|
82
|
+
#
|
83
|
+
# {https://developers.hubspot.com/docs/methods/contacts/create_contact}
|
84
|
+
#
|
85
|
+
# Will return a Contact object representing the new contact
|
86
|
+
def create!(email,properties={})
|
87
|
+
begin
|
88
|
+
params = properties.stringify_keys.merge('email' => email)
|
89
|
+
post_data = {properties: Hubscreen::Utils.hash_to_properties(params)}
|
90
|
+
Hubscreen::Request.new.contacts(CONTACT_ROOT).post(body: post_data).contact
|
91
|
+
rescue Hubscreen::RequestError => e
|
92
|
+
|
93
|
+
failure_response = Response.new(e.response)
|
94
|
+
failure_response.status_code = e.response[:status_code]
|
95
|
+
return failure_response
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Update New Contact
|
100
|
+
#
|
101
|
+
# {https://developers.hubspot.com/docs/methods/contacts/update_contact}
|
102
|
+
#
|
103
|
+
# Will return a Contact object representing the updated contact
|
104
|
+
def update!(vid,properties={})
|
105
|
+
begin
|
106
|
+
params = properties.stringify_keys
|
107
|
+
post_data = {properties: Hubscreen::Utils.hash_to_properties(params)}
|
108
|
+
Hubscreen::Request.new.contacts(UPDATE_PATH,vid).profile.post(body: post_data).contact
|
109
|
+
rescue Hubscreen::RequestError => e
|
110
|
+
#binding.pry
|
111
|
+
failure_response = Response.new(e.response)
|
112
|
+
failure_response.status_code = e.response[:status_code]
|
113
|
+
return failure_response
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Upsert Contact (Create or Update)
|
118
|
+
#
|
119
|
+
# {https://developers.hubspot.com/docs/methods/contacts/create_or_update}
|
120
|
+
#
|
121
|
+
# Will return a Contact object representing the new contact
|
122
|
+
def upsert(email,properties={})
|
123
|
+
begin
|
124
|
+
params = properties.stringify_keys.merge('email' => email)
|
125
|
+
post_data = {properties: Hubscreen::Utils.hash_to_properties(params)}
|
126
|
+
Hubscreen::Request.new.contacts(CREATE_OR_UPDATE_PATH,email).post(body: post_data).contact
|
127
|
+
rescue Hubscreen::RequestError => e
|
128
|
+
|
129
|
+
failure_response = Response.new(e.response)
|
130
|
+
failure_response.status_code = e.response[:status_code]
|
131
|
+
return failure_response
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Batch Methods
|
136
|
+
|
137
|
+
# Create or update a group of contacts
|
138
|
+
#
|
139
|
+
# {https://developers.hubspot.com/docs/methods/contacts/batch_create_or_update}
|
140
|
+
#
|
141
|
+
# POST /contacts/v1/contact/batch/
|
142
|
+
#
|
143
|
+
# From Hubspot:
|
144
|
+
# Create a group of contacts or update them if they already exist. Particularly useful for periodic syncs from another contacts database to HubSpot.
|
145
|
+
#
|
146
|
+
# Performance is best when calls are limited to 100 or fewer contacts.
|
147
|
+
#
|
148
|
+
# When using this endpoint, please keep in mind that any errors with a single contact in your batch will prevent the entire batch from processing. If this happens, we'll return a 400 response with additional details as to the cause.
|
149
|
+
#
|
150
|
+
# This method only supports update by vid (vid is mandatory)
|
151
|
+
# To use this method, pass in a array of hashes using the following structure:
|
152
|
+
#
|
153
|
+
# [
|
154
|
+
# {
|
155
|
+
# vid: 5464,
|
156
|
+
# firstname: "George",
|
157
|
+
# lastname: "Henry"
|
158
|
+
# },
|
159
|
+
# {
|
160
|
+
# vid: 5464,
|
161
|
+
# firstname: "Codey",
|
162
|
+
# lastname: "Lang"
|
163
|
+
# }
|
164
|
+
# ]
|
165
|
+
def upsert_batch_by_vid(contacts = [])
|
166
|
+
begin
|
167
|
+
params = []
|
168
|
+
contacts.each do |contact|
|
169
|
+
params << {'vid': contact[:vid],'properties': Hubscreen::Utils.hash_to_properties(contact.except(:vid).stringify_keys)}
|
170
|
+
end
|
171
|
+
post_data = params
|
172
|
+
Hubscreen::Request.new.contacts(UPDATE_BATCH_PATH).post(body: post_data)
|
173
|
+
rescue Hubscreen::RequestError => e
|
174
|
+
|
175
|
+
failure_response = Response.new(e.response)
|
176
|
+
failure_response.status_code = e.response[:status_code]
|
177
|
+
return failure_response
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Create or update a group of contacts
|
182
|
+
#
|
183
|
+
# {https://developers.hubspot.com/docs/methods/contacts/batch_create_or_update}
|
184
|
+
#
|
185
|
+
# POST /contacts/v1/contact/batch/
|
186
|
+
#
|
187
|
+
# From Hubspot:
|
188
|
+
# Create a group of contacts or update them if they already exist. Particularly useful for periodic syncs from another contacts database to HubSpot.
|
189
|
+
#
|
190
|
+
# Performance is best when calls are limited to 100 or fewer contacts.
|
191
|
+
#
|
192
|
+
# When using this endpoint, please keep in mind that any errors with a single contact in your batch will prevent the entire batch from processing. If this happens, we'll return a 400 response with additional details as to the cause.
|
193
|
+
#
|
194
|
+
# This method only supports update by emails (email is manatory)
|
195
|
+
# DO NOT PASS VID
|
196
|
+
#
|
197
|
+
# To use this method, pass in a array of hashes using the following structure:
|
198
|
+
#
|
199
|
+
# [
|
200
|
+
# {
|
201
|
+
# email: "first@email.com",
|
202
|
+
# firstname: "George",
|
203
|
+
# lastname: "Henry"
|
204
|
+
# },
|
205
|
+
# {
|
206
|
+
# email: "second@email.com",
|
207
|
+
# firstname: "Codey",
|
208
|
+
# lastname: "Lang"
|
209
|
+
# }
|
210
|
+
# ]
|
211
|
+
def upsert_batch_by_email(contacts = [])
|
212
|
+
begin
|
213
|
+
params = []
|
214
|
+
contacts.each do |contact|
|
215
|
+
params << {'email': contact[:email],'properties': Hubscreen::Utils.hash_to_properties(contact.except(:email).stringify_keys)}
|
216
|
+
end
|
217
|
+
post_data = params
|
218
|
+
Hubscreen::Request.new.contacts(UPDATE_BATCH_PATH).post(body: post_data)
|
219
|
+
rescue Hubscreen::RequestError => e
|
220
|
+
|
221
|
+
failure_response = Response.new(e.response)
|
222
|
+
failure_response.status_code = e.response[:status_code]
|
223
|
+
return failure_response
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Search for a batch of contacts by email address
|
228
|
+
#
|
229
|
+
# {https://developers.hubspot.com/docs/methods/contacts/get_batch_by_email}
|
230
|
+
#
|
231
|
+
# GET /contacts/v1/contact/emails/batch/:contact_emails
|
232
|
+
#
|
233
|
+
# This method will return an array of Contact objects representing the search results
|
234
|
+
#
|
235
|
+
# If there is a connection error, the status code of the first element will reflect it. Always check this.
|
236
|
+
def find_by_emails(emails = [])
|
237
|
+
|
238
|
+
begin
|
239
|
+
params = {email: emails} # Based on Faraday code >= v0.9
|
240
|
+
response = Hubscreen::Request.new.contacts(FIND_BY_EMAIL_BATCH_PATH).get(params: params)
|
241
|
+
results = []
|
242
|
+
response.raw_hash.each do |key,value|
|
243
|
+
results << new(Response.new(value))
|
244
|
+
end
|
245
|
+
return results
|
246
|
+
rescue Hubscreen::RequestError => e
|
247
|
+
failure_response = Response.new(e.response)
|
248
|
+
failure_response.status_code = e.response[:status_code]
|
249
|
+
return [failure_response]
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
|
256
|
+
end
|
40
257
|
|
41
258
|
end
|
42
259
|
|
data/lib/hubscreen/request.rb
CHANGED
data/lib/hubscreen/response.rb
CHANGED
@@ -7,7 +7,7 @@ module Hubscreen
|
|
7
7
|
# By default all APIRequests will return a Response object. To disable this, set Hubscreen.configure(encapsulate_response: false)
|
8
8
|
|
9
9
|
class Response
|
10
|
-
attr_accessor :raw_hash, :raw_response
|
10
|
+
attr_accessor :raw_hash, :raw_response, :status_code
|
11
11
|
|
12
12
|
def initialize(response_json_hash)
|
13
13
|
@raw_hash = response_json_hash
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# Based on [hubspot-crm gem](https://github.com/adimichele/hubspot-ruby)
|
2
|
+
|
3
|
+
module Hubscreen
|
4
|
+
class Utils
|
5
|
+
class << self
|
6
|
+
|
7
|
+
# Parses the hubspot properties format into a key-value hash
|
8
|
+
def properties_to_hash(props)
|
9
|
+
newprops = HashWithIndifferentAccess.new
|
10
|
+
props.each { |k, v| newprops[k] = v["value"] }
|
11
|
+
newprops
|
12
|
+
end
|
13
|
+
|
14
|
+
# Turns a hash into the hubspot properties format
|
15
|
+
def hash_to_properties(hash, opts = {})
|
16
|
+
key_name = opts[:key_name] || "property"
|
17
|
+
hash.map { |k, v| { key_name => k.to_s, "value" => v } }
|
18
|
+
end
|
19
|
+
|
20
|
+
def dump_properties(klass, hapikey=ENV['HUBSPOT_API_KEY'], filter={})
|
21
|
+
with_hapikey(hapikey) do
|
22
|
+
{ 'groups' => klass.groups({}, filter),
|
23
|
+
'properties' => klass.all({}, filter).select { |p| !p['hubspotDefined'] }
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def restore_properties(klass, hapikey=ENV['HUPSPOT_API_KEY'], properties={}, dry_run=false)
|
29
|
+
existing_properties = dump_properties(klass, hapikey)
|
30
|
+
skip, new_groups, new_props, update_props = compare_property_lists(klass, properties, existing_properties)
|
31
|
+
puts '', 'Dry Run - Changes will not be applied' if dry_run
|
32
|
+
puts '','Skipping'
|
33
|
+
skip.each { |h| puts "#{h[:reason]} - #{h[:prop]['groupName']}:#{h[:prop]['name']}" }
|
34
|
+
with_hapikey(hapikey) do
|
35
|
+
create_groups(klass, new_groups, dry_run)
|
36
|
+
create_properties(klass, new_props, dry_run)
|
37
|
+
update_properties(klass, update_props, dry_run)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_groups(klass, groups, dry_run=false)
|
42
|
+
puts '','Creating new groups'
|
43
|
+
groups.each do |g|
|
44
|
+
if dry_run || klass.create_group!(g)
|
45
|
+
puts "Created: #{g['name']}"
|
46
|
+
else
|
47
|
+
puts "Failed: #{g['name']}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_properties(klass, props, dry_run=false)
|
53
|
+
puts '','Creating new properties'
|
54
|
+
props.each do |p|
|
55
|
+
if dry_run || klass.create!(p)
|
56
|
+
puts "Created: #{p['groupName']}:#{p['name']}"
|
57
|
+
else
|
58
|
+
puts "Failed: #{p['groupName']}:#{p['name']}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def update_properties(klass, props, dry_run=false)
|
64
|
+
puts '','Updating existing properties'
|
65
|
+
props.each do |p|
|
66
|
+
if dry_run || klass.update!(p['name'], p)
|
67
|
+
puts "Updated: #{p['groupName']}:#{p['name']}"
|
68
|
+
else
|
69
|
+
puts "Failed: #{p['groupName']}:#{p['name']}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def compare_property_lists(klass, source, target)
|
75
|
+
skip = [] # Array of skipped properties and the reason
|
76
|
+
new_groups = Set.new # Array of groups to create
|
77
|
+
new_props = [] # Array of properties to add
|
78
|
+
update_props = [] # Array of properties to update
|
79
|
+
src_groups = source['groups']
|
80
|
+
dst_groups = target['groups']
|
81
|
+
src_props = source['properties']
|
82
|
+
dst_props = target['properties']
|
83
|
+
|
84
|
+
src_props.each do |src|
|
85
|
+
group = find_by_name(src['groupName'], src_groups)
|
86
|
+
if src['createdUserId'].blank? && src['updatedUserId'].blank? then
|
87
|
+
skip << { prop: src, reason: 'Not user created' }
|
88
|
+
else
|
89
|
+
dst = find_by_name(src['name'], dst_props)
|
90
|
+
if dst
|
91
|
+
if dst['readOnlyDefinition']
|
92
|
+
skip << { prop: src, reason: 'Definition is read-only' }
|
93
|
+
elsif klass.same?(src, dst)
|
94
|
+
skip << { prop: src, reason: 'No change' }
|
95
|
+
else
|
96
|
+
new_groups << group unless group.blank? || find_by_name(group['name'], dst_groups)
|
97
|
+
update_props << src
|
98
|
+
end
|
99
|
+
else
|
100
|
+
new_groups << group unless group.blank? || find_by_name(group['name'], dst_groups)
|
101
|
+
new_props << src
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
[skip, new_groups.to_a, new_props, update_props]
|
106
|
+
end
|
107
|
+
|
108
|
+
def with_hapikey(hapikey)
|
109
|
+
begin
|
110
|
+
Hubspot.configure(hapikey: hapikey) unless hapikey.blank?
|
111
|
+
yield if block_given?
|
112
|
+
ensure
|
113
|
+
Hubspot.configure(hapikey: ENV['HUBSPOT_API_KEY']) unless hapikey.blank?
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def find_by_name(name, set)
|
120
|
+
set.detect { |item| item['name'] == name }
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/lib/hubscreen/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hubscreen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bryan Vaz
|
@@ -114,14 +114,14 @@ dependencies:
|
|
114
114
|
requirements:
|
115
115
|
- - ">="
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: '0.
|
117
|
+
version: '0.9'
|
118
118
|
type: :runtime
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: '0.
|
124
|
+
version: '0.9'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: multi_json
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -178,6 +178,7 @@ files:
|
|
178
178
|
- lib/hubscreen/exceptions.rb
|
179
179
|
- lib/hubscreen/request.rb
|
180
180
|
- lib/hubscreen/response.rb
|
181
|
+
- lib/hubscreen/utils.rb
|
181
182
|
- lib/hubscreen/version.rb
|
182
183
|
homepage: https://github.com/bryanvaz/hubscreen
|
183
184
|
licenses:
|