intercom 3.5.10 → 4.1.2

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.
Files changed (101) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +35 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +5 -0
  4. data/Gemfile +1 -4
  5. data/README.md +418 -216
  6. data/RELEASING.md +9 -0
  7. data/Rakefile +1 -1
  8. data/changes.txt +141 -0
  9. data/intercom.gemspec +1 -2
  10. data/lib/intercom.rb +34 -19
  11. data/lib/intercom/api_operations/archive.rb +16 -0
  12. data/lib/intercom/api_operations/delete.rb +4 -1
  13. data/lib/intercom/api_operations/find.rb +5 -2
  14. data/lib/intercom/api_operations/find_all.rb +4 -3
  15. data/lib/intercom/api_operations/list.rb +4 -1
  16. data/lib/intercom/api_operations/load.rb +4 -2
  17. data/lib/intercom/api_operations/nested_resource.rb +70 -0
  18. data/lib/intercom/api_operations/request_hard_delete.rb +12 -0
  19. data/lib/intercom/api_operations/save.rb +6 -4
  20. data/lib/intercom/api_operations/scroll.rb +4 -5
  21. data/lib/intercom/api_operations/search.rb +18 -0
  22. data/lib/intercom/article.rb +7 -0
  23. data/lib/intercom/base_collection_proxy.rb +72 -0
  24. data/lib/intercom/client.rb +66 -18
  25. data/lib/intercom/client_collection_proxy.rb +17 -39
  26. data/lib/intercom/collection.rb +7 -0
  27. data/lib/intercom/company.rb +8 -0
  28. data/lib/intercom/contact.rb +22 -3
  29. data/lib/intercom/conversation.rb +5 -0
  30. data/lib/intercom/data_attribute.rb +7 -0
  31. data/lib/intercom/deprecated_leads_collection_proxy.rb +22 -0
  32. data/lib/intercom/deprecated_resources.rb +13 -0
  33. data/lib/intercom/errors.rb +44 -4
  34. data/lib/intercom/extended_api_operations/segments.rb +3 -1
  35. data/lib/intercom/extended_api_operations/tags.rb +3 -1
  36. data/lib/intercom/lead.rb +21 -0
  37. data/lib/intercom/lib/typed_json_deserializer.rb +42 -37
  38. data/lib/intercom/note.rb +4 -0
  39. data/lib/intercom/request.rb +162 -95
  40. data/lib/intercom/scroll_collection_proxy.rb +38 -42
  41. data/lib/intercom/search_collection_proxy.rb +47 -0
  42. data/lib/intercom/section.rb +23 -0
  43. data/lib/intercom/segment.rb +4 -0
  44. data/lib/intercom/service/article.rb +20 -0
  45. data/lib/intercom/service/base_service.rb +13 -0
  46. data/lib/intercom/service/collection.rb +24 -0
  47. data/lib/intercom/service/company.rb +4 -2
  48. data/lib/intercom/service/contact.rb +29 -6
  49. data/lib/intercom/service/conversation.rb +23 -2
  50. data/lib/intercom/service/data_attribute.rb +20 -0
  51. data/lib/intercom/service/event.rb +12 -0
  52. data/lib/intercom/service/lead.rb +41 -0
  53. data/lib/intercom/service/note.rb +4 -8
  54. data/lib/intercom/service/section.rb +7 -0
  55. data/lib/intercom/service/tag.rb +8 -8
  56. data/lib/intercom/service/team.rb +17 -0
  57. data/lib/intercom/service/user.rb +4 -2
  58. data/lib/intercom/service/visitor.rb +15 -6
  59. data/lib/intercom/tag.rb +4 -0
  60. data/lib/intercom/team.rb +7 -0
  61. data/lib/intercom/traits/api_resource.rb +48 -27
  62. data/lib/intercom/traits/dirty_tracking.rb +8 -1
  63. data/lib/intercom/user.rb +12 -3
  64. data/lib/intercom/utils.rb +13 -2
  65. data/lib/intercom/version.rb +1 -1
  66. data/lib/intercom/visitor.rb +0 -2
  67. data/spec/spec_helper.rb +881 -436
  68. data/spec/unit/intercom/admin_spec.rb +2 -2
  69. data/spec/unit/intercom/article_spec.rb +40 -0
  70. data/spec/unit/intercom/base_collection_proxy_spec.rb +30 -0
  71. data/spec/unit/intercom/client_collection_proxy_spec.rb +41 -41
  72. data/spec/unit/intercom/client_spec.rb +76 -9
  73. data/spec/unit/intercom/collection_spec.rb +32 -0
  74. data/spec/unit/intercom/company_spec.rb +29 -21
  75. data/spec/unit/intercom/contact_spec.rb +365 -29
  76. data/spec/unit/intercom/conversation_spec.rb +70 -7
  77. data/spec/unit/intercom/count_spec.rb +4 -4
  78. data/spec/unit/intercom/data_attribute_spec.rb +40 -0
  79. data/spec/unit/intercom/deprecated_leads_collection_proxy_spec.rb +17 -0
  80. data/spec/unit/intercom/event_spec.rb +25 -8
  81. data/spec/unit/intercom/job_spec.rb +24 -24
  82. data/spec/unit/intercom/lead_spec.rb +57 -0
  83. data/spec/unit/intercom/lib/flat_store_spec.rb +22 -20
  84. data/spec/unit/intercom/message_spec.rb +1 -1
  85. data/spec/unit/intercom/note_spec.rb +4 -10
  86. data/spec/unit/intercom/request_spec.rb +150 -9
  87. data/spec/unit/intercom/scroll_collection_proxy_spec.rb +40 -39
  88. data/spec/unit/intercom/search_collection_proxy_spec.rb +60 -0
  89. data/spec/unit/intercom/section_spec.rb +32 -0
  90. data/spec/unit/intercom/segment_spec.rb +2 -2
  91. data/spec/unit/intercom/subscription_spec.rb +5 -6
  92. data/spec/unit/intercom/tag_spec.rb +22 -14
  93. data/spec/unit/intercom/team_spec.rb +21 -0
  94. data/spec/unit/intercom/traits/api_resource_spec.rb +129 -47
  95. data/spec/unit/intercom/user_spec.rb +227 -217
  96. data/spec/unit/intercom/visitor_spec.rb +49 -0
  97. data/spec/unit/intercom_spec.rb +5 -3
  98. metadata +63 -26
  99. data/.travis.yml +0 -6
  100. data/lib/intercom/extended_api_operations/users.rb +0 -16
  101. data/spec/unit/intercom/visitors_spec.rb +0 -61
