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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5fca1dbf931d9f64c4cb17b7d25f9310c2405ccf
4
- data.tar.gz: 18c70a4cd24f52c3b396442c6b184f44a03aea5b
3
+ metadata.gz: 6466da0961fcb2137144c6b5dbda8accb5a6479d
4
+ data.tar.gz: 9c812e7003d7855d52db04130d2be6f8b83f2b29
5
5
  SHA512:
6
- metadata.gz: d853eadb41140f1b11c363c9d714018c76cb52fdd14b32f59530460490b3b9d7cdf02771fc109136020fded4393f65371d54b96eb34a2f00a8d94573104e8afa
7
- data.tar.gz: e9927f0c810be10a2220dfee7e98269b31075b23d064a21e75162ccc99def1f5edec15aaff8f1541f705c7233d95943ca6d572c857c2d86bbe781b8582c44a10
6
+ metadata.gz: 4afbb29d4b4775f37cec9259f0a87b99d4fa954906d9e3b9c9970812bb4ff85507a1dc7419aaf5195ca080997e9bbaf248993ddeb9b6c21489100dad8002f0ff
7
+ data.tar.gz: a566bcb452938d3888480eac7e86b96dcc981ce9314bb95ac0ec7f955526dad8e68e903c61c9abe5acb35507f1b44d3da8a9eddd0d14ef62acdd22e734b016a6
@@ -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.8')
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
 
@@ -12,6 +12,7 @@ require "hubscreen/request"
12
12
  require "hubscreen/response"
13
13
 
14
14
  #Gem Lib Files
15
+ require "hubscreen/utils"
15
16
  require "hubscreen/version"
16
17
  require "hubscreen/config"
17
18
  require "hubscreen/exceptions"
@@ -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
@@ -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
 
@@ -1,4 +1,8 @@
1
1
  module Hubscreen
2
+ #
3
+ # Primary Request Object for API Access
4
+ #
5
+ # Remember to protect all API requests with error handling to catch non 200 response codes
2
6
  class Request
3
7
  attr_accessor :api_key, :api_endpoint, :timeout, :proxy, :faraday_adapter, :debug
4
8
 
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Hubscreen
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
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
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.8'
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.8'
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: