intercom 3.9.3 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +340 -222
  3. data/Rakefile +1 -1
  4. data/changes.txt +21 -0
  5. data/lib/intercom.rb +33 -22
  6. data/lib/intercom/api_operations/archive.rb +2 -1
  7. data/lib/intercom/api_operations/delete.rb +16 -0
  8. data/lib/intercom/api_operations/find.rb +5 -2
  9. data/lib/intercom/api_operations/find_all.rb +4 -3
  10. data/lib/intercom/api_operations/list.rb +4 -1
  11. data/lib/intercom/api_operations/load.rb +4 -2
  12. data/lib/intercom/api_operations/nested_resource.rb +70 -0
  13. data/lib/intercom/api_operations/save.rb +6 -4
  14. data/lib/intercom/api_operations/scroll.rb +4 -5
  15. data/lib/intercom/api_operations/search.rb +3 -2
  16. data/lib/intercom/article.rb +7 -0
  17. data/lib/intercom/base_collection_proxy.rb +72 -0
  18. data/lib/intercom/client.rb +32 -25
  19. data/lib/intercom/client_collection_proxy.rb +17 -39
  20. data/lib/intercom/collection.rb +7 -0
  21. data/lib/intercom/company.rb +8 -0
  22. data/lib/intercom/contact.rb +21 -3
  23. data/lib/intercom/conversation.rb +5 -0
  24. data/lib/intercom/data_attribute.rb +7 -0
  25. data/lib/intercom/deprecated_leads_collection_proxy.rb +22 -0
  26. data/lib/intercom/deprecated_resources.rb +13 -0
  27. data/lib/intercom/errors.rb +6 -0
  28. data/lib/intercom/extended_api_operations/segments.rb +3 -1
  29. data/lib/intercom/extended_api_operations/tags.rb +3 -1
  30. data/lib/intercom/lead.rb +21 -0
  31. data/lib/intercom/lib/typed_json_deserializer.rb +42 -37
  32. data/lib/intercom/note.rb +4 -0
  33. data/lib/intercom/request.rb +38 -32
  34. data/lib/intercom/scroll_collection_proxy.rb +33 -38
  35. data/lib/intercom/search_collection_proxy.rb +30 -65
  36. data/lib/intercom/section.rb +23 -0
  37. data/lib/intercom/service/article.rb +20 -0
  38. data/lib/intercom/service/base_service.rb +7 -0
  39. data/lib/intercom/service/collection.rb +24 -0
  40. data/lib/intercom/service/company.rb +0 -12
  41. data/lib/intercom/service/contact.rb +21 -10
  42. data/lib/intercom/service/conversation.rb +12 -3
  43. data/lib/intercom/service/data_attribute.rb +20 -0
  44. data/lib/intercom/service/lead.rb +41 -0
  45. data/lib/intercom/service/note.rb +4 -8
  46. data/lib/intercom/service/section.rb +7 -0
  47. data/lib/intercom/service/subscription.rb +2 -2
  48. data/lib/intercom/service/tag.rb +9 -9
  49. data/lib/intercom/service/visitor.rb +17 -8
  50. data/lib/intercom/tag.rb +4 -0
  51. data/lib/intercom/traits/api_resource.rb +44 -18
  52. data/lib/intercom/traits/dirty_tracking.rb +8 -1
  53. data/lib/intercom/user.rb +12 -3
  54. data/lib/intercom/utils.rb +13 -2
  55. data/lib/intercom/version.rb +1 -1
  56. data/lib/intercom/visitor.rb +0 -2
  57. data/spec/spec_helper.rb +843 -520
  58. data/spec/unit/intercom/admin_spec.rb +2 -2
  59. data/spec/unit/intercom/article_spec.rb +40 -0
  60. data/spec/unit/intercom/base_collection_proxy_spec.rb +30 -0
  61. data/spec/unit/intercom/client_collection_proxy_spec.rb +41 -41
  62. data/spec/unit/intercom/client_spec.rb +28 -25
  63. data/spec/unit/intercom/collection_spec.rb +32 -0
  64. data/spec/unit/intercom/company_spec.rb +13 -15
  65. data/spec/unit/intercom/contact_spec.rb +344 -33
  66. data/spec/unit/intercom/conversation_spec.rb +55 -7
  67. data/spec/unit/intercom/count_spec.rb +4 -4
  68. data/spec/unit/intercom/data_attribute_spec.rb +40 -0
  69. data/spec/unit/intercom/deprecated_leads_collection_proxy_spec.rb +17 -0
  70. data/spec/unit/intercom/event_spec.rb +9 -11
  71. data/spec/unit/intercom/job_spec.rb +24 -24
  72. data/spec/unit/intercom/lead_spec.rb +57 -0
  73. data/spec/unit/intercom/lib/flat_store_spec.rb +22 -20
  74. data/spec/unit/intercom/message_spec.rb +1 -1
  75. data/spec/unit/intercom/note_spec.rb +4 -10
  76. data/spec/unit/intercom/request_spec.rb +10 -1
  77. data/spec/unit/intercom/scroll_collection_proxy_spec.rb +40 -39
  78. data/spec/unit/intercom/search_collection_proxy_spec.rb +32 -28
  79. data/spec/unit/intercom/section_spec.rb +32 -0
  80. data/spec/unit/intercom/segment_spec.rb +2 -2
  81. data/spec/unit/intercom/subscription_spec.rb +5 -6
  82. data/spec/unit/intercom/tag_spec.rb +22 -14
  83. data/spec/unit/intercom/team_spec.rb +2 -2
  84. data/spec/unit/intercom/traits/api_resource_spec.rb +107 -52
  85. data/spec/unit/intercom/user_spec.rb +224 -226
  86. data/spec/unit/intercom/visitor_spec.rb +49 -0
  87. data/spec/unit/intercom_spec.rb +5 -3
  88. metadata +34 -8
  89. data/lib/intercom/customer.rb +0 -10
  90. data/lib/intercom/service/customer.rb +0 -14
  91. data/spec/unit/intercom/visitors_spec.rb +0 -61
@@ -1,38 +1,35 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Intercom
2
4
  class MisconfiguredClientError < StandardError; end
3
5
  class Client
4
6
  include Options
5
- attr_reader :base_url, :rate_limit_details, :username_part, :password_part, :handle_rate_limit, :timeouts, :api_version
7
+ include DeprecatedResources
8
+ attr_reader :base_url, :rate_limit_details, :token, :handle_rate_limit, :timeouts, :api_version
6
9
 
7
10
  class << self
8
11
  def set_base_url(base_url)
9
- return Proc.new do |o|
12
+ proc do |o|
10
13
  old_url = o.base_url
11
14
  o.send(:base_url=, base_url)
12
- Proc.new { |obj| set_base_url(old_url).call(o) }
15
+ proc { |_obj| set_base_url(old_url).call(o) }
13
16
  end
14
17
  end
15
18
 
16
19
  def set_timeouts(open_timeout: nil, read_timeout: nil)
17
- return Proc.new do |o|
20
+ proc do |o|
18
21
  old_timeouts = o.timeouts
19
22
  timeouts = {}
20
23
  timeouts[:open_timeout] = open_timeout if open_timeout
21
24
  timeouts[:read_timeout] = read_timeout if read_timeout
22
25
  o.send(:timeouts=, timeouts)
23
- Proc.new { |obj| set_timeouts(old_timeouts).call(o) }
26
+ proc { |_obj| set_timeouts(old_timeouts).call(o) }
24
27
  end
25
28
  end
26
29
  end
27
30
 
28
- def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil, base_url:'https://api.intercom.io', handle_rate_limit: false, api_version: nil)
29
- if token
30
- @username_part = token
31
- @password_part = ""
32
- else
33
- @username_part = app_id
34
- @password_part = api_key
35
- end
31
+ def initialize(token: nil, base_url: 'https://api.intercom.io', handle_rate_limit: false, api_version: nil)
32
+ @token = token
36
33
  validate_credentials!
37
34
 
38
35
  @api_version = api_version
@@ -51,6 +48,10 @@ module Intercom
51
48
  Intercom::Service::Admin.new(self)
52
49
  end
53
50
 
51
+ def articles
52
+ Intercom::Service::Article.new(self)
53
+ end
54
+
54
55
  def companies
55
56
  Intercom::Service::Company.new(self)
56
57
  end
@@ -67,10 +68,6 @@ module Intercom
67
68
  Intercom::Service::Counts.new(self)
68
69
  end
69
70
 
70
- def customers
71
- Intercom::Service::Customer.new(self)
72
- end
73
-
74
71
  def events
75
72
  Intercom::Service::Event.new(self)
76
73
  end
@@ -91,6 +88,10 @@ module Intercom
91
88
  Intercom::Service::Segment.new(self)
92
89
  end
93
90
 
91
+ def sections
92
+ Intercom::Service::Section.new(self)
93
+ end
94
+
94
95
  def tags
95
96
  Intercom::Service::Tag.new(self)
96
97
  end
@@ -111,6 +112,14 @@ module Intercom
111
112
  Intercom::Service::Job.new(self)
112
113
  end
113
114
 
115
+ def data_attributes
116
+ Intercom::Service::DataAttribute.new(self)
117
+ end
118
+
119
+ def collections
120
+ Intercom::Service::Collection.new(self)
121
+ end
122
+
114
123
  def get(path, params)
115
124
  execute_request Intercom::Request.get(path, params)
116
125
  end
@@ -130,25 +139,23 @@ module Intercom
130
139
  private
131
140
 
132
141
  def validate_credentials!
133
- error = MisconfiguredClientError.new("app_id and api_key must not be nil")
134
- fail error if @username_part.nil?
142
+ error = MisconfiguredClientError.new('an access token must be provided')
143
+ raise error if @token.nil?
135
144
  end
136
145
 
137
146
  def validate_api_version!
138
- error = MisconfiguredClientError.new("api_version must be either nil or a valid API version")
139
- fail error if (@api_version && Gem::Version.new(@api_version) < Gem::Version.new('1.0'))
147
+ error = MisconfiguredClientError.new('api_version must be either nil or a valid API version')
148
+ raise error if @api_version && @api_version != 'Unstable' && Gem::Version.new(@api_version) < Gem::Version.new('1.0')
140
149
  end
141
150
 
142
151
  def execute_request(request)
143
152
  request.handle_rate_limit = handle_rate_limit
144
- request.execute(@base_url, username: @username_part, secret: @password_part, api_version: @api_version, **timeouts)
153
+ request.execute(@base_url, token: @token, api_version: @api_version, **timeouts)
145
154
  ensure
146
155
  @rate_limit_details = request.rate_limit_details
147
156
  end
148
157
 
149
- def base_url=(new_url)
150
- @base_url = new_url
151
- end
158
+ attr_writer :base_url
152
159
 
153
160
  def timeouts=(timeouts)
154
161
  @timeouts = @timeouts.merge(timeouts)
@@ -1,71 +1,49 @@
1
- require "intercom/utils"
1
+ # frozen_string_literal: true
2
2
 
3
- module Intercom
4
- class ClientCollectionProxy
5
-
6
- attr_reader :resource_name, :finder_url, :resource_class
7
-
8
- def initialize(resource_name, finder_details: {}, client:)
9
- @resource_name = resource_name
10
- @resource_class = Utils.constantize_resource_name(resource_name)
11
- @finder_url = (finder_details[:url] || "/#{@resource_name}")
12
- @finder_params = (finder_details[:params] || {})
13
- @client = client
14
- end
3
+ require 'intercom/utils'
4
+ require 'intercom/base_collection_proxy'
15
5
 
6
+ module Intercom
7
+ class ClientCollectionProxy < BaseCollectionProxy
16
8
  def each(&block)
17
9
  next_page = nil
18
10
  current_page = nil
19
11
  loop do
20
- if next_page
21
- response_hash = @client.get(next_page, {})
22
- else
23
- response_hash = @client.get(@finder_url, @finder_params)
24
- end
25
- raise Intercom::HttpError.new('Http Error - No response entity returned') unless response_hash
12
+ response_hash = fetch(next_page)
13
+ raise Intercom::HttpError, 'Http Error - No response entity returned' unless response_hash
14
+
26
15
  current_page = extract_current_page(response_hash)
27
16
  deserialize_response_hash(response_hash, block)
28
17
  next_page = extract_next_link(response_hash)
29
- break if next_page.nil? or (@finder_params[:page] and (current_page >= @finder_params[:page]))
18
+ break if next_page.nil? || (@params[:page] && (current_page >= @params[:page]))
30
19
  end
31
20
  self
32
21
  end
33
22
 
34
- def [](target_index)
35
- self.each_with_index do |item, index|
36
- return item if index == target_index
23
+ def fetch(next_page)
24
+ if next_page
25
+ @client.get(next_page, {})
26
+ else
27
+ @client.get(@url, @params)
37
28
  end
38
- nil
39
29
  end
40
30
 
41
- include Enumerable
42
-
43
31
  private
44
32
 
45
- def deserialize_response_hash(response_hash, block)
46
- top_level_type = response_hash.delete('type')
47
- if resource_name == 'subscriptions'
48
- top_level_entity_key = 'items'
49
- else
50
- top_level_entity_key = Utils.entity_key_from_type(top_level_type)
51
- end
52
- response_hash[top_level_entity_key].each do |object_json|
53
- block.call Lib::TypedJsonDeserializer.new(object_json).deserialize
54
- end
55
- end
56
-
57
33
  def paging_info_present?(response_hash)
58
34
  !!(response_hash['pages'] && response_hash['pages']['type'])
59
35
  end
60
36
 
61
37
  def extract_next_link(response_hash)
62
38
  return nil unless paging_info_present?(response_hash)
39
+
63
40
  paging_info = response_hash.delete('pages')
64
- paging_info["next"]
41
+ paging_info['next']
65
42
  end
66
43
 
67
44
  def extract_current_page(response_hash)
68
45
  return nil unless paging_info_present?(response_hash)
46
+
69
47
  response_hash['pages']['page']
70
48
  end
71
49
  end
@@ -0,0 +1,7 @@
1
+ require 'intercom/traits/api_resource'
2
+
3
+ module Intercom
4
+ class Collection
5
+ include Traits::ApiResource
6
+ end
7
+ end
@@ -1,10 +1,18 @@
1
1
  require 'intercom/traits/incrementable_attributes'
2
2
  require 'intercom/traits/api_resource'
3
+ require 'intercom/api_operations/nested_resource'
3
4
 
4
5
  module Intercom
5
6
  class Company
6
7
  include Traits::IncrementableAttributes
7
8
  include Traits::ApiResource
9
+ include ApiOperations::NestedResource
10
+
11
+ nested_resource_methods :contact, operations: %i[list]
12
+
13
+ def self.collection_proxy_class
14
+ Intercom::ClientCollectionProxy
15
+ end
8
16
 
9
17
  def identity_vars ; [:id, :company_id] ; end
10
18
  def flat_store_attributes ; [:custom_attributes] ; end
@@ -1,11 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'intercom/traits/incrementable_attributes'
1
4
  require 'intercom/traits/api_resource'
5
+ require 'intercom/api_operations/nested_resource'
2
6
 
3
7
  module Intercom
4
8
  class Contact
9
+ include Traits::IncrementableAttributes
5
10
  include Traits::ApiResource
11
+ include ApiOperations::NestedResource
12
+
13
+ nested_resource_methods :tag, operations: %i[add delete list]
14
+ nested_resource_methods :note, operations: %i[create list]
15
+ nested_resource_methods :company, operations: %i[add delete list]
16
+
17
+ def self.collection_proxy_class
18
+ Intercom::BaseCollectionProxy
19
+ end
20
+
21
+ def identity_vars
22
+ [:id]
23
+ end
6
24
 
7
- def identity_vars ; [:email, :user_id] ; end
8
- def flat_store_attributes ; [:custom_attributes] ; end
9
- def update_verb; 'put' ; end
25
+ def flat_store_attributes
26
+ [:custom_attributes]
27
+ end
10
28
  end
11
29
  end
@@ -1,7 +1,12 @@
1
1
  require 'intercom/traits/api_resource'
2
+ require 'intercom/api_operations/nested_resource'
2
3
 
3
4
  module Intercom
4
5
  class Conversation
5
6
  include Traits::ApiResource
7
+ include ApiOperations::NestedResource
8
+
9
+ nested_resource_methods :tag, operations: %i[add delete]
10
+ nested_resource_methods :contact, operations: %i[add delete], path: :customers
6
11
  end
7
12
  end
@@ -0,0 +1,7 @@
1
+ require 'intercom/traits/api_resource'
2
+
3
+ module Intercom
4
+ class DataAttribute
5
+ include Traits::ApiResource
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Intercom
4
+ class DeprecatedLeadsCollectionProxy < ClientCollectionProxy
5
+ def fetch(next_page)
6
+ response_hash = if next_page
7
+ @client.get(next_page, {})
8
+ else
9
+ @client.get(@url, @params)
10
+ end
11
+ transform(response_hash)
12
+ end
13
+
14
+ def transform(response_hash)
15
+ response_hash['type'] = 'lead.list'
16
+ leads_list = response_hash.delete('contacts')
17
+ leads_list.each { |lead| lead['type'] = 'lead' }
18
+ response_hash['leads'] = leads_list
19
+ response_hash
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Intercom
4
+ module DeprecatedResources
5
+ def deprecated__leads
6
+ Intercom::Service::Lead.new(self)
7
+ end
8
+
9
+ def deprecated__users
10
+ Intercom::Service::User.new(self)
11
+ end
12
+ end
13
+ end
@@ -42,6 +42,9 @@ module Intercom
42
42
  # Raised when we have bad gateway errors.
43
43
  class BadGatewayError < IntercomError; end
44
44
 
45
+ # Raised when we have gateway timeout errors.
46
+ class GatewayTimeoutError < IntercomError; end
47
+
45
48
  # Raised when we experience a socket read timeout
46
49
  class ServiceUnavailableError < IntercomError; end
47
50
 
@@ -93,6 +96,9 @@ module Intercom
93
96
  # Raised when a CDA is invalid
94
97
  class InvalidDocumentError < IntercomError; end
95
98
 
99
+ # Raised when a merge is invalid
100
+ class InvalidMergeError < IntercomError; end
101
+
96
102
  #
97
103
  # Non-public errors (internal to the gem)
98
104
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'intercom/client_collection_proxy'
2
4
  require 'intercom/utils'
3
5
 
@@ -6,7 +8,7 @@ module Intercom
6
8
  module Segments
7
9
  def by_segment(id)
8
10
  collection_name = Utils.resource_class_to_collection_name(collection_class)
9
- ClientCollectionProxy.new(collection_name, finder_details: {url: "/#{collection_name}?segment_id=#{id}"}, client: @client)
11
+ ClientCollectionProxy.new(collection_name, collection_class, details: { url: "/#{collection_name}?segment_id=#{id}" }, client: @client)
10
12
  end
11
13
  end
12
14
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'intercom/client_collection_proxy'
2
4
  require 'intercom/utils'
3
5
 
@@ -6,7 +8,7 @@ module Intercom
6
8
  module Tags
7
9
  def by_tag(id)
8
10
  collection_name = Utils.resource_class_to_collection_name(collection_class)
9
- ClientCollectionProxy.new(collection_name, finder_details: {url: "/#{collection_name}?tag_id=#{id}"}, client: @client)
11
+ ClientCollectionProxy.new(collection_name, collection_class, details: { url: "/#{collection_name}?tag_id=#{id}" }, client: @client)
10
12
  end
11
13
  end
12
14
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'intercom/traits/api_resource'
4
+
5
+ module Intercom
6
+ class Lead
7
+ include Traits::ApiResource
8
+
9
+ def identity_vars
10
+ %i[email user_id]
11
+ end
12
+
13
+ def flat_store_attributes
14
+ [:custom_attributes]
15
+ end
16
+
17
+ def update_verb
18
+ 'put'
19
+ end
20
+ end
21
+ end
@@ -1,54 +1,59 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'intercom/utils'
2
4
 
3
5
  module Intercom
4
6
  module Lib
5
- # Responsibility: To decide whether we are deserializing a collection or an
6
- # entity of a particular type and to dispatch deserialization
7
- class TypedJsonDeserializer
8
- attr_reader :json
9
-
10
- def initialize(json)
11
- @json = json
12
- end
7
+ # Responsibility: To decide whether we are deserializing a collection or an
8
+ # entity of a particular type and to dispatch deserialization
9
+ class TypedJsonDeserializer
10
+ attr_reader :json
11
+
12
+ def initialize(json, client)
13
+ @json = json
14
+ @client = client
15
+ end
13
16
 
14
- def deserialize
15
- if blank_object_type?(object_type)
16
- raise DeserializationError, "No type field was found to facilitate deserialization"
17
- elsif list_object_type?(object_type)
18
- deserialize_collection(json[object_entity_key])
19
- else # singular object type
20
- deserialize_object(json)
21
- end
17
+ def deserialize
18
+ if blank_object_type?(object_type)
19
+ raise DeserializationError, 'No type field was found to facilitate deserialization'
20
+ elsif list_object_type?(object_type)
21
+ deserialize_collection(json[object_entity_key])
22
+ else # singular object type
23
+ deserialize_object(json)
22
24
  end
25
+ end
23
26
 
24
- private
27
+ private
25
28
 
26
- def blank_object_type?(object_type)
27
- object_type.nil? || object_type == ''
28
- end
29
+ def blank_object_type?(object_type)
30
+ object_type.nil? || object_type == ''
31
+ end
29
32
 
30
- def list_object_type?(object_type)
31
- object_type.end_with?('.list')
32
- end
33
+ def list_object_type?(object_type)
34
+ object_type.end_with?('.list')
35
+ end
33
36
 
34
- def deserialize_collection(collection_json)
35
- return [] if collection_json == nil
36
- collection_json.map { |item_json| TypedJsonDeserializer.new(item_json).deserialize }
37
- end
37
+ def deserialize_collection(collection_json)
38
+ return [] if collection_json.nil?
38
39
 
39
- def deserialize_object(object_json)
40
- entity_class = Utils.constantize_singular_resource_name(object_entity_key)
41
- entity_class.from_api(object_json)
42
- end
40
+ collection_json.map { |item_json| TypedJsonDeserializer.new(item_json, @client).deserialize }
41
+ end
43
42
 
44
- def object_type
45
- @object_type ||= json['type']
46
- end
43
+ def deserialize_object(object_json)
44
+ entity_class = Utils.constantize_singular_resource_name(object_entity_key)
45
+ deserialized = entity_class.from_api(object_json)
46
+ deserialized.client = @client
47
+ deserialized
48
+ end
47
49
 
48
- def object_entity_key
49
- @object_entity_key ||= Utils.entity_key_from_type(object_type)
50
- end
50
+ def object_type
51
+ @object_type ||= json['type']
52
+ end
51
53
 
54
+ def object_entity_key
55
+ @object_entity_key ||= Utils.entity_key_from_type(object_type)
52
56
  end
57
+ end
53
58
  end
54
59
  end