@@ -0,0 +1,12 @@
1
+ require 'intercom/utils'
2
+
3
+ module Intercom
4
+ module ApiOperations
5
+ module RequestHardDelete
6
+ def request_hard_delete(object)
7
+ @client.post("/user_delete_requests", {intercom_user_id: object.id})
8
+ object
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'intercom/utils'
2
4
  require 'ext/sliceable_hash'
3
5
 
@@ -8,8 +10,8 @@ module Intercom
8
10
  private_constant :PARAMS_NOT_PROVIDED
9
11
 
10
12
  def create(params = PARAMS_NOT_PROVIDED)
11
- if collection_class.ancestors.include?(Intercom::Contact) && params == PARAMS_NOT_PROVIDED
12
- params = Hash.new
13
+ if collection_class.ancestors.include?(Intercom::Lead) && params == PARAMS_NOT_PROVIDED
14
+ params = {}
13
15
  elsif params == PARAMS_NOT_PROVIDED
14
16
  raise ArgumentError, '.create requires 1 parameter'
15
17
  end
@@ -20,17 +22,17 @@ module Intercom
20
22
  end
21
23
 
22
24
  def save(object)
23
- collection_name = Utils.resource_class_to_collection_name(collection_class)
24
25
  if id_present?(object) && !posted_updates?(object)
25
26
  response = @client.put("/#{collection_name}/#{object.id}", object.to_submittable_hash)
26
27
  else
27
28
  response = @client.post("/#{collection_name}", object.to_submittable_hash.merge(identity_hash(object)))
28
29
  end
30
+ object.client = @client
29
31
  object.from_response(response) if response # may be nil we received back a 202
30
32
  end
31
33
 
32
34
  def identity_hash(object)
33
- object.respond_to?(:identity_vars) ? SliceableHash.new(object.to_hash).slice(*(object.identity_vars.map(&:to_s))) : {}
35
+ object.respond_to?(:identity_vars) ? SliceableHash.new(object.to_hash).slice(*object.identity_vars.map(&:to_s)) : {}
34
36
  end
35
37
 
36
38
  private
@@ -1,17 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'intercom/scroll_collection_proxy'
2
4
  require 'intercom/utils'
3
5
 
4
6
  module Intercom
5
7
  module ApiOperations
6
8
  module Scroll
7
-
8
- def scroll()
9
- collection_name = Utils.resource_class_to_collection_name(collection_class)
9
+ def scroll
10
10
  finder_details = {}
11
11
  finder_details[:url] = "/#{collection_name}"
12
- ScrollCollectionProxy.new(collection_name, finder_details: finder_details, client: @client)
12
+ ScrollCollectionProxy.new(collection_name, collection_class, details: finder_details, client: @client)
13
13
  end
14
-
15
14
  end
16
15
  end
17
16
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'intercom/search_collection_proxy'
4
+ require 'intercom/utils'
5
+
6
+ module Intercom
7
+ module ApiOperations
8
+ module Search
9
+ def search(params)
10
+ search_details = {
11
+ url: "/#{collection_name}/search",
12
+ params: params
13
+ }
14
+ SearchCollectionProxy.new(collection_name, collection_class, details: search_details, client: @client)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ require 'intercom/traits/api_resource'
2
+
3
+ module Intercom
4
+ class Article
5
+ include Traits::ApiResource
6
+ end
7
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'intercom/utils'
4
+
5
+ module Intercom
6
+ class BaseCollectionProxy
7
+ attr_reader :resource_name, :url, :resource_class
8
+
9
+ def initialize(resource_name, resource_class, details: {}, client:, method: 'get')
10
+ @resource_name = resource_name
11
+ @resource_class = resource_class
12
+ @url = (details[:url] || "/#{@resource_name}")
13
+ @params = (details[:params] || {})
14
+ @client = client
15
+ @method = method
16
+ end
17
+
18
+ def each(&block)
19
+ loop do
20
+ response_hash = @client.public_send(@method, @url, payload)
21
+ raise Intercom::HttpError, 'Http Error - No response entity returned' unless response_hash
22
+
23
+ deserialize_response_hash(response_hash, block)
24
+ break unless has_next_link?(response_hash)
25
+ end
26
+ self
27
+ end
28
+
29
+ def [](target_index)
30
+ each_with_index do |item, index|
31
+ return item if index == target_index
32
+ end
33
+ nil
34
+ end
35
+
36
+ include Enumerable
37
+
38
+ private
39
+
40
+ def deserialize_response_hash(response_hash, block)
41
+ top_level_type = response_hash.delete('type')
42
+ top_level_entity_key = if resource_name == 'subscriptions'
43
+ 'items'
44
+ else
45
+ Utils.entity_key_from_type(top_level_type)
46
+ end
47
+ response_hash[top_level_entity_key].each do |object_json|
48
+ block.call Lib::TypedJsonDeserializer.new(object_json, @client).deserialize
49
+ end
50
+ end
51
+
52
+ def has_next_link?(response_hash)
53
+ paging_info = response_hash.delete('pages')
54
+ return false unless paging_info
55
+
56
+ paging_next = paging_info['next']
57
+ if paging_next
58
+ @params[:starting_after] = paging_next['starting_after']
59
+ return true
60
+ else
61
+ return false
62
+ end
63
+ end
64
+
65
+ def payload
66
+ payload = {}
67
+ payload[:per_page] = @params[:per_page] if @params[:per_page]
68
+ payload[:starting_after] = @params[:starting_after] if @params[:starting_after]
69
+ payload
70
+ end
71
+ end
72
+ end
@@ -1,37 +1,57 @@
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
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
- end
16
18
 
17
- def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil)
18
- if token
19
- @username_part = token
20
- @password_part = ""
21
- else
22
- @username_part = app_id
23
- @password_part = api_key
19
+ def set_timeouts(open_timeout: nil, read_timeout: nil)
20
+ proc do |o|
21
+ old_timeouts = o.timeouts
22
+ timeouts = {}
23
+ timeouts[:open_timeout] = open_timeout if open_timeout
24
+ timeouts[:read_timeout] = read_timeout if read_timeout
25
+ o.send(:timeouts=, timeouts)
26
+ proc { |_obj| set_timeouts(old_timeouts).call(o) }
27
+ end
24
28
  end
29
+ end
30
+
31
+ def initialize(token: nil, base_url: 'https://api.intercom.io', handle_rate_limit: false, api_version: nil)
32
+ @token = token
25
33
  validate_credentials!
26
34
 
27
- @base_url = 'https://api.intercom.io'
35
+ @api_version = api_version
36
+ validate_api_version!
37
+
38
+ @base_url = base_url
28
39
  @rate_limit_details = {}
40
+ @handle_rate_limit = handle_rate_limit
41
+ @timeouts = {
42
+ open_timeout: 30,
43
+ read_timeout: 90
44
+ }
29
45
  end
30
46
 
31
47
  def admins
32
48
  Intercom::Service::Admin.new(self)
33
49
  end
34
50
 
51
+ def articles
52
+ Intercom::Service::Article.new(self)
53
+ end
54
+
35
55
  def companies
36
56
  Intercom::Service::Company.new(self)
37
57
  end
@@ -68,14 +88,26 @@ module Intercom
68
88
  Intercom::Service::Segment.new(self)
69
89
  end
70
90
 
91
+ def sections
92
+ Intercom::Service::Section.new(self)
93
+ end
94
+
71
95
  def tags
72
96
  Intercom::Service::Tag.new(self)
73
97
  end
74
98
 
99
+ def teams
100
+ Intercom::Service::Team.new(self)
101
+ end
102
+
75
103
  def users
76
104
  Intercom::Service::User.new(self)
77
105
  end
78
106
 
107
+ def leads
108
+ Intercom::Service::Lead.new(self)
109
+ end
110
+
79
111
  def visitors
80
112
  Intercom::Service::Visitor.new(self)
81
113
  end
@@ -84,6 +116,14 @@ module Intercom
84
116
  Intercom::Service::Job.new(self)
85
117
  end
86
118
 
119
+ def data_attributes
120
+ Intercom::Service::DataAttribute.new(self)
121
+ end
122
+
123
+ def collections
124
+ Intercom::Service::Collection.new(self)
125
+ end
126
+
87
127
  def get(path, params)
88
128
  execute_request Intercom::Request.get(path, params)
89
129
  end
@@ -103,18 +143,26 @@ module Intercom
103
143
  private
104
144
 
105
145
  def validate_credentials!
106
- error = MisconfiguredClientError.new("app_id and api_key must not be nil")
107
- fail error if @username_part.nil?
146
+ error = MisconfiguredClientError.new('an access token must be provided')
147
+ raise error if @token.nil?
148
+ end
149
+
150
+ def validate_api_version!
151
+ error = MisconfiguredClientError.new('api_version must be either nil or a valid API version')
152
+ raise error if @api_version && @api_version != 'Unstable' && Gem::Version.new(@api_version) < Gem::Version.new('1.0')
108
153
  end
109
154
 
110
155
  def execute_request(request)
111
- result = request.execute(@base_url, username: @username_part, secret: @password_part)
156
+ request.handle_rate_limit = handle_rate_limit
157
+ request.execute(@base_url, token: @token, api_version: @api_version, **timeouts)
158
+ ensure
112
159
  @rate_limit_details = request.rate_limit_details
113
- result
114
160
  end
115
161
 
116
- def base_url=(new_url)
117
- @base_url = new_url
162
+ attr_writer :base_url
163
+
164
+ def timeouts=(timeouts)
165
+ @timeouts = @timeouts.merge(timeouts)
118
166
  end
119
167
  end
120
168
  end
@@ -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,30 @@
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
+ nested_resource_methods :segment, operations: %i[list]
17
+
18
+ def self.collection_proxy_class
19
+ Intercom::BaseCollectionProxy
20
+ end
21
+
22
+ def identity_vars
23
+ [:id]
24
+ end
6
25
 
7
- def identity_vars ; [:email, :user_id] ; end
8
- def flat_store_attributes ; [:custom_attributes] ; end
9
- def update_verb; 'put' ; end
26
+ def flat_store_attributes
27
+ [:custom_attributes]
28
+ end
10
29
  end
11
30
  end