hubscreen 0.0.4 → 0.0.5

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
  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: