intercom 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -8
  3. data/Gemfile +3 -0
  4. data/README.md +208 -52
  5. data/changes.txt +3 -0
  6. data/intercom.gemspec +2 -2
  7. data/lib/ext/hash.rb +18 -0
  8. data/lib/intercom.rb +38 -43
  9. data/lib/intercom/api_operations/count.rb +16 -0
  10. data/lib/intercom/api_operations/delete.rb +15 -0
  11. data/lib/intercom/api_operations/find.rb +22 -0
  12. data/lib/intercom/api_operations/find_all.rb +33 -0
  13. data/lib/intercom/api_operations/list.rb +17 -0
  14. data/lib/intercom/api_operations/load.rb +15 -0
  15. data/lib/intercom/api_operations/save.rb +44 -0
  16. data/lib/intercom/collection_proxy.rb +66 -0
  17. data/lib/intercom/company.rb +29 -0
  18. data/lib/intercom/conversation.rb +15 -0
  19. data/lib/intercom/count.rb +21 -0
  20. data/lib/intercom/errors.rb +52 -0
  21. data/lib/intercom/event.rb +4 -101
  22. data/lib/intercom/extended_api_operations/reply.rb +16 -0
  23. data/lib/intercom/extended_api_operations/tags.rb +14 -0
  24. data/lib/intercom/extended_api_operations/users.rb +17 -0
  25. data/lib/intercom/generic_handlers/base_handler.rb +22 -0
  26. data/lib/intercom/generic_handlers/count.rb +59 -0
  27. data/lib/intercom/generic_handlers/tag.rb +71 -0
  28. data/lib/intercom/generic_handlers/tag_find_all.rb +47 -0
  29. data/lib/intercom/lib/dynamic_accessors.rb +59 -0
  30. data/lib/intercom/lib/dynamic_accessors_on_method_missing.rb +53 -0
  31. data/lib/intercom/lib/flat_store.rb +31 -0
  32. data/lib/intercom/lib/typed_json_deserializer.rb +52 -0
  33. data/lib/intercom/message.rb +9 -0
  34. data/lib/intercom/note.rb +14 -42
  35. data/lib/intercom/request.rb +40 -4
  36. data/lib/intercom/segment.rb +14 -0
  37. data/lib/intercom/tag.rb +19 -78
  38. data/lib/intercom/traits/api_resource.rb +120 -0
  39. data/lib/intercom/traits/dirty_tracking.rb +33 -0
  40. data/lib/intercom/traits/generic_handler_binding.rb +29 -0
  41. data/lib/intercom/traits/incrementable_attributes.rb +23 -0
  42. data/lib/intercom/user.rb +25 -361
  43. data/lib/intercom/utils.rb +50 -0
  44. data/lib/intercom/version.rb +1 -1
  45. data/spec/spec_helper.rb +64 -33
  46. data/spec/unit/intercom/collection_proxy_spec.rb +34 -0
  47. data/spec/unit/intercom/event_spec.rb +25 -0
  48. data/spec/unit/intercom/{flat_store_spec.rb → lib/flat_store_spec.rb} +7 -7
  49. data/spec/unit/intercom/note_spec.rb +5 -4
  50. data/spec/unit/intercom/tag_spec.rb +3 -3
  51. data/spec/unit/intercom/traits/api_resource_spec.rb +79 -0
  52. data/spec/unit/intercom/user_spec.rb +101 -119
  53. data/spec/unit/intercom_spec.rb +7 -7
  54. metadata +50 -26
  55. data/lib/intercom/flat_store.rb +0 -27
  56. data/lib/intercom/hashable_object.rb +0 -22
  57. data/lib/intercom/impression.rb +0 -63
  58. data/lib/intercom/message_thread.rb +0 -189
  59. data/lib/intercom/requires_parameters.rb +0 -10
  60. data/lib/intercom/social_profile.rb +0 -24
  61. data/lib/intercom/unix_timestamp_unwrapper.rb +0 -12
  62. data/lib/intercom/user_collection_proxy.rb +0 -52
  63. data/lib/intercom/user_resource.rb +0 -82
  64. data/spec/integration/fixtures/v1-user.json +0 -45
  65. data/spec/integration/fixtures/v1-users-impression.json +0 -3
  66. data/spec/integration/fixtures/v1-users-message_thread.json +0 -44
  67. data/spec/integration/fixtures/v1-users-message_threads.json +0 -46
  68. data/spec/integration/fixtures/v1-users-note.json +0 -49
  69. data/spec/integration/fixtures/v1-users.json +0 -144
  70. data/spec/integration/intercom_api_integration_spec.rb +0 -134
  71. data/spec/unit/intercom/impression_spec.rb +0 -18
  72. data/spec/unit/intercom/message_thread_spec.rb +0 -74
  73. data/spec/unit/intercom/user_collection_proxy_spec.rb +0 -46
  74. data/spec/unit/intercom/user_event_spec.rb +0 -83
  75. data/spec/unit/intercom/user_resource_spec.rb +0 -13
@@ -0,0 +1,9 @@
1
+ require 'intercom/api_operations/save'
2
+ require 'intercom/traits/api_resource'
3
+
4
+ module Intercom
5
+ class Message
6
+ include ApiOperations::Save
7
+ include Traits::ApiResource
8
+ end
9
+ end
data/lib/intercom/note.rb CHANGED
@@ -1,45 +1,17 @@
1
- require 'intercom/user_resource'
1
+ require 'intercom/api_operations/save'
2
+ require 'intercom/api_operations/list'
3
+ require 'intercom/api_operations/find_all'
4
+ require 'intercom/api_operations/find'
5
+ require 'intercom/api_operations/load'
6
+ require 'intercom/traits/api_resource'
2
7
 
3
8
  module Intercom
4
-
5
- ##
6
- # Represents a note on a user
7
- #
8
- # A note contains a note (the text of the note you want to leave)
9
- #
10
- # == Examples
11
- #
12
- # note = Intercom::Note.create(:email => "person@example.com", :body => "This is the note you want to make on the user account")
13
-
14
- # You can also create a note and save it like this:
15
- # note = Intercom::Note.new
16
- # note.body = "This is the note you want to make on the user account"
17
- # note.save
18
-
19
- class Note < UserResource
20
- ##
21
- # Creates a new Note using params and saves it
22
- # @see #save
23
- def self.create(params)
24
- requires_parameters(params, %W(body))
25
- Note.new(params).save
26
- end
27
-
28
- ##
29
- # Records a note on a user of your application
30
- def save
31
- response = Intercom.post("/v1/users/notes", to_hash)
32
- self.update_from_api_response(response)
33
- end
34
-
35
- ##
36
- # Set the text of the note for the user
37
- def body=(body)
38
- @attributes["body"] = body
39
- end
40
-
41
- def user
42
- User.from_api(@attributes['user'])
43
- end
9
+ class Note
10
+ include ApiOperations::Save
11
+ include ApiOperations::List
12
+ include ApiOperations::FindAll
13
+ include ApiOperations::Find
14
+ include ApiOperations::Load
15
+ include Traits::ApiResource
44
16
  end
45
- end
17
+ end
@@ -12,7 +12,6 @@ module Intercom
12
12
 
13
13
  def set_common_headers(method, base_uri)
14
14
  method.basic_auth(CGI.unescape(base_uri.user), CGI.unescape(base_uri.password))
15
- method.add_field('Accept', 'application/json')
16
15
  method.add_field('AcceptEncoding', 'gzip, deflate')
17
16
  end
18
17
 
@@ -40,7 +39,7 @@ module Intercom
40
39
  end
41
40
 
42
41
  def self.default_headers
43
- {'Accept-Encoding' => 'gzip, deflate', 'Accept' => 'application/json'}
42
+ {'Accept-Encoding' => 'gzip, deflate', 'Accept' => 'application/vnd.intercom.3+json'}
44
43
  end
45
44
 
46
45
  def client(uri)
@@ -60,9 +59,13 @@ module Intercom
60
59
  set_common_headers(net_http_method, base_uri)
61
60
  client(base_uri).start do |http|
62
61
  response = http.request(net_http_method)
63
- raise_errors_on_failure(response)
64
62
  decoded = decode(response['content-encoding'], response.body)
65
- JSON.parse(decoded) unless decoded.strip.empty?
63
+ unless decoded.strip.empty?
64
+ parsed_body = JSON.parse(decoded)
65
+ raise_application_errors_on_failure(parsed_body, response.code.to_i) if parsed_body['type'] == 'error.list'
66
+ end
67
+ raise_errors_on_failure(response)
68
+ parsed_body
66
69
  end
67
70
  rescue Timeout::Error
68
71
  raise Intercom::ServiceUnavailableError
@@ -87,6 +90,39 @@ module Intercom
87
90
  end
88
91
  end
89
92
 
93
+ def raise_application_errors_on_failure(error_list_details, http_code)
94
+ # Currently, we don't support multiple errors
95
+ error_details = error_list_details['errors'].first
96
+ error_code = error_details['type'] || error_details['code']
97
+ parsed_http_code = (http_code > 0 ? http_code : nil)
98
+ error_context = {
99
+ :http_code => parsed_http_code,
100
+ :application_error_code => error_code
101
+ }
102
+ case error_code
103
+ when 'unauthorized'
104
+ raise Intercom::AuthenticationError.new(error_details['message'], error_context)
105
+ when "bad_request", "missing_parameter", 'parameter_invalid'
106
+ raise Intercom::BadRequestError.new(error_details['message'], error_context)
107
+ when "not_found"
108
+ raise Intercom::ResourceNotFound.new(error_details['message'], error_context)
109
+ when "rate_limit_exceeded"
110
+ raise Intercom::RateLimitExceeded.new(error_details['message'], error_context)
111
+ when nil, ''
112
+ raise Intercom::UnexpectedError.new(message_for_unexpected_error_without_type(error_details, parsed_http_code), error_context)
113
+ else
114
+ raise Intercom::UnexpectedError.new(message_for_unexpected_error_with_type(error_details, parsed_http_code), error_context)
115
+ end
116
+ end
117
+
118
+ def message_for_unexpected_error_with_type(error_details, parsed_http_code)
119
+ "The error of type '#{error_details['type']}' is not recognized. It occurred with the message: #{error_details['message']} and http_code: '#{parsed_http_code}'. Please contact Intercom with these details."
120
+ end
121
+
122
+ def message_for_unexpected_error_without_type(error_details, parsed_http_code)
123
+ "An unexpected error occured. It occurred with the message: #{error_details['message']} and http_code: '#{parsed_http_code}'. Please contact Intercom with these details."
124
+ end
125
+
90
126
  def self.append_query_string_to_url(url, params)
91
127
  return url if params.empty?
92
128
  query_string = params.map { |k, v| "#{k.to_s}=#{CGI::escape(v.to_s)}" }.join('&')
@@ -0,0 +1,14 @@
1
+ require 'intercom/api_operations/count'
2
+ require 'intercom/api_operations/find'
3
+ require 'intercom/api_operations/save'
4
+ require 'intercom/traits/api_resource'
5
+
6
+ module Intercom
7
+ class Segment
8
+ include ApiOperations::List
9
+ include ApiOperations::Find
10
+ include ApiOperations::Save
11
+ include ApiOperations::Count
12
+ include Traits::ApiResource
13
+ end
14
+ end
data/lib/intercom/tag.rb CHANGED
@@ -1,82 +1,23 @@
1
- require 'intercom/requires_parameters'
2
- require 'intercom/hashable_object'
1
+ require 'intercom/api_operations/count'
2
+ require 'intercom/api_operations/save'
3
+ require 'intercom/api_operations/list'
4
+ require 'intercom/api_operations/find'
5
+ require 'intercom/api_operations/find_all'
6
+ require 'intercom/traits/api_resource'
7
+ require 'intercom/traits/generic_handler_binding'
8
+ require 'intercom/generic_handlers/tag'
9
+ require 'intercom/generic_handlers/tag_find_all'
3
10
 
4
11
  module Intercom
5
-
6
- ##
7
- # Represents a tag
8
- #
9
- # A tag consists of a name, and (optionally) users that you would like to tag. Returns details about the tag and a count of the number of users currently tagged.
10
- #
11
- # == Examples
12
- #
13
- # tag = Intercom::Tag.create(:name => "Super Tag", :user_ids => ['abc123', 'def456'])
14
- # tag = Intercom::Tag.create(:name => "Super Tag", :emails => ['bob@example.com', 'joe@example.com'])
15
- #
16
- # You can also create a tag and save it like this:
17
- # tag = Intercom::Tag.new
18
- # tag.name = "Super Tag"
19
- # tag.user_ids = ['abc123', 'def456']
20
- # tag.tag_or_untag = "tag"
21
- # tag.save
22
- #
23
- # Or update a tag and save it like this:
24
- # tag = Intercom::Tag.find_by_name "Super Tag"
25
- # tag.user_ids = ['abc123']
26
- # tag.tag_or_untag = "untag"
27
- # tag.save
28
-
29
12
  class Tag
30
- extend RequiresParameters
31
- include HashableObject
32
-
33
- attr_accessor :name
34
- attr_reader :segment, :tagged_user_count, :id
35
- attr_writer :user_ids, :emails, :tag_or_untag
36
-
37
- def initialize(attributes={})
38
- from_hash(attributes)
39
- end
40
-
41
- ##
42
- # Finds a Tag using params
43
- def self.find(params)
44
- response = Intercom.get("/v1/tags", params)
45
- from_api(response)
46
- end
47
-
48
- def self.from_api(api_response)
49
- tag = Tag.new
50
- tag.from_hash(api_response)
51
- tag.displayable_self
52
- end
53
-
54
- ##
55
- # Finds a Tag using a name
56
- def self.find_by_name(name)
57
- find({:name => name})
58
- end
59
-
60
- ##
61
- # Creates a new Tag using params and saves it
62
- # @see #save
63
- def self.create(params)
64
- requires_parameters(params, %W(name))
65
- Tag.new(params).save
66
- end
67
-
68
- ##
69
- # Saves a Tag on your application
70
- def save
71
- response = Intercom.post("/v1/tags", to_wire)
72
- self.from_hash(response)
73
- displayable_self
74
- end
75
-
76
- ##
77
- # Create a new clean instance to return (only showing the readable attributes)
78
- def displayable_self
79
- Tag.new(self.displayable_attributes)
80
- end
13
+ include ApiOperations::Count
14
+ include ApiOperations::Save
15
+ include ApiOperations::List
16
+ include ApiOperations::Find
17
+ include ApiOperations::FindAll
18
+ include Traits::ApiResource
19
+ include Traits::GenericHandlerBinding
20
+ include GenericHandlers::Tag
21
+ include GenericHandlers::TagFindAll
81
22
  end
82
- end
23
+ end
@@ -0,0 +1,120 @@
1
+ require 'intercom/lib/flat_store'
2
+ require 'intercom/lib/dynamic_accessors'
3
+ require 'intercom/lib/dynamic_accessors_on_method_missing'
4
+ require 'intercom/traits/dirty_tracking'
5
+ require 'intercom/lib/typed_json_deserializer'
6
+
7
+ module Intercom
8
+ module Traits
9
+
10
+ module ApiResource
11
+ include DirtyTracking
12
+
13
+ attr_accessor :id
14
+
15
+ def initialize(attributes = {})
16
+ from_hash(attributes)
17
+ end
18
+
19
+ def from_response(response)
20
+ from_hash(response)
21
+ reset_changed_fields!
22
+ self
23
+ end
24
+
25
+ def from_hash(hash)
26
+ hash.each do |attribute, value|
27
+ next if type_field?(attribute)
28
+ initialize_property(attribute, value)
29
+ end
30
+ initialize_missing_flat_store_attributes if respond_to? :flat_store_attributes
31
+ self
32
+ end
33
+
34
+ def to_hash
35
+ instance_variables_excluding_dirty_tracking_field.inject({}) do |hash, variable|
36
+ hash[variable.to_s.delete("@")] = instance_variable_get(variable)
37
+ hash
38
+ end
39
+ end
40
+
41
+ def to_submittable_hash
42
+ submittable_hash = {}
43
+ to_hash.each do |attribute, value|
44
+ submittable_hash[attribute] = value if submittable_attribute?(attribute, value)
45
+ end
46
+ submittable_hash
47
+ end
48
+
49
+ def method_missing(method_sym, *arguments, &block)
50
+ Lib::DynamicAccessorsOnMethodMissing.new(method_sym, *arguments, self).
51
+ define_accessors_or_call { super }
52
+ end
53
+
54
+ def flat_store_attribute?(attribute)
55
+ (respond_to?(:flat_store_attributes)) && (flat_store_attributes.map(&:to_s).include?(attribute.to_s))
56
+ end
57
+
58
+ private
59
+
60
+ def initialize_property(attribute, value)
61
+ Lib::DynamicAccessors.define_accessors(attribute, value, self) unless accessors_already_defined?(attribute)
62
+ set_property(attribute, value)
63
+ end
64
+
65
+ def accessors_already_defined?(attribute)
66
+ respond_to?(attribute) && respond_to?("#{attribute}=")
67
+ end
68
+
69
+ def set_property(attribute, value)
70
+ if typed_value?(value)
71
+ value_to_set = Intercom::Lib::TypedJsonDeserializer.new(value).deserialize
72
+ elsif flat_store_attribute?(attribute)
73
+ value_to_set = Intercom::Lib::FlatStore.new(value)
74
+ else
75
+ value_to_set = value
76
+ end
77
+ call_setter_for_attribute(attribute, value_to_set)
78
+ end
79
+
80
+ def typed_value?(value)
81
+ value.is_a? Hash and !!value['type']
82
+ end
83
+
84
+ def call_setter_for_attribute(attribute, value)
85
+ setter_method = "#{attribute.to_s}="
86
+ self.send(setter_method, value)
87
+ end
88
+
89
+ def type_field?(attribute)
90
+ attribute == 'type'
91
+ end
92
+
93
+ def initialize_missing_flat_store_attributes
94
+ flat_store_attributes.each do |attribute|
95
+ unless instance_variables_excluding_dirty_tracking_field.map(&:to_s).include? "@#{attribute}"
96
+ initialize_property(attribute, {})
97
+ end
98
+ end
99
+ end
100
+
101
+ def submittable_attribute?(attribute, value)
102
+ # FlatStores always submitted, even if not changed, as we don't track their dirtyness
103
+ value.is_a?(Intercom::Lib::FlatStore) || field_changed?(attribute)
104
+ end
105
+
106
+ module ClassMethods
107
+ def from_api(api_response)
108
+ object = self.new
109
+ object.from_response(api_response)
110
+ object
111
+ end
112
+ end
113
+
114
+ def self.included(base)
115
+ base.extend(ClassMethods)
116
+ end
117
+
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,33 @@
1
+ require 'set'
2
+
3
+ module Intercom
4
+ module Traits
5
+ module DirtyTracking
6
+
7
+ def reset_changed_fields!
8
+ @changed_fields = Set.new
9
+ end
10
+
11
+ def mark_fields_as_changed!(field_names)
12
+ @changed_fields ||= Set.new
13
+ field_names.each do |attr|
14
+ @changed_fields.add(attr.to_s)
15
+ end
16
+ end
17
+
18
+ def mark_field_as_changed!(field_name)
19
+ @changed_fields ||= Set.new
20
+ @changed_fields.add(field_name.to_s)
21
+ end
22
+
23
+ def field_changed?(field_name)
24
+ @changed_fields ||= Set.new
25
+ @changed_fields.include?(field_name.to_s)
26
+ end
27
+
28
+ def instance_variables_excluding_dirty_tracking_field
29
+ instance_variables.reject{|var| var == :@changed_fields}
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,29 @@
1
+ module Intercom
2
+ module Traits
3
+
4
+ # Allows us to have one class level method missing handler across all entities
5
+ # which can dispatch to the appropriate function based on the method name
6
+ module GenericHandlerBinding
7
+
8
+ module ClassMethods
9
+ def method_missing(method_sym, *arguments, &block)
10
+ if respond_to? :generic_tag and GenericHandlers::Tag.handles_method?(method_sym)
11
+ return generic_tag(method_sym, *arguments, block)
12
+ elsif respond_to? :generic_tag_find_all and GenericHandlers::TagFindAll.handles_method?(method_sym)
13
+ return generic_tag_find_all(method_sym, *arguments, block)
14
+ elsif respond_to? :generic_count and GenericHandlers::Count.handles_method?(method_sym)
15
+ return generic_count(method_sym, *arguments, block)
16
+ end
17
+ super
18
+ rescue Intercom::NoMethodMissingHandler
19
+ super
20
+ end
21
+ end
22
+
23
+ def self.included(base)
24
+ base.extend(ClassMethods)
25
+ end
26
+
27
+ end
28
+ end
29
+ end