From 2bb3cfb358908aeab55dad44df7601b5480632e6 Mon Sep 17 00:00:00 2001 From: gondalez Date: Fri, 1 May 2015 14:49:20 +0800 Subject: [PATCH 001/245] Add extra context to IntercomError * request_id that helps when contacting intercom support * 'field' that denotes the cause of a validation error --- lib/intercom/errors.rb | 10 ++++++---- lib/intercom/request.rb | 7 +++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index 2b220ec9..38ed1408 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -2,10 +2,12 @@ module Intercom # Base class exception from which all public Intercom exceptions will be derived class IntercomError < StandardError - attr_reader :http_code, :application_error_code - def initialize(message, http_code = nil, error_code = application_error_code) - @http_code = http_code - @application_error_code = error_code + attr_reader :http_code, :application_error_code, :field, :request_id + def initialize(message, context={}) + @http_code = context[:http_code] + @application_error_code = context[:application_error_code] + @field = context[:field] + @request_id = context[:request_id] super(message) end end diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 1f72e7b0..db1ea93d 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -74,7 +74,7 @@ def execute(target_base_url=nil) raise Intercom::ServiceConnectionError.new('Failed to connect to service [connection attempt timed out]') end end - + def decode_body(response) decode(response['content-encoding'], response.body) end @@ -124,10 +124,13 @@ def raise_application_errors_on_failure(error_list_details, http_code) # Currently, we don't support multiple errors error_details = error_list_details['errors'].first error_code = error_details['type'] || error_details['code'] + error_field = error_details['field'] parsed_http_code = (http_code > 0 ? http_code : nil) error_context = { :http_code => parsed_http_code, - :application_error_code => error_code + :application_error_code => error_code, + :field => error_field, + :request_id => error_list_details['request_id'] } case error_code when 'unauthorized', 'forbidden' From 3acba1c5797792a3dbb843d95d3fd4835f63958f Mon Sep 17 00:00:00 2001 From: gondalez Date: Fri, 1 May 2015 15:03:36 +0800 Subject: [PATCH 002/245] Implement IntercomError #inspect and #to_hash These come in handy when logging error detail. --- lib/intercom/errors.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index 38ed1408..c8f5623d 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -10,6 +10,17 @@ def initialize(message, context={}) @request_id = context[:request_id] super(message) end + def inspect + attributes = instance_variables.map do |var| + value = instance_variable_get(var).inspect + "#{var}=#{value}" + end + "##{self.class.name}:#{message} #{attributes.join(' ')}" + end + def to_hash + {message: message} + .merge(Hash[instance_variables.map{ |var| [var[1..-1], instance_variable_get(var)] }]) + end end # Raised when the credentials you provide don't match a valid account on Intercom. From a030bfcacd3919051423847df8b697f1a92536aa Mon Sep 17 00:00:00 2001 From: Jim Mellor Date: Wed, 23 Mar 2016 16:27:31 +0000 Subject: [PATCH 003/245] Force unzipped body encoding to UTF-8 --- lib/intercom/request.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 75abef91..b5b23ea8 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -105,7 +105,7 @@ def set_rate_limit_details(response) def decode(content_encoding, body) return body if (!body) || body.empty? || content_encoding != 'gzip' - Zlib::GzipReader.new(StringIO.new(body)).read + Zlib::GzipReader.new(StringIO.new(body)).read.force_encoding("utf-8") end def raise_errors_on_failure(res) From ce22a7294f58a712b89953d013bdcd9e7711e876 Mon Sep 17 00:00:00 2001 From: Danny Hiemstra Date: Thu, 7 Apr 2016 11:16:28 +0200 Subject: [PATCH 004/245] Get universal conversation count --- lib/intercom/service/count.rb | 4 +++- spec/unit/intercom/count_spec.rb | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/intercom/service/count.rb b/lib/intercom/service/count.rb index 38aca901..3fc206b1 100644 --- a/lib/intercom/service/count.rb +++ b/lib/intercom/service/count.rb @@ -15,7 +15,9 @@ def for_app end def for_type(type:, count: nil) - find(type: type, count: count) + params = {type: type} + params[:count] = count if count + find(params) end end end diff --git a/spec/unit/intercom/count_spec.rb b/spec/unit/intercom/count_spec.rb index 6d3f1c9d..11d9cb9f 100644 --- a/spec/unit/intercom/count_spec.rb +++ b/spec/unit/intercom/count_spec.rb @@ -14,4 +14,10 @@ counts = client.counts.for_type(type: 'user', count: 'segment') counts.user['segment'][4]["segment 1"].must_equal(1) end + + it 'should not include count param when nil' do + client.expects(:get).with("/counts", {type: 'conversation'}).returns(test_segment_count) + counts = client.counts.for_type(type: 'conversation') + counts.user['segment'][4]["segment 1"].must_equal(1) + end end From 84ce4a934b03d9b1f0d0c1d0780b17ced03c1ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vi=CC=81t=20Krchov?= Date: Wed, 18 May 2016 12:39:49 +0200 Subject: [PATCH 005/245] Add support to find admin by id --- README.md | 2 ++ lib/intercom/service/admin.rb | 2 ++ spec/spec_helper.rb | 9 +++++++++ spec/unit/intercom/admin_spec.rb | 5 +++++ 4 files changed, 18 insertions(+) diff --git a/README.md b/README.md index 467e23d2..a6b8765a 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,8 @@ intercom.users.submit_bulk_job(create_items: [{user_id: 25, email: "alice@exampl #### Admins ```ruby +# Find an admin by id +intercom.admins.find(:id => admin_id) # Iterate over all admins intercom.admins.all.each {|admin| puts admin.email } ``` diff --git a/lib/intercom/service/admin.rb b/lib/intercom/service/admin.rb index 4c7da142..352be7ca 100644 --- a/lib/intercom/service/admin.rb +++ b/lib/intercom/service/admin.rb @@ -1,10 +1,12 @@ require 'intercom/service/base_service' require 'intercom/api_operations/list' +require 'intercom/api_operations/find' module Intercom module Service class Admin < BaseService include ApiOperations::List + include ApiOperations::Find def collection_class Intercom::Admin diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b9c720e1..45f2fe3d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -71,6 +71,15 @@ def test_admin_list } end +def test_admin + { + "type" => "admin", + "id" => "1234", + "name" => "Hoban Washburne", + "email" => "wash@serenity.io" + } +end + def test_company { "type" => "company", diff --git a/spec/unit/intercom/admin_spec.rb b/spec/unit/intercom/admin_spec.rb index 46e6a7dc..143fb619 100644 --- a/spec/unit/intercom/admin_spec.rb +++ b/spec/unit/intercom/admin_spec.rb @@ -13,4 +13,9 @@ client.expects(:get).with("/admins", {}).returns(test_admin_list) client.admins.all.each { |a| } end + + it "gets an admin" do + client.expects(:get).with("/admins/1234", {}).returns(test_admin) + client.admins.find(:id => "1234") + end end From cd7e44c150c57a1a2a18c0654d3b809514d4d8d2 Mon Sep 17 00:00:00 2001 From: The Gitter Badger Date: Fri, 20 May 2016 12:34:30 +0000 Subject: [PATCH 006/245] Add Gitter badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 467e23d2..5e7ca822 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # intercom-ruby +[![Join the chat at https://gitter.im/intercom/intercom-ruby](https://badges.gitter.im/intercom/intercom-ruby.svg)](https://gitter.im/intercom/intercom-ruby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + Ruby bindings for the Intercom API (https://api.intercom.io). [API Documentation](https://api.intercom.io/docs) From 4a94c285034785f5099f907681b85b8081f5aab7 Mon Sep 17 00:00:00 2001 From: choran Date: Fri, 20 May 2016 15:49:47 +0000 Subject: [PATCH 007/245] Removing Gitter forum reference --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 5e7ca822..467e23d2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # intercom-ruby -[![Join the chat at https://gitter.im/intercom/intercom-ruby](https://badges.gitter.im/intercom/intercom-ruby.svg)](https://gitter.im/intercom/intercom-ruby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - Ruby bindings for the Intercom API (https://api.intercom.io). [API Documentation](https://api.intercom.io/docs) From ffaddb37f9c33e21eeb6756b84c9fdee658be6c8 Mon Sep 17 00:00:00 2001 From: Skaelv Date: Tue, 24 May 2016 14:39:38 +0100 Subject: [PATCH 008/245] Bump to 3.5.1 --- README.md | 2 +- lib/intercom/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 467e23d2..b6bc8cbe 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', "~> 3.4.0" + gem 'intercom', "~> 3.5.0" ## Basic Usage diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 6058df9f..34cc658b 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.4.0" + VERSION = "3.5.1" end From c7d5fc474ffd052dc7237fa832db0f67d1f5a9e2 Mon Sep 17 00:00:00 2001 From: Antoine Kevin Date: Tue, 24 May 2016 14:52:18 +0100 Subject: [PATCH 009/245] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6bc8cbe..ae2cf841 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', "~> 3.5.0" + gem 'intercom', "~> 3.5.1" ## Basic Usage From 8a1c506b68759d7494d52634cedda37d5ff36a98 Mon Sep 17 00:00:00 2001 From: Skaelv Date: Wed, 25 May 2016 17:40:34 +0100 Subject: [PATCH 010/245] update CHANGELOG --- changes.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changes.txt b/changes.txt index f7d24b47..1beafa8d 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,7 @@ +3.5.1 + - Support for 'visitors' + - Fix utf8 body parsing + 3.4.0 - Add a "token" keyword for OAuth clients From c1830969d936b7479e7f8383cc8148f2ca078a4f Mon Sep 17 00:00:00 2001 From: Antoine Kevin Date: Fri, 27 May 2016 11:40:40 +0100 Subject: [PATCH 011/245] Update README.md Add few lines about oauth --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ae2cf841..20d6b43f 100644 --- a/README.md +++ b/README.md @@ -30,12 +30,17 @@ Using bundler: ```ruby intercom = Intercom::Client.new(app_id: 'my_app_id', api_key: 'my_api_key') +``` + +You can get your `app_id` from the URL when you're logged into Intercom (it's the alphanumeric just after `/apps/`) and your API key from the API keys integration settings page (under your app settings - integrations in Intercom). +```ruby # With an OAuth token: intercom = Intercom::Client.new(token: 'my_token') ``` -You can get your `app_id` from the URL when you're logged into Intercom (it's the alphanumeric just after `/apps/`) and your API key from the API keys integration settings page (under your app settings - integrations in Intercom). +If you are building a third party application you can get your access_tokens by [setting-up-oauth](https://developers.intercom.io/page/setting-up-oauth) for Intercom. +You can also use the [omniauth-intercom lib](https://github.com/intercom/omniauth-intercom) which is a middleware helping you to handle the authentication process with Intercom. ### Resources From c06fcdcf4b92efffeae7c2e0494c80e9d857e523 Mon Sep 17 00:00:00 2001 From: Brian Mason Date: Wed, 8 Jun 2016 10:05:31 +0200 Subject: [PATCH 012/245] Fix name of base Intercom error class in README The base class for all intercom errors is `Intercom::IntercomError`, not `Intercom::Error`, as the README previously stated. --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 20d6b43f..4d46caac 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ intercom = Intercom::Client.new(app_id: 'my_app_id', api_key: 'my_api_key') You can get your `app_id` from the URL when you're logged into Intercom (it's the alphanumeric just after `/apps/`) and your API key from the API keys integration settings page (under your app settings - integrations in Intercom). -```ruby +```ruby # With an OAuth token: intercom = Intercom::Client.new(token: 'my_token') ``` @@ -444,10 +444,9 @@ intercom.jobs.errors(id: 'job_abcd1234') There are different styles for error handling - some people prefer exceptions; some prefer nil and check; some prefer error objects/codes. Balancing these preferences alongside our wish to provide an idiomatic gem has brought us to use the current mechanism of throwing specific exceptions. Our approach in the client is to propagate errors and signal our failure loudly so that erroneous data does not get propagated through our customers' systems - in other words, if you see a `Intercom::ServiceUnavailableError` you know where the problem is. -You do not need to deal with the HTTP response from an API call directly. If there is an unsuccessful response then an error that is a subclass of `Intercom::Error` will be raised. If desired, you can get at the http_code of an `Intercom::Error` via its `http_code` method. +You do not need to deal with the HTTP response from an API call directly. If there is an unsuccessful response then an error that is a subclass of `Intercom::IntercomError` will be raised. If desired, you can get at the http_code of an `Intercom::IntercomError` via its `http_code` method. -The list of different error subclasses are listed below. As they all inherit off Intercom::IntercomError you can choose to rescue Intercom::IntercomError or -else rescue the more specific error subclass. +The list of different error subclasses are listed below. As they all inherit off Intercom::IntercomError you can choose to rescue Intercom::IntercomError or else rescue the more specific error subclass. ```ruby Intercom::AuthenticationError From fbe2e38a3fa35ca498b3ce9b879828b7403d0a85 Mon Sep 17 00:00:00 2001 From: Bob Long Date: Thu, 23 Jun 2016 21:44:13 +0100 Subject: [PATCH 013/245] Pass extra params with #find (#262) Pass extra params with #find --- lib/intercom/api_operations/find.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/intercom/api_operations/find.rb b/lib/intercom/api_operations/find.rb index 52945b64..361a9d57 100644 --- a/lib/intercom/api_operations/find.rb +++ b/lib/intercom/api_operations/find.rb @@ -5,7 +5,8 @@ def find(params) raise BadRequestError, "#{self}#find takes a hash as its parameter but you supplied #{params.inspect}" unless params.is_a? Hash collection_name = Utils.resource_class_to_collection_name(collection_class) if params[:id] - response = @client.get("/#{collection_name}/#{params[:id]}", {}) + id = params.delete(:id) + response = @client.get("/#{collection_name}/#{id}", params) else response = @client.get("/#{collection_name}", params) end From ee8be5b89b20aba905e3df161490b91d4b37d92e Mon Sep 17 00:00:00 2001 From: choran Date: Tue, 5 Jul 2016 14:08:24 +0000 Subject: [PATCH 014/245] Adding feature for pagination in Ruby SDK --- lib/intercom/client_collection_proxy.rb | 9 ++- spec/spec_helper.rb | 72 +++++++++++++++++++ .../intercom/client_collection_proxy_spec.rb | 45 ++++++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) diff --git a/lib/intercom/client_collection_proxy.rb b/lib/intercom/client_collection_proxy.rb index c20597ba..fe261741 100644 --- a/lib/intercom/client_collection_proxy.rb +++ b/lib/intercom/client_collection_proxy.rb @@ -16,6 +16,7 @@ def initialize(resource_name, finder_details: {}, client:) def each(&block) next_page = nil + current_page = nil loop do if next_page response_hash = @client.get(next_page, {}) @@ -23,9 +24,10 @@ def each(&block) response_hash = @client.get(@finder_url, @finder_params) end raise Intercom::HttpError.new('Http Error - No response entity returned') unless response_hash + current_page = extract_current_page(response_hash) deserialize_response_hash(response_hash, block) next_page = extract_next_link(response_hash) - break if next_page.nil? + break if next_page.nil? or (@finder_params[:page] and (current_page >= @finder_params[:page])) end self end @@ -62,5 +64,10 @@ def extract_next_link(response_hash) paging_info = response_hash.delete('pages') paging_info["next"] end + + def extract_current_page(response_hash) + return nil unless paging_info_present?(response_hash) + response_hash['pages']['page'] + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b9c720e1..67e7bfbb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -57,6 +57,61 @@ def test_user(email="bob@example.com") } end +def test_user_dates(email="bob@example.com", created_at=1401970114, last_request_at=1401970113) + { + "type" =>"user", + "id" =>"aaaaaaaaaaaaaaaaaaaaaaaa", + "user_id" => 'id-from-customers-app', + "email" => email, + "name" => "Joe Schmoe", + "avatar" => {"type"=>"avatar", "image_url"=>"https://graph.facebook.com/1/picture?width=24&height=24"}, + "app_id" => "the-app-id", + "custom_attributes" => {"a" => "b", "b" => 2}, + "companies" => + {"type"=>"company.list", + "companies"=> + [{"type"=>"company", + "company_id"=>"123", + "id"=>"bbbbbbbbbbbbbbbbbbbbbbbb", + "app_id"=>"the-app-id", + "name"=>"Company 1", + "remote_created_at"=>1390936440, + "created_at"=>1401970114, + "updated_at"=>1401970114, + "last_request_at"=>1401970113, + "monthly_spend"=>0, + "session_count"=>0, + "user_count"=>1, + "tag_ids"=>[], + "custom_attributes"=>{"category"=>"Tech"}}]}, + "session_count" => 123, + "unsubscribed_from_emails" => true, + "last_request_at" =>last_request_at, + "created_at" =>created_at, + "remote_created_at" =>1393613864, + "updated_at" =>1401970114, + "user_agent_data" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", + "social_profiles" =>{"type"=>"social_profile.list", + "social_profiles" => [ + {"type" => "social_profile", "name" => "twitter", "url" => "http://twitter.com/abc", "username" => "abc", "id" => nil}, + {"type" => "social_profile", "name" => "twitter", "username" => "abc2", "url" => "http://twitter.com/abc2", "id" => nil}, + {"type" => "social_profile", "name" => "facebook", "url" => "http://facebook.com/abc", "username" => "abc", "id" => "1234242"}, + {"type" => "social_profile", "name" => "quora", "url" => "http://facebook.com/abc", "username" => "abc", "id" => "1234242"} + ]}, + "location_data"=> + {"type"=>"location_data", + "city_name"=> 'Dublin', + "continent_code"=> 'EU', + "country_name"=> 'Ireland', + "latitude"=> '90', + "longitude"=> '10', + "postal_code"=> 'IE', + "region_name"=> 'Europe', + "timezone"=> '+1000', + "country_code" => "IRL"} + } +end + def test_admin_list { "type" => "admin.list", @@ -161,6 +216,23 @@ def page_of_users(include_next_link= false) } end +def users_pagination(include_next_link=false, per_page=0, page=0, total_pages=0, total_count=0, user_list=[]) + { + "type"=>"user.list", + "pages"=> + { + "type"=>"pages", + "next"=> (include_next_link ? "https://api.intercom.io/users?per_page=" \ + + per_page.to_s + "&page=" + (page+1).to_s : nil), + "page"=>page, + "per_page"=>per_page, + "total_pages"=>total_pages + }, + "users"=> user_list, + "total_count"=>total_count + } +end + def test_conversation { "type" => "conversation", diff --git a/spec/unit/intercom/client_collection_proxy_spec.rb b/spec/unit/intercom/client_collection_proxy_spec.rb index d46897af..ee682deb 100644 --- a/spec/unit/intercom/client_collection_proxy_spec.rb +++ b/spec/unit/intercom/client_collection_proxy_spec.rb @@ -32,4 +32,49 @@ client.expects(:get).with("/users", {:tag_name => 'Taggart J'}).returns(page_of_users(false)) client.users.find_all(:tag_name => 'Taggart J').map(&:email).must_equal %W(user1@example.com user2@example.com user3@example.com) end + + it "supports single page pagination" do + users = [test_user("user1@example.com"), test_user("user2@example.com"), test_user("user3@example.com"), + test_user("user4@example.com"), test_user("user5@example.com"), test_user("user6@example.com"), + test_user("user7@example.com"), test_user("user8@example.com"), test_user("user9@example.com"), + test_user("user10@example.com")] + client.expects(:get).with("/users", {:type=>'users', :per_page => 10, :page => 1}).returns(users_pagination(false, per_page=10, page=1, total_pages=1, total_count=10, user_list=users)) + result = client.users.find_all(:type=>'users', :per_page => 10, :page => 1).map {|user| user.email } + result.must_equal %W(user1@example.com user2@example.com user3@example.com user4@example.com user5@example.com user6@example.com user7@example.com user8@example.com user9@example.com user10@example.com) + end + + it "supports multi page pagination" do + users = [test_user("user3@example.com"), test_user("user4@example.com")] + client.expects(:get).with("/users", {:type=>'users', :per_page => 2, :page => 3}).returns(users_pagination(true, per_page=2, page=3, total_pages=5, total_count=10, user_list=users)) + result = client.users.find_all(:type=>'users', :per_page => 2, :page => 3).map {|user| user.email } + result.must_equal %W(user3@example.com user4@example.com) + end + + it "works with page out of range request" do + users = [] + client.expects(:get).with("/users", {:type=>'users', :per_page => 2, :page => 30}).returns(users_pagination(true, per_page=2, page=30, total_pages=2, total_count=3, user_list=users)) + result = client.users.find_all(:type=>'users', :per_page => 2, :page => 30).map {|user| user.email } + result.must_equal %W() + end + + it "works with asc order" do + test_date=1457337600 + time_increment=1000 + users = [test_user_dates(email="user1@example.com", created_at=test_date), test_user_dates(email="user2@example.com", created_at=test_date-time_increment), + test_user_dates(email="user3@example.com", created_at=test_date-2*time_increment), test_user_dates(email="user4@example.com", created_at=test_date-3*time_increment)] + client.expects(:get).with("/users", {:type=>'users', :per_page => 4, :page => 5, :order => "asc", :sort => "created_at"}).returns(users_pagination(true, per_page=4, page=5, total_pages=6, total_count=30, user_list=users)) + result = client.users.find_all(:type=>'users', :per_page => 4, :page => 5, :order => "asc", :sort => "created_at").map(&:email) + result.must_equal %W(user1@example.com user2@example.com user3@example.com user4@example.com) + end + + it "works with desc order" do + test_date=1457337600 + time_increment=1000 + users = [test_user_dates(email="user4@example.com", created_at=3*test_date), test_user_dates(email="user3@example.com", created_at=test_date-2*time_increment), + test_user_dates(email="user2@example.com", created_at=test_date-time_increment), test_user_dates(email="user1@example.com", created_at=test_date)] + client.expects(:get).with("/users", {:type=>'users', :per_page => 4, :page => 5, :order => "desc", :sort => "created_at"}).returns(users_pagination(true, per_page=4, page=5, total_pages=6, total_count=30, user_list=users)) + result = client.users.find_all(:type=>'users', :per_page => 4, :page => 5, :order => "desc", :sort => "created_at").map {|user| user.email } + result.must_equal %W(user4@example.com user3@example.com user2@example.com user1@example.com) + end + end From ec6ed9d56cd8b97e9c95ef3192b7604d604bbf11 Mon Sep 17 00:00:00 2001 From: choran Date: Fri, 8 Jul 2016 10:15:09 +0000 Subject: [PATCH 015/245] Updating Ruby version and changes.txt file --- changes.txt | 3 +++ lib/intercom/version.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/changes.txt b/changes.txt index 1beafa8d..559252dd 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.5.2 +- Add Support for pagination + 3.5.1 - Support for 'visitors' - Fix utf8 body parsing diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 34cc658b..19d68d3f 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.1" + VERSION = "3.5.2" end From 54111cbb55d57ebf79c13d3049800c75f15c3f35 Mon Sep 17 00:00:00 2001 From: Jeffrey Dill Date: Mon, 11 Jul 2016 22:20:22 -0400 Subject: [PATCH 016/245] Add decrement method to incrementable traits --- README.md | 3 ++ .../traits/incrementable_attributes.rb | 6 ++++ spec/unit/intercom/user_spec.rb | 28 +++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/README.md b/README.md index 4d46caac..29e0ff47 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,9 @@ intercom.users.save(user) # Perform incrementing user.increment('karma') intercom.users.save(user) +# Perform decrementing +user.decrement('karma', 5) +intercom.users.save(user) # Iterate over all users intercom.users.all.each {|user| puts %Q(#{user.email} - #{user.custom_attributes["average_monthly_spend"]}) } intercom.users.all.map {|user| user.email } diff --git a/lib/intercom/traits/incrementable_attributes.rb b/lib/intercom/traits/incrementable_attributes.rb index b3b671f0..d9452a5e 100644 --- a/lib/intercom/traits/incrementable_attributes.rb +++ b/lib/intercom/traits/incrementable_attributes.rb @@ -7,6 +7,12 @@ def increment(key, value=1) existing_value ||= 0 self.custom_attributes[key] = existing_value + value end + + def decrement(key, value=1) + existing_value = self.custom_attributes[key] || 0 + self.custom_attributes[key] = existing_value - value + end + end end end diff --git a/spec/unit/intercom/user_spec.rb b/spec/unit/intercom/user_spec.rb index 1550db76..cff318a5 100644 --- a/spec/unit/intercom/user_spec.rb +++ b/spec/unit/intercom/user_spec.rb @@ -148,6 +148,34 @@ end end + describe "decrementing custom_attributes fields" do + before :each do + @now = Time.now + @user = Intercom::User.new("email" => "jo@example.com", :user_id => "i-1224242", :custom_attributes => {"mad" => 123, "another" => 432, "other" => @now.to_i, :thing => "yay"}) + end + + it "decrements down by 1 with no args" do + @user.decrement("mad") + @user.to_hash["custom_attributes"]["mad"].must_equal 122 + end + + it "decrements down by given value" do + @user.decrement("mad", 3) + @user.to_hash["custom_attributes"]["mad"].must_equal 120 + end + + it "can decrement new custom data fields" do + @user.decrement("new_field", 5) + @user.to_hash["custom_attributes"]["new_field"].must_equal -5 + end + + it "can call decrement on the same key twice and decrement by 2" do + @user.decrement("mad") + @user.decrement("mad") + @user.to_hash["custom_attributes"]["mad"].must_equal 121 + end + end + it "fetches a user" do client.expects(:get).with("/users", {"email" => "bo@example.com"}).returns(test_user) user = client.users.find("email" => "bo@example.com") From 9c6853e725001fc1329381daf12aedc42e32784a Mon Sep 17 00:00:00 2001 From: choran Date: Tue, 26 Jul 2016 10:15:12 +0000 Subject: [PATCH 017/245] Changing id to user_id to make reame example a little clearer --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4d46caac..796205c6 100644 --- a/README.md +++ b/README.md @@ -240,7 +240,7 @@ intercom.messages.create({ }, :to => { :type => "user", - :id => "5678" + :user_id => "5678" } }) From 089f7893ac19eb6807bc544fdeded07e79a8de94 Mon Sep 17 00:00:00 2001 From: Reid Beels Date: Wed, 27 Jul 2016 11:54:48 +0200 Subject: [PATCH 018/245] Suppress printing of users during test runs --- spec/unit/intercom/user_spec.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/spec/unit/intercom/user_spec.rb b/spec/unit/intercom/user_spec.rb index 1550db76..09650072 100644 --- a/spec/unit/intercom/user_spec.rb +++ b/spec/unit/intercom/user_spec.rb @@ -238,8 +238,17 @@ it 'can print users without crashing' do client.expects(:get).with("/users", {"email" => "bo@example.com"}).returns(test_user) user = client.users.find("email" => "bo@example.com") - puts user - p user + + begin + orignal_stdout = $stdout + $stdout = StringIO.new + + puts user + p user + + ensure + $stdout = orignal_stdout + end end describe 'bulk operations' do From ef9f4236da63ee6de797dde48f75467b4f498f84 Mon Sep 17 00:00:00 2001 From: Boris Rorsvort Date: Wed, 3 Aug 2016 10:24:41 +0200 Subject: [PATCH 019/245] Update links to API --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 796205c6..1858e940 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # intercom-ruby -Ruby bindings for the Intercom API (https://api.intercom.io). +Ruby bindings for the Intercom API (https://developers.intercom.io/reference). -[API Documentation](https://api.intercom.io/docs) +[API Documentation](https://developers.intercom.io/docs) [Gem Documentation](http://rubydoc.info/github/intercom/intercom-ruby/master/frames) From 19e92dc386f20d97d2ddb4b1e3924a33c4e6762d Mon Sep 17 00:00:00 2001 From: Danny Hiemstra Date: Sat, 13 Aug 2016 10:24:41 +0200 Subject: [PATCH 020/245] Test global conversation count response --- spec/spec_helper.rb | 12 ++++++++++++ spec/unit/intercom/count_spec.rb | 9 +++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b9c720e1..c8667361 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -448,6 +448,18 @@ def test_segment_count } end +def test_conversation_count + { + "type" => "count", + "conversation" => { + "assigned" => 1, + "closed" => 15, + "open" => 1, + "unassigned" => 0 + } + } +end + def error_on_modify_frozen RUBY_VERSION =~ /1.8/ ? TypeError : RuntimeError end diff --git a/spec/unit/intercom/count_spec.rb b/spec/unit/intercom/count_spec.rb index 11d9cb9f..e0990018 100644 --- a/spec/unit/intercom/count_spec.rb +++ b/spec/unit/intercom/count_spec.rb @@ -16,8 +16,13 @@ end it 'should not include count param when nil' do - client.expects(:get).with("/counts", {type: 'conversation'}).returns(test_segment_count) + client.expects(:get).with("/counts", {type: 'conversation'}).returns(test_conversation_count) counts = client.counts.for_type(type: 'conversation') - counts.user['segment'][4]["segment 1"].must_equal(1) + counts.conversation.must_equal({ + "assigned" => 1, + "closed" => 15, + "open" => 1, + "unassigned" => 0 + }) end end From 7f1105237607e49d802898f8e2bfdf4088c928fb Mon Sep 17 00:00:00 2001 From: Antoine Kevin Date: Mon, 15 Aug 2016 13:55:06 +0100 Subject: [PATCH 021/245] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 796205c6..98ed6911 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', "~> 3.5.1" + gem 'intercom', "~> 3.5.2" ## Basic Usage From 120454a3613a9b32d083d96f8b5f220cde6fbcec Mon Sep 17 00:00:00 2001 From: choran Date: Thu, 25 Aug 2016 17:29:55 +0000 Subject: [PATCH 022/245] Adding support for global counts of conversations --- changes.txt | 3 +++ lib/intercom/version.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/changes.txt b/changes.txt index 559252dd..e937e239 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.5.3 +- Add support for global conversation counts + 3.5.2 - Add Support for pagination diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 19d68d3f..1ff15812 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.2" + VERSION = "3.5.3" end From a3cf070746f53cb3892bcdc386c62aa2a3e8a87e Mon Sep 17 00:00:00 2001 From: choran Date: Thu, 25 Aug 2016 19:35:01 +0000 Subject: [PATCH 023/245] Updating README version to 3.5.3 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 571ef095..3ccf3b59 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', "~> 3.5.2" + gem 'intercom', "~> 3.5.3" ## Basic Usage From fbe269ec12513f6949bc1e01d73209c2364ebd9f Mon Sep 17 00:00:00 2001 From: choran Date: Mon, 29 Aug 2016 17:28:18 +0000 Subject: [PATCH 024/245] Adding scroll API feature --- lib/intercom/api_operations/scroll.rb | 16 ++++ lib/intercom/scroll_collection_proxy.rb | 83 +++++++++++++++++++ lib/intercom/service/user.rb | 2 + spec/spec_helper.rb | 8 ++ .../intercom/scroll_collection_proxy_spec.rb | 56 +++++++++++++ 5 files changed, 165 insertions(+) create mode 100644 lib/intercom/api_operations/scroll.rb create mode 100644 lib/intercom/scroll_collection_proxy.rb create mode 100644 spec/unit/intercom/scroll_collection_proxy_spec.rb diff --git a/lib/intercom/api_operations/scroll.rb b/lib/intercom/api_operations/scroll.rb new file mode 100644 index 00000000..d66407c5 --- /dev/null +++ b/lib/intercom/api_operations/scroll.rb @@ -0,0 +1,16 @@ +require 'intercom/scroll_collection_proxy' + +module Intercom + module ApiOperations + module Scroll + + def scroll() + collection_name = Utils.resource_class_to_collection_name(collection_class) + finder_details = {} + finder_details[:url] = "/#{collection_name}" + ScrollCollectionProxy.new(collection_name, finder_details: finder_details, client: @client) + end + + end + end +end \ No newline at end of file diff --git a/lib/intercom/scroll_collection_proxy.rb b/lib/intercom/scroll_collection_proxy.rb new file mode 100644 index 00000000..8982d3af --- /dev/null +++ b/lib/intercom/scroll_collection_proxy.rb @@ -0,0 +1,83 @@ +require "intercom/utils" +require "ext/sliceable_hash" + +module Intercom + class ScrollCollectionProxy + + attr_reader :resource_name, :scroll_url, :resource_class, :scroll_param, :records + + def initialize(resource_name, finder_details: {}, client:) + @resource_name = resource_name + @resource_class = Utils.constantize_resource_name(resource_name) + @scroll_url = (finder_details[:url] || "/#{@resource_name}") + '/scroll' + @client = client + + end + + def next(scroll_parameter=nil) + @records = [] + if not scroll_parameter + #First time so do initial get without scroll_param + response_hash = @client.get(@scroll_url, '') + else + #Not first call so use get next page + response_hash = @client.get(@scroll_url, scroll_param: scroll_parameter) + end + raise Intercom::HttpError.new('Http Error - No response entity returned') unless response_hash + @scroll_param = extract_scroll_param(response_hash) + top_level_entity_key = deserialize_response_hash(response_hash) + response_hash[top_level_entity_key] = response_hash[top_level_entity_key].map do |object_json| + Lib::TypedJsonDeserializer.new(object_json).deserialize + end + @records = response_hash[@resource_name] + self + end + + def each(&block) + scroll_param = nil + loop do + if not scroll_param + response_hash = @client.get(@scroll_url, '') + else + response_hash = @client.get(@scroll_url, scroll_param: scroll_param) + end + raise Intercom::HttpError.new('Http Error - No response entity returned') unless response_hash + response_hash[deserialize_response_hash(response_hash)].each do |object_json| + block.call Lib::TypedJsonDeserializer.new(object_json).deserialize + end + scroll_param = extract_scroll_param(response_hash) + break if not records_present?(response_hash) + end + self + end + + def [](target_index) + self.each_with_index do |item, index| + return item if index == target_index + end + nil + end + + include Enumerable + + private + + def deserialize_response_hash(response_hash) + top_level_type = response_hash.delete('type') + if resource_name == 'subscriptions' + top_level_entity_key = 'items' + else + top_level_entity_key = Utils.entity_key_from_type(top_level_type) + end + end + + def records_present?(response_hash) + (response_hash[@resource_name].length > 0) + end + + def extract_scroll_param(response_hash) + return nil unless records_present?(response_hash) + response_hash['scroll_param'] + end + end +end \ No newline at end of file diff --git a/lib/intercom/service/user.rb b/lib/intercom/service/user.rb index 9a11b7d0..bb040d2b 100644 --- a/lib/intercom/service/user.rb +++ b/lib/intercom/service/user.rb @@ -1,6 +1,7 @@ require 'intercom/service/base_service' require 'intercom/api_operations/load' require 'intercom/api_operations/list' +require 'intercom/api_operations/scroll' require 'intercom/api_operations/find' require 'intercom/api_operations/find_all' require 'intercom/api_operations/save' @@ -14,6 +15,7 @@ module Service class User < BaseService include ApiOperations::Load include ApiOperations::List + include ApiOperations::Scroll include ApiOperations::Find include ApiOperations::FindAll include ApiOperations::Save diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 67e7bfbb..d079dec4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -216,6 +216,14 @@ def page_of_users(include_next_link= false) } end +def users_scroll(include_users= false) + { + "type"=>"user.list", + "scroll_param"=> ("da6bbbac-25f6-4f07-866b-b911082d7"), + "users"=> (include_users ? [test_user("user1@example.com"), test_user("user2@example.com"), test_user("user3@example.com")] : []), + } +end + def users_pagination(include_next_link=false, per_page=0, page=0, total_pages=0, total_count=0, user_list=[]) { "type"=>"user.list", diff --git a/spec/unit/intercom/scroll_collection_proxy_spec.rb b/spec/unit/intercom/scroll_collection_proxy_spec.rb new file mode 100644 index 00000000..ff4eefd1 --- /dev/null +++ b/spec/unit/intercom/scroll_collection_proxy_spec.rb @@ -0,0 +1,56 @@ +require "spec_helper" + +describe Intercom::ScrollCollectionProxy do + let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } + + it "stops iterating if no users returned" do + client.expects(:get).with("/users/scroll", '').returns(users_scroll(false)) + emails = [] + client.users.scroll.each { |user| emails << user.email } + emails.must_equal %W() + end + + it "keeps iterating if users returned" do + client.expects(:get).with("/users/scroll", '').returns(users_scroll(true)) + client.expects(:get).with('/users/scroll', {:scroll_param => 'da6bbbac-25f6-4f07-866b-b911082d7'}).returns(users_scroll(false)) + emails = [] + client.users.scroll.each { |user| emails << user.email } + end + + it "supports indexed array access" do + client.expects(:get).with("/users/scroll", '').returns(users_scroll(true)) + client.users.scroll[0].email.must_equal 'user1@example.com' + end + + it "supports map" do + client.expects(:get).with("/users/scroll", '').returns(users_scroll(true)) + client.expects(:get).with('/users/scroll', {:scroll_param => 'da6bbbac-25f6-4f07-866b-b911082d7'}).returns(users_scroll(false)) + emails = client.users.scroll.map { |user| user.email } + emails.must_equal %W(user1@example.com user2@example.com user3@example.com) + end + + it "returns one page scroll" do + client.expects(:get).with("/users/scroll", '').returns(users_scroll(true)) + scroll = client.users.scroll.next + emails = [] + scroll.records.each {|usr| emails << usr.email} + emails.must_equal %W(user1@example.com user2@example.com user3@example.com) + end + + it "keeps iterating if called with scroll_param" do + client.expects(:get).with("/users/scroll", '').returns(users_scroll(true)) + client.expects(:get).with('/users/scroll', {:scroll_param => 'da6bbbac-25f6-4f07-866b-b911082d7'}).returns(users_scroll(true)) + scroll = client.users.scroll.next + scroll = client.users.scroll.next('da6bbbac-25f6-4f07-866b-b911082d7') + emails =[] + scroll.records.each {|usr| puts usr.email} + end + + it "works with an empty list" do + client.expects(:get).with("/users/scroll", '').returns(users_scroll(false)) + scroll = client.users.scroll.next + emails = [] + scroll.records.each {|usr| emails << usr.email} + emails.must_equal %W() + end +end From eef11c349d7f6cafcbf292757e1ea928c8f1813a Mon Sep 17 00:00:00 2001 From: choran Date: Tue, 30 Aug 2016 15:53:17 +0000 Subject: [PATCH 025/245] Updating version to 3.5.4 to publish gem for scroll feature --- changes.txt | 3 +++ lib/intercom/version.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/changes.txt b/changes.txt index e937e239..9e5031b5 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.5.4 +- Add support for scoll API feature + 3.5.3 - Add support for global conversation counts diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 1ff15812..78002f69 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.3" + VERSION = "3.5.4" end From 778a54abaf16adcda03f36c067864a86bdcdb19a Mon Sep 17 00:00:00 2001 From: choran Date: Tue, 30 Aug 2016 15:58:14 +0000 Subject: [PATCH 026/245] Updating Readme version to 3.5.4 for scroll feature --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ccf3b59..c62fb0fd 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', "~> 3.5.3" + gem 'intercom', "~> 3.5.4" ## Basic Usage From a9da9cbaa3250db76d77f10cd166df8af505f788 Mon Sep 17 00:00:00 2001 From: choran Date: Wed, 31 Aug 2016 10:53:21 +0000 Subject: [PATCH 027/245] Adding info on scroll and pagination for users --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index c62fb0fd..ea81532a 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,22 @@ intercom.users.save(user) # Iterate over all users intercom.users.all.each {|user| puts %Q(#{user.email} - #{user.custom_attributes["average_monthly_spend"]}) } intercom.users.all.map {|user| user.email } +# List your users create in the last two days +intercom.users.find_all(type: 'users', page: 1, per_page: 10, created_since: 2, order: :asc).to_a.each_with_index {|usr, i| puts "#{i+1}: #{usr.name}"}; +# Paginate through your list of users choosing how many to return per page (default and max is 50 per page) +intercom.users.find_all(type: 'users', page: 1, per_page: 10, order: :asc).to_a.each_with_index {|usr, i| puts "#{i+1}: #{usr.name}"} +# If you have over 10,000 users then you will need to use the scroll function to list your users +# otherwise you will encounter a page limit with list all your users +# You can use the scroll method to list all your users +intercom.users.scroll.each { |user| puts user.name} +# Alternatively you can use the scroll.next method to get 100 users with each request +result = intercom.users.scroll.next +# The result object then contains a records attributes that is an array of your user objects and it also contains a scroll_param which +# you can then use to request the next 100 users. Note that the scroll parameter will time out after one minute and you will need to +# make a new request +result.scroll_param +=> "0730e341-63ef-44da-ab9c-9113f886326d" +result = = intercom.users.scroll.next("0730e341-63ef-44da-ab9c-9113f886326d"); #Bulk operations. # Submit bulk job, to create users, if any of the items in create_items match an existing user that user will be updated From 50326282a7353aa79b7ef9f625208e8e5efe63bc Mon Sep 17 00:00:00 2001 From: Skaelv Date: Thu, 15 Sep 2016 12:30:18 +0100 Subject: [PATCH 028/245] scroll api for contacts --- lib/intercom/service/contact.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/intercom/service/contact.rb b/lib/intercom/service/contact.rb index 11c5460d..508f4fac 100644 --- a/lib/intercom/service/contact.rb +++ b/lib/intercom/service/contact.rb @@ -4,6 +4,7 @@ require 'intercom/api_operations/find' require 'intercom/api_operations/find_all' require 'intercom/api_operations/save' +require 'intercom/api_operations/scroll' require 'intercom/api_operations/convert' require 'intercom/api_operations/delete' @@ -15,6 +16,7 @@ class Contact < BaseService include ApiOperations::Find include ApiOperations::FindAll include ApiOperations::Save + include ApiOperations::Scroll include ApiOperations::Convert include ApiOperations::Delete From a1cf2fb854583be2c0e52d8a3bdc908f72b3b44b Mon Sep 17 00:00:00 2001 From: Alex Burke Date: Mon, 19 Sep 2016 16:56:27 +0100 Subject: [PATCH 029/245] Add example for bulk request Right now if you submit a bulk request, but do so incorrectly, you wont find out for awhile due to bulk processing times. By showing an example of how to properly nest companies, I think we can prevent possible mistakes from being made and also prevent CS from being confused. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ea81532a..d8157eec 100644 --- a/README.md +++ b/README.md @@ -103,8 +103,10 @@ result.scroll_param result = = intercom.users.scroll.next("0730e341-63ef-44da-ab9c-9113f886326d"); #Bulk operations. -# Submit bulk job, to create users, if any of the items in create_items match an existing user that user will be updated +# Submit bulk job to create users. If any of the items in create_items match an existing user that user will be updated intercom.users.submit_bulk_job(create_items: [{user_id: 25, email: "alice@example.com"}, {user_id: 25, email: "bob@example.com"}]) +# Submit bulk job to create users with companies. Companies must be sent as an array of objects nested within each applicable user object +intercom.users.submit_bulk_job(create_items: [{user_id: 25, email: "alice@example.com", companies: [{:company_id => 9, :name => "Test Company"}]}]) # Submit bulk job, to delete users intercom.users.submit_bulk_job(delete_items: [{user_id: 25, email: "alice@example.com"}, {user_id: 25, email: "bob@example.com"}]) # Submit bulk job, to add items to existing job From ffcd022f34aede2b8beaaf56180ae205308c8897 Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Wed, 21 Sep 2016 14:06:30 -0700 Subject: [PATCH 030/245] Cleanup requires a bit * Require utils wherever it is used, rather than in intercom.rb * Require sliceable_hash only where it is used * Drop some random unused requires Prompted by "uninitialized constant Intercom::Lib::TypedJsonDeserializer::Utils" when running `require 'intercom/conversation'` --- lib/intercom.rb | 1 - lib/intercom/api_operations/bulk/submit.rb | 2 ++ lib/intercom/api_operations/delete.rb | 2 +- lib/intercom/api_operations/find.rb | 2 ++ lib/intercom/api_operations/find_all.rb | 1 + lib/intercom/api_operations/list.rb | 1 + lib/intercom/api_operations/load.rb | 2 ++ lib/intercom/api_operations/save.rb | 3 ++- lib/intercom/api_operations/scroll.rb | 3 ++- lib/intercom/client_collection_proxy.rb | 1 - lib/intercom/extended_api_operations/segments.rb | 2 +- lib/intercom/extended_api_operations/tags.rb | 2 +- lib/intercom/extended_api_operations/users.rb | 3 ++- lib/intercom/lib/typed_json_deserializer.rb | 3 ++- lib/intercom/scroll_collection_proxy.rb | 3 +-- lib/intercom/service/conversation.rb | 1 + 16 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/intercom.rb b/lib/intercom.rb index 264ec92a..ac89b7d5 100644 --- a/lib/intercom.rb +++ b/lib/intercom.rb @@ -29,7 +29,6 @@ require "intercom/admin" require "intercom/request" require "intercom/subscription" -require "intercom/utils" require "intercom/errors" require "intercom/visitor" require "json" diff --git a/lib/intercom/api_operations/bulk/submit.rb b/lib/intercom/api_operations/bulk/submit.rb index 2fb68710..7336e2ec 100644 --- a/lib/intercom/api_operations/bulk/submit.rb +++ b/lib/intercom/api_operations/bulk/submit.rb @@ -1,3 +1,5 @@ +require 'intercom/utils' + module Intercom module ApiOperations module Bulk diff --git a/lib/intercom/api_operations/delete.rb b/lib/intercom/api_operations/delete.rb index 864eb552..dbb466be 100644 --- a/lib/intercom/api_operations/delete.rb +++ b/lib/intercom/api_operations/delete.rb @@ -1,4 +1,4 @@ -require 'intercom/traits/api_resource' +require 'intercom/utils' module Intercom module ApiOperations diff --git a/lib/intercom/api_operations/find.rb b/lib/intercom/api_operations/find.rb index 361a9d57..9744062b 100644 --- a/lib/intercom/api_operations/find.rb +++ b/lib/intercom/api_operations/find.rb @@ -1,3 +1,5 @@ +require 'intercom/utils' + module Intercom module ApiOperations module Find diff --git a/lib/intercom/api_operations/find_all.rb b/lib/intercom/api_operations/find_all.rb index cdbac043..6f06f3b0 100644 --- a/lib/intercom/api_operations/find_all.rb +++ b/lib/intercom/api_operations/find_all.rb @@ -1,4 +1,5 @@ require 'intercom/client_collection_proxy' +require 'intercom/utils' module Intercom module ApiOperations diff --git a/lib/intercom/api_operations/list.rb b/lib/intercom/api_operations/list.rb index 27833453..c11fb19b 100644 --- a/lib/intercom/api_operations/list.rb +++ b/lib/intercom/api_operations/list.rb @@ -1,4 +1,5 @@ require 'intercom/client_collection_proxy' +require 'intercom/utils' module Intercom module ApiOperations diff --git a/lib/intercom/api_operations/load.rb b/lib/intercom/api_operations/load.rb index 58bfdc83..6d25a152 100644 --- a/lib/intercom/api_operations/load.rb +++ b/lib/intercom/api_operations/load.rb @@ -1,3 +1,5 @@ +require 'intercom/utils' + module Intercom module ApiOperations module Load diff --git a/lib/intercom/api_operations/save.rb b/lib/intercom/api_operations/save.rb index 126582c8..f90a9a7c 100644 --- a/lib/intercom/api_operations/save.rb +++ b/lib/intercom/api_operations/save.rb @@ -1,4 +1,5 @@ -require 'intercom/traits/api_resource' +require 'intercom/utils' +require 'ext/sliceable_hash' module Intercom module ApiOperations diff --git a/lib/intercom/api_operations/scroll.rb b/lib/intercom/api_operations/scroll.rb index d66407c5..c3204d17 100644 --- a/lib/intercom/api_operations/scroll.rb +++ b/lib/intercom/api_operations/scroll.rb @@ -1,4 +1,5 @@ require 'intercom/scroll_collection_proxy' +require 'intercom/utils' module Intercom module ApiOperations @@ -13,4 +14,4 @@ def scroll() end end -end \ No newline at end of file +end diff --git a/lib/intercom/client_collection_proxy.rb b/lib/intercom/client_collection_proxy.rb index fe261741..dc2bcc44 100644 --- a/lib/intercom/client_collection_proxy.rb +++ b/lib/intercom/client_collection_proxy.rb @@ -1,5 +1,4 @@ require "intercom/utils" -require "ext/sliceable_hash" module Intercom class ClientCollectionProxy diff --git a/lib/intercom/extended_api_operations/segments.rb b/lib/intercom/extended_api_operations/segments.rb index 5fc2eacc..a1bee733 100644 --- a/lib/intercom/extended_api_operations/segments.rb +++ b/lib/intercom/extended_api_operations/segments.rb @@ -1,5 +1,5 @@ -require 'intercom/traits/api_resource' require 'intercom/client_collection_proxy' +require 'intercom/utils' module Intercom module ExtendedApiOperations diff --git a/lib/intercom/extended_api_operations/tags.rb b/lib/intercom/extended_api_operations/tags.rb index cecc6e7e..2e9b9e41 100644 --- a/lib/intercom/extended_api_operations/tags.rb +++ b/lib/intercom/extended_api_operations/tags.rb @@ -1,5 +1,5 @@ -require 'intercom/traits/api_resource' require 'intercom/client_collection_proxy' +require 'intercom/utils' module Intercom module ExtendedApiOperations diff --git a/lib/intercom/extended_api_operations/users.rb b/lib/intercom/extended_api_operations/users.rb index 7401dbc4..55756531 100644 --- a/lib/intercom/extended_api_operations/users.rb +++ b/lib/intercom/extended_api_operations/users.rb @@ -1,4 +1,5 @@ -require 'intercom/traits/api_resource' +require 'intercom/client_collection_proxy' +require 'intercom/utils' module Intercom module ExtendedApiOperations diff --git a/lib/intercom/lib/typed_json_deserializer.rb b/lib/intercom/lib/typed_json_deserializer.rb index f4b0b598..f805a5e2 100644 --- a/lib/intercom/lib/typed_json_deserializer.rb +++ b/lib/intercom/lib/typed_json_deserializer.rb @@ -1,6 +1,7 @@ +require 'intercom/utils' + module Intercom module Lib - # Responsibility: To decide whether we are deserializing a collection or an # entity of a particular type and to dispatch deserialization class TypedJsonDeserializer diff --git a/lib/intercom/scroll_collection_proxy.rb b/lib/intercom/scroll_collection_proxy.rb index 8982d3af..0727fe36 100644 --- a/lib/intercom/scroll_collection_proxy.rb +++ b/lib/intercom/scroll_collection_proxy.rb @@ -1,5 +1,4 @@ require "intercom/utils" -require "ext/sliceable_hash" module Intercom class ScrollCollectionProxy @@ -80,4 +79,4 @@ def extract_scroll_param(response_hash) response_hash['scroll_param'] end end -end \ No newline at end of file +end diff --git a/lib/intercom/service/conversation.rb b/lib/intercom/service/conversation.rb index 8c62e41b..31e51486 100644 --- a/lib/intercom/service/conversation.rb +++ b/lib/intercom/service/conversation.rb @@ -3,6 +3,7 @@ require 'intercom/api_operations/find' require 'intercom/api_operations/load' require 'intercom/api_operations/save' +require 'intercom/utils' module Intercom module Service From f2b80486ffc1ae4ea292f3ce8f54a29928f8b9ce Mon Sep 17 00:00:00 2001 From: Skyler Shaw Date: Thu, 22 Sep 2016 10:47:29 +0100 Subject: [PATCH 031/245] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8157eec..b7581fcb 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ intercom = Intercom::Client.new(app_id: 'my_app_id', api_key: 'my_api_key') You can get your `app_id` from the URL when you're logged into Intercom (it's the alphanumeric just after `/apps/`) and your API key from the API keys integration settings page (under your app settings - integrations in Intercom). ```ruby -# With an OAuth token: +# With an OAuth or Personal Access token: intercom = Intercom::Client.new(token: 'my_token') ``` From f77020a4a1fa0e870ca2cafd1f5e9db4f945e2c0 Mon Sep 17 00:00:00 2001 From: Antoine Kevin Date: Thu, 22 Sep 2016 16:13:42 +0100 Subject: [PATCH 032/245] Update README.md --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index b7581fcb..592fb802 100644 --- a/README.md +++ b/README.md @@ -487,3 +487,20 @@ Calling your client's `rate_limit_details` returns a Hash that contains details intercom.rate_limit_details #=> {:limit=>180, :remaining=>179, :reset_at=>2014-10-07 14:58:00 +0100} ``` + + +### Pull Requests + +- **Add tests!** Your patch won't be accepted if it doesn't have tests. + +- **Document any change in behaviour**. Make sure the README and any other + relevant documentation are kept up-to-date. + +- **Create topic branches**. Don't ask us to pull from your master branch. + +- **One pull request per feature**. If you want to do more than one thing, send + multiple pull requests. + +- **Send coherent history**. Make sure each individual commit in your pull + request is meaningful. If you had to make multiple intermediate commits while + developing, please squash them before sending them to us. From 02922e4b8e1e3e1a4e7a99b6b402c2af338c9b5c Mon Sep 17 00:00:00 2001 From: Marin Martinic Date: Fri, 7 Oct 2016 11:57:25 +0100 Subject: [PATCH 033/245] bump version --- README.md | 2 +- changes.txt | 8 ++++++++ lib/intercom/version.rb | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fed1c3cf..beca9695 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', "~> 3.5.4" + gem 'intercom', "~> 3.5.5" ## Basic Usage diff --git a/changes.txt b/changes.txt index 9e5031b5..c8907968 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,11 @@ +3.5.5 +- Add scroll api for contacts +- Add extra context to IntercomError +- Add support to find admin by id +- Add decrement method to incrementable traits +- Suppress printing of users during test runs + + 3.5.4 - Add support for scoll API feature diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 78002f69..3ccca79f 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.4" + VERSION = "3.5.5" end From 18ef5e30a74db3ac60d584604aba3cb7aa0e0c69 Mon Sep 17 00:00:00 2001 From: Marin Martinic Date: Fri, 7 Oct 2016 12:31:31 +0100 Subject: [PATCH 034/245] add gem-release --- intercom.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/intercom.gemspec b/intercom.gemspec index 0f87f30f..81fdeeaf 100644 --- a/intercom.gemspec +++ b/intercom.gemspec @@ -27,4 +27,5 @@ Gem::Specification.new do |spec| spec.add_dependency 'json', '~> 1.8' spec.required_ruby_version = '>= 2.1.0' + spec.add_development_dependency 'gem-release' end From 98a241687649b94ee032d5c83e87ab3cae7e6f54 Mon Sep 17 00:00:00 2001 From: Antoine Kevin Date: Thu, 20 Oct 2016 09:44:55 +0100 Subject: [PATCH 035/245] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index beca9695..1f8e91c4 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Using bundler: ```ruby intercom = Intercom::Client.new(app_id: 'my_app_id', api_key: 'my_api_key') ``` +> Warning: API Keys are being deprecated - you should use a [personal access token](https://app.intercom.io/a/apps/_/settings/personal-access-token) instead. [Learn more](https://developers.intercom.io/docs/personal-access-tokens) about API Keys deprecation You can get your `app_id` from the URL when you're logged into Intercom (it's the alphanumeric just after `/apps/`) and your API key from the API keys integration settings page (under your app settings - integrations in Intercom). From 06c8f7fb78e6eb1a24941efdaa1885cae45090d0 Mon Sep 17 00:00:00 2001 From: Jordan Feldstein Date: Thu, 20 Oct 2016 10:04:40 -0700 Subject: [PATCH 036/245] Always POST events, so :id may be used to specify a Lead --- lib/intercom/event.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/intercom/event.rb b/lib/intercom/event.rb index aecf9f5d..697d4ae5 100644 --- a/lib/intercom/event.rb +++ b/lib/intercom/event.rb @@ -3,5 +3,6 @@ module Intercom class Event include Traits::ApiResource + def update_verb; 'post' ; end end end From 98e430562d3f04b8b49e17b0dac73ffb11db0f18 Mon Sep 17 00:00:00 2001 From: Skaelv Date: Mon, 24 Oct 2016 10:00:02 +0100 Subject: [PATCH 037/245] Bump to 3.5.6 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 3ccca79f..a5404841 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.5" + VERSION = "3.5.6" end From 8cb887bbf7318de2d78b03fc2842d81d2a25b4f4 Mon Sep 17 00:00:00 2001 From: Kevin Antoine Date: Mon, 24 Oct 2016 10:00:46 +0100 Subject: [PATCH 038/245] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1f8e91c4..25ae9416 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', "~> 3.5.5" + gem 'intercom', "~> 3.5.6" ## Basic Usage From ecc1dd90fa9a2bd4a108fc6d2aba364dfc080cc5 Mon Sep 17 00:00:00 2001 From: Reid Beels Date: Wed, 27 Jul 2016 12:21:37 +0200 Subject: [PATCH 039/245] Support find_all for Events https://developers.intercom.io/reference#list-user-events --- lib/intercom/service/event.rb | 1 + spec/spec_helper.rb | 23 +++++++++++++++++++++++ spec/unit/intercom/event_spec.rb | 5 +++++ 3 files changed, 29 insertions(+) diff --git a/lib/intercom/service/event.rb b/lib/intercom/service/event.rb index 24e10ebe..a4799c11 100644 --- a/lib/intercom/service/event.rb +++ b/lib/intercom/service/event.rb @@ -5,6 +5,7 @@ module Intercom module Service class Event < BaseService + include ApiOperations::FindAll include ApiOperations::Save include ApiOperations::Bulk::Submit diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 08cbeb04..16115278 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -549,6 +549,29 @@ def test_conversation_count } end +def test_event + { + "type" => "event", + "event_name" => "invited-friend", + "created_at" => 1389913941, + "user_id" => "314159", + "metadata" => { + "invitee_email" => "pi@example.org", + "invite_code" => "ADDAFRIEND" + } + } +end + +def test_event_list + { + "type" => "event.list", + "events" => [ test_event ], + "pages" => { + "next" => "https =>//api.intercom.io/events?type=user&intercom_user_id=55a3b&before=144474756550" + } + } +end + def error_on_modify_frozen RUBY_VERSION =~ /1.8/ ? TypeError : RuntimeError end diff --git a/spec/unit/intercom/event_spec.rb b/spec/unit/intercom/event_spec.rb index 3ee2bedd..db30e309 100644 --- a/spec/unit/intercom/event_spec.rb +++ b/spec/unit/intercom/event_spec.rb @@ -6,6 +6,11 @@ let(:created_time) {Time.now - 300} let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } + it 'gets events for a user' do + client.expects(:get).with('/events', type: 'user', email: 'joe@example.com').returns(test_event_list) + client.events.find_all(type: 'user', email: 'joe@example.com').first + end + it "creates an event with metadata" do client.expects(:post).with('/events', {'event_name' => 'Eventful 1', 'created_at' => created_time.to_i, 'email' => 'joe@example.com', 'metadata' => {'invitee_email' => 'pi@example.org', :invite_code => 'ADDAFRIEND', 'found_date' => 12909364407}}).returns(:status => 202) From 6609da8f5870a15c1805292c077bc64482b4bcad Mon Sep 17 00:00:00 2001 From: Reid Beels Date: Wed, 27 Jul 2016 14:39:47 +0200 Subject: [PATCH 040/245] Always treat 'metadata' as a hash, not a typed object --- lib/intercom/traits/api_resource.rb | 10 +++++++++- spec/spec_helper.rb | 7 ++++--- spec/unit/intercom/traits/api_resource_spec.rb | 10 +++++++++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/intercom/traits/api_resource.rb b/lib/intercom/traits/api_resource.rb index 175277ca..5dbca91e 100644 --- a/lib/intercom/traits/api_resource.rb +++ b/lib/intercom/traits/api_resource.rb @@ -67,7 +67,7 @@ def accessors_already_defined?(attribute) end def set_property(attribute, value) - if typed_value?(value) && !custom_attribute_field?(attribute) && !message_from_field?(attribute, value) && !message_to_field?(attribute, value) + if typed_property?(attribute, value) value_to_set = Intercom::Lib::TypedJsonDeserializer.new(value).deserialize elsif flat_store_attribute?(attribute) value_to_set = Intercom::Lib::FlatStore.new(value) @@ -89,6 +89,14 @@ def message_to_field?(attribute, value) attribute.to_s == 'to' && value.is_a?(Hash) && value['type'] end + def typed_property?(attribute, value) + typed_value?(value) && + !custom_attribute_field?(attribute) && + !message_from_field?(attribute, value) && + !message_to_field?(attribute, value) && + attribute != 'metadata' + end + def typed_value?(value) value.is_a? Hash and !!value['type'] end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 16115278..1c412fee 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -556,9 +556,10 @@ def test_event "created_at" => 1389913941, "user_id" => "314159", "metadata" => { - "invitee_email" => "pi@example.org", - "invite_code" => "ADDAFRIEND" - } + "type" => "user", + "invitee_email" => "pi@example.org", + "invite_code" => "ADDAFRIEND" + } } end diff --git a/spec/unit/intercom/traits/api_resource_spec.rb b/spec/unit/intercom/traits/api_resource_spec.rb index 2a485f78..04c956fe 100644 --- a/spec/unit/intercom/traits/api_resource_spec.rb +++ b/spec/unit/intercom/traits/api_resource_spec.rb @@ -11,7 +11,11 @@ "remote_created_at"=>103201, "created_at"=>1374056196, "user_count"=>1, - "custom_attributes"=>{}} + "custom_attributes"=>{}, + "metadata"=>{ + "type"=>"user", + "color"=>"cyan" + }} end let(:api_resource) { DummyClass.new.extend(Intercom::Traits::ApiResource)} @@ -29,6 +33,10 @@ assert_equal Time.at(1374056196), api_resource.created_at end + it "treats 'metadata' as a plain hash, not a typed object" do + assert_equal Hash, api_resource.metadata.class + end + it "dynamically defines accessors when a non-existent property is called that looks like a setter" do api_resource.wont_respond_to :spiders api_resource.spiders = 4 From 8453afa567f9cf31b5528428f45a06d316fa469e Mon Sep 17 00:00:00 2001 From: Skaelv Date: Wed, 26 Oct 2016 08:18:02 +0100 Subject: [PATCH 041/245] add find_all events to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 25ae9416..098766c0 100644 --- a/README.md +++ b/README.md @@ -329,6 +329,8 @@ intercom.events.create( } ) +# Retrieve event list for user with id:'123abc' + intercom.events.find_all("type" => "user", "intercom_user_id" => "123abc") ``` From 5efb8abce9462880e707a890e52afc695c5da102 Mon Sep 17 00:00:00 2001 From: Skaelv Date: Wed, 26 Oct 2016 08:24:38 +0100 Subject: [PATCH 042/245] changes in 3.5.7 --- changes.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changes.txt b/changes.txt index c8907968..8e5b7a34 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,8 @@ +3.5.7 +- Add method to find all events for a user (@reidab) + +3.5.6 + 3.5.5 - Add scroll api for contacts - Add extra context to IntercomError From 2f8abac89e08a7c482ce471926bf69f81864571a Mon Sep 17 00:00:00 2001 From: Skaelv Date: Wed, 26 Oct 2016 08:25:40 +0100 Subject: [PATCH 043/245] Bump to 3.5.7 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index a5404841..1467e48f 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.6" + VERSION = "3.5.7" end From 60aad9472f3309b1a9feb8349ad57264e463540b Mon Sep 17 00:00:00 2001 From: Kevin Antoine Date: Wed, 26 Oct 2016 08:42:27 +0100 Subject: [PATCH 044/245] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 098766c0..27493744 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', "~> 3.5.6" + gem 'intercom', "~> 3.5.7" ## Basic Usage From 63d410c536ba830145be908442360487afdc55bb Mon Sep 17 00:00:00 2001 From: Skaelv Date: Thu, 27 Oct 2016 11:09:26 +0100 Subject: [PATCH 045/245] add GET /me endpoint --- lib/intercom/service/admin.rb | 6 ++++++ spec/unit/intercom/admin_spec.rb | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/intercom/service/admin.rb b/lib/intercom/service/admin.rb index 352be7ca..fbb57c49 100644 --- a/lib/intercom/service/admin.rb +++ b/lib/intercom/service/admin.rb @@ -11,6 +11,12 @@ class Admin < BaseService def collection_class Intercom::Admin end + + def me + response = @client.get("/me", {}) + raise Intercom::HttpError.new('Http Error - No response entity returned') unless response + from_api(response) + end end end end diff --git a/spec/unit/intercom/admin_spec.rb b/spec/unit/intercom/admin_spec.rb index 143fb619..30424e03 100644 --- a/spec/unit/intercom/admin_spec.rb +++ b/spec/unit/intercom/admin_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe "Intercom::Admin" do - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } + let (:client) { Intercom::Client.new(token: 'token') } it "returns a CollectionProxy for all without making any requests" do client.expects(:execute_request).never @@ -9,6 +9,11 @@ all.must_be_instance_of(Intercom::ClientCollectionProxy) end + it "gets me (access token method only)" do + client.expects(:get).with("/me", {}).returns(test_admin) + client.admins.me + end + it 'gets an admin list' do client.expects(:get).with("/admins", {}).returns(test_admin_list) client.admins.all.each { |a| } From 7104c3a679264017390d5a60a88d9252ab529637 Mon Sep 17 00:00:00 2001 From: Skaelv Date: Thu, 27 Oct 2016 11:21:26 +0100 Subject: [PATCH 046/245] update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 27493744..92217789 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ result = = intercom.users.scroll.next("0730e341-63ef-44da-ab9c-9113f886326d"); #Bulk operations. # Submit bulk job to create users. If any of the items in create_items match an existing user that user will be updated intercom.users.submit_bulk_job(create_items: [{user_id: 25, email: "alice@example.com"}, {user_id: 25, email: "bob@example.com"}]) -# Submit bulk job to create users with companies. Companies must be sent as an array of objects nested within each applicable user object +# Submit bulk job to create users with companies. Companies must be sent as an array of objects nested within each applicable user object intercom.users.submit_bulk_job(create_items: [{user_id: 25, email: "alice@example.com", companies: [{:company_id => 9, :name => "Test Company"}]}]) # Submit bulk job, to delete users intercom.users.submit_bulk_job(delete_items: [{user_id: 25, email: "alice@example.com"}, {user_id: 25, email: "bob@example.com"}]) @@ -119,6 +119,8 @@ intercom.users.submit_bulk_job(create_items: [{user_id: 25, email: "alice@exampl #### Admins ```ruby +# Find access token owner (only with Personal Access Token and OAuth) +intercom.admins.me # Find an admin by id intercom.admins.find(:id => admin_id) # Iterate over all admins From 18bd58565449d473181957cef33bb80a325074da Mon Sep 17 00:00:00 2001 From: Skaelv Date: Thu, 27 Oct 2016 11:54:23 +0100 Subject: [PATCH 047/245] Bump to 3.5.8 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 1467e48f..09b69d8a 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.7" + VERSION = "3.5.8" end From 9a8b402c344ab8fcc7d417a632fd2a418feef022 Mon Sep 17 00:00:00 2001 From: Skaelv Date: Thu, 27 Oct 2016 11:54:59 +0100 Subject: [PATCH 048/245] update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 92217789..1e9fafd8 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', "~> 3.5.7" + gem 'intercom', "~> 3.5.8" ## Basic Usage From ded362055673d0628bd11def6365f75812228631 Mon Sep 17 00:00:00 2001 From: Skaelv Date: Thu, 27 Oct 2016 12:18:25 +0100 Subject: [PATCH 049/245] update specs --- spec/unit/intercom/event_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/intercom/event_spec.rb b/spec/unit/intercom/event_spec.rb index 3ee2bedd..4f085063 100644 --- a/spec/unit/intercom/event_spec.rb +++ b/spec/unit/intercom/event_spec.rb @@ -19,8 +19,8 @@ end it "creates an event without metadata" do - client.expects(:post).with('/events', {'event_name' => 'sale of item', 'email' => 'joe@example.com'}) - client.events.create(:event_name => "sale of item", :email => 'joe@example.com') + client.expects(:post).with('/events', {'event_name' => 'sale of item', 'id' => 123}) + client.events.create(:event_name => "sale of item", :id => 123) end describe 'bulk operations' do From 5589babd9205c638015b60133cae187dea870db6 Mon Sep 17 00:00:00 2001 From: Skaelv Date: Thu, 27 Oct 2016 14:24:47 +0100 Subject: [PATCH 050/245] update changes.txt --- README.md | 2 +- changes.txt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e9fafd8..2794baa7 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', "~> 3.5.8" + gem 'intercom', "~> 3.5.9" ## Basic Usage diff --git a/changes.txt b/changes.txt index 8e5b7a34..b5097557 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,9 @@ +3.5.9 + - Fix event create method + +3.5.8 + - Add admins.me method + 3.5.7 - Add method to find all events for a user (@reidab) From 211bc06e4ee2c371667b489b7926369245f94e77 Mon Sep 17 00:00:00 2001 From: Skaelv Date: Thu, 27 Oct 2016 14:24:50 +0100 Subject: [PATCH 051/245] Bump to 3.5.9 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 09b69d8a..a8d8e450 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.8" + VERSION = "3.5.9" end From ca59f04c7ed4bb9073198335ddd99a32e7e3f332 Mon Sep 17 00:00:00 2001 From: Thomas Dziedzic Date: Wed, 2 Nov 2016 21:06:58 -0700 Subject: [PATCH 052/245] relax json depends to allow 2.x --- intercom.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intercom.gemspec b/intercom.gemspec index 81fdeeaf..07ee4dc9 100644 --- a/intercom.gemspec +++ b/intercom.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency "fakeweb", ["~> 1.3"] spec.add_development_dependency "pry" - spec.add_dependency 'json', '~> 1.8' + spec.add_dependency 'json', '>= 1.8' spec.required_ruby_version = '>= 2.1.0' spec.add_development_dependency 'gem-release' end From 5109d28e3cd4a470543568525e275d46e37c9928 Mon Sep 17 00:00:00 2001 From: Dorian Date: Thu, 17 Nov 2016 16:14:19 +0100 Subject: [PATCH 053/245] Update examples in README.md with ruby hash symbols syntax --- README.md | 162 +++++++++++++++++++++++++++--------------------------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 2794baa7..4ee6e201 100644 --- a/README.md +++ b/README.md @@ -67,15 +67,15 @@ Resources this API supports: ```ruby # Find user by email -user = intercom.users.find(:email => "bob@example.com") +user = intercom.users.find(email: "bob@example.com") # Find user by user_id -user = intercom.users.find(:user_id => "1") +user = intercom.users.find(user_id: "1") # Find user by id -user = intercom.users.find(:id => "1") +user = intercom.users.find(id: "1") # Create a user -user = intercom.users.create(:email => "bob@example.com", :name => "Bob Smith", :signed_up_at => Time.now.to_i) +user = intercom.users.create(email: "bob@example.com", name: "Bob Smith", signed_up_at: Time.now.to_i) # Delete a user -user = intercom.users.find(:id => "1") +user = intercom.users.find(id: "1") deleted_user = intercom.users.delete(user) # Update custom_attributes for a user user.custom_attributes["average_monthly_spend"] = 1234.56 @@ -110,7 +110,7 @@ result = = intercom.users.scroll.next("0730e341-63ef-44da-ab9c-9113f886326d"); # Submit bulk job to create users. If any of the items in create_items match an existing user that user will be updated intercom.users.submit_bulk_job(create_items: [{user_id: 25, email: "alice@example.com"}, {user_id: 25, email: "bob@example.com"}]) # Submit bulk job to create users with companies. Companies must be sent as an array of objects nested within each applicable user object -intercom.users.submit_bulk_job(create_items: [{user_id: 25, email: "alice@example.com", companies: [{:company_id => 9, :name => "Test Company"}]}]) +intercom.users.submit_bulk_job(create_items: [{user_id: 25, email: "alice@example.com", companies: [{company_id: 9, name: "Test Company"}]}]) # Submit bulk job, to delete users intercom.users.submit_bulk_job(delete_items: [{user_id: 25, email: "alice@example.com"}, {user_id: 25, email: "bob@example.com"}]) # Submit bulk job, to add items to existing job @@ -122,7 +122,7 @@ intercom.users.submit_bulk_job(create_items: [{user_id: 25, email: "alice@exampl # Find access token owner (only with Personal Access Token and OAuth) intercom.admins.me # Find an admin by id -intercom.admins.find(:id => admin_id) +intercom.admins.find(id: admin_id) # Iterate over all admins intercom.admins.all.each {|admin| puts admin.email } ``` @@ -130,18 +130,18 @@ intercom.admins.all.each {|admin| puts admin.email } #### Companies ```ruby # Add a user to one or more companies -user = intercom.users.find(:email => "bob@example.com") -user.companies = [{:company_id => 6, :name => "Intercom"}, {:company_id => 9, :name => "Test Company"}] +user = intercom.users.find(email: "bob@example.com") +user.companies = [{company_id: 6, name: "Intercom"}, {company_id: 9, name: "Test Company"}] intercom.users.save(user) # You can also pass custom attributes within a company as you do this -user.companies = [{:id => 6, :name => "Intercom", :custom_attributes => {:referral_source => "Google"} } ] +user.companies = [{id: 6, name: "Intercom", custom_attributes: {referral_source: "Google"} } ] intercom.users.save(user) # Find a company by company_id -company = intercom.companies.find(:company_id => "44") +company = intercom.companies.find(company_id: "44") # Find a company by name -company = intercom.companies.find(:name => "Some company") +company = intercom.companies.find(name: "Some company") # Find a company by id -company = intercom.companies.find(:id => "41e66f0313708347cb0000d0") +company = intercom.companies.find(id: "41e66f0313708347cb0000d0") # Update a company company.name = 'Updated company name' intercom.companies.save(company) @@ -168,7 +168,7 @@ tag = intercom.tags.tag(name: 'blue', companies: [{id: "42ea2f1b93891f6a99000427 #### Segments ```ruby # Find a segment -segment = intercom.segments.find(:id => segment_id) +segment = intercom.segments.find(id: segment_id) # Iterate over all segments intercom.segments.all.each {|segment| puts "id: #{segment.id} name: #{segment.name}"} ``` @@ -176,37 +176,37 @@ intercom.segments.all.each {|segment| puts "id: #{segment.id} name: #{segment.na #### Notes ```ruby # Find a note by id -note = intercom.notes.find(:id => note) +note = intercom.notes.find(id: note) # Create a note for a user -note = intercom.notes.create(:body => "

Text for the note

", :email => 'joe@example.com') +note = intercom.notes.create(body: "

Text for the note

", email: 'joe@example.com') # Iterate over all notes for a user via their email address -intercom.notes.find_all(:email => 'joe@example.com').each {|note| puts note.body} +intercom.notes.find_all(email: 'joe@example.com').each {|note| puts note.body} # Iterate over all notes for a user via their user_id -intercom.notes.find_all(:user_id => '123').each {|note| puts note.body} +intercom.notes.find_all(user_id: '123').each {|note| puts note.body} ``` #### Conversations ```ruby # FINDING CONVERSATIONS FOR AN ADMIN # Iterate over all conversations (open and closed) assigned to an admin -intercom.conversations.find_all(:type => 'admin', :id => '7').each {|convo| ... } +intercom.conversations.find_all(type: 'admin', id: '7').each {|convo| ... } # Iterate over all open conversations assigned to an admin -intercom.conversations.find_all(:type => 'admin', :id => 7, :open => true).each {|convo| ... } +intercom.conversations.find_all(type: 'admin', id: 7, open: true).each {|convo| ... } # Iterate over closed conversations assigned to an admin -intercom.conversations.find_all(:type => 'admin', :id => 7, :open => false).each {|convo| ... } +intercom.conversations.find_all(type: 'admin', id: 7, open: false).each {|convo| ... } # Iterate over closed conversations for assigned an admin, before a certain moment in time -intercom.conversations.find_all(:type => 'admin', :id => 7, :open => false, :before => 1374844930).each {|convo| ... } +intercom.conversations.find_all(type: 'admin', id: 7, open: false, before: 1374844930).each {|convo| ... } # FINDING CONVERSATIONS FOR A USER # Iterate over all conversations (read + unread, correct) with a user based on the users email -intercom.conversations.find_all(:email => 'joe@example.com', :type => 'user').each {|convo| ... } +intercom.conversations.find_all(email: 'joe@example.com', type: 'user').each {|convo| ... } # Iterate over through all conversations (read + unread) with a user based on the users email -intercom.conversations.find_all(:email => 'joe@example.com', :type => 'user', :unread => false).each {|convo| ... } +intercom.conversations.find_all(email: 'joe@example.com', type: 'user', unread: false).each {|convo| ... } # Iterate over all unread conversations with a user based on the users email -intercom.conversations.find_all(:email => 'joe@example.com', :type => 'user', :unread => true).each {|convo| ... } +intercom.conversations.find_all(email: 'joe@example.com', type: 'user', unread: true).each {|convo| ... } # FINDING A SINGLE CONVERSATION -conversation = intercom.conversations.find(:id => '1') +conversation = intercom.conversations.find(id: '1') # INTERACTING WITH THE PARTS OF A CONVERSATION # Getting the subject of a part (only applies to email-based conversations) @@ -218,11 +218,11 @@ conversation.conversation_parts[1].body # REPLYING TO CONVERSATIONS # User (identified by email) replies with a comment -intercom.conversations.reply(:id => conversation.id, :type => 'user', :email => 'joe@example.com', :message_type => 'comment', :body => 'foo') +intercom.conversations.reply(id: conversation.id, type: 'user', email: 'joe@example.com', message_type: 'comment', body: 'foo') # Admin (identified by id) replies with a comment -intercom.conversations.reply(:id => conversation.id, :type => 'admin', :admin_id => '123', :message_type => 'comment', :body => 'bar') +intercom.conversations.reply(id: conversation.id, type: 'admin', admin_id: '123', message_type: 'comment', body: 'bar') # User (identified by email) replies with a comment and attachment -intercom.conversations.reply(:id => conversation.id, :type => 'user', :email => 'joe@example.com', :message_type => 'comment', :body => 'foo', :attachment_urls => ['http://www.example.com/attachment.jpg']) +intercom.conversations.reply(id: conversation.id, type: 'user', email: 'joe@example.com', message_type: 'comment', body: 'foo', attachment_urls: ['http://www.example.com/attachment.jpg']) # Open intercom.conversations.open(id: conversation.id, admin_id: '123') @@ -234,13 +234,13 @@ intercom.conversations.close(id: conversation.id, admin_id: '123') intercom.conversations.assign(id: conversation.id, admin_id: '123', assignee_id: '124') # Reply and Open -intercom.conversations.reply(:id => conversation.id, :type => 'admin', :admin_id => '123', :message_type => 'open', :body => 'bar') +intercom.conversations.reply(id: conversation.id, type: 'admin', admin_id: '123', message_type: 'open', body: 'bar') # Reply and Close -intercom.conversations.reply(:id => conversation.id, :type => 'admin', :admin_id => '123', :message_type => 'close', :body => 'bar') +intercom.conversations.reply(id: conversation.id, type: 'admin', admin_id: '123', message_type: 'close', body: 'bar') # ASSIGNING CONVERSATIONS TO ADMINS -intercom.conversations.reply(:id => conversation.id, :type => 'admin', :assignee_id => assignee_admin.id, :admin_id => admin.id, :message_type => 'assignment') +intercom.conversations.reply(id: conversation.id, type: 'admin', assignee_id: assignee_admin.id, admin_id: admin.id, message_type: 'assignment') # MARKING A CONVERSATION AS READ intercom.conversations.mark_read(conversation.id) @@ -258,75 +258,75 @@ intercom.users.load(conversation.user) # InApp message from admin to user intercom.messages.create({ - :message_type => 'inapp', - :body => "What's up :)", - :from => { - :type => 'admin', - :id => "1234" + message_type: 'inapp', + body: "What's up :)", + from: { + type: 'admin', + id: "1234" }, - :to => { - :type => "user", - :user_id => "5678" + to: { + type: "user", + user_id: "5678" } }) # Email message from admin to user intercom.messages.create({ - :message_type => 'email', - :subject => 'Hey there', - :body => "What's up :)", - :template => "plain", # or "personal", - :from => { - :type => "admin", - :id => "1234" + message_type: 'email', + subject: 'Hey there', + body: "What's up :)", + template: "plain", # or "personal", + from: { + type: "admin", + id: "1234" }, - :to => { - :type => "user", - :id => "536e564f316c83104c000020" + to: { + type: "user", + id: "536e564f316c83104c000020" } }) # Message from a user intercom.messages.create({ - :from => { - :type => "user", - :id => "536e564f316c83104c000020" + from: { + type: "user", + id: "536e564f316c83104c000020" }, - :body => "halp" + body: "halp" }) # Message from admin to contact intercom.messages.create({ - :body => "How can I help :)", - :from => { - :type => "admin", - :id => "1234" + body: "How can I help :)", + from: { + type: "admin", + id: "1234" }, - :to => { - :type => "contact", - :id => "536e5643as316c83104c400671" + to: { + type: "contact", + id: "536e5643as316c83104c400671" } }) # Message from a contact intercom.messages.create({ - :from => { - :type => "contact", - :id => "536e5643as316c83104c400671" + from: { + type: "contact", + id: "536e5643as316c83104c400671" }, - :body => "halp" + body: "halp" }) ``` #### Events ```ruby intercom.events.create( - :event_name => "invited-friend", :created_at => Time.now.to_i, - :email => user.email, - :metadata => { + event_name: "invited-friend", created_at: Time.now.to_i, + email: user.email, + metadata: { "invitee_email" => "pi@example.org", - :invite_code => "ADDAFRIEND", + invite_code: "ADDAFRIEND", "found_date" => 12909364407 } ) @@ -339,18 +339,18 @@ intercom.events.create( Metadata Objects support a few simple types that Intercom can present on your behalf ```ruby -intercom.events.create(:event_name => "placed-order", :email => current_user.email, - :created_at => 1403001013, - :metadata => { - :order_date => Time.now.to_i, - :stripe_invoice => 'inv_3434343434', - :order_number => { - :value => '3434-3434', - :url => 'https://example.org/orders/3434-3434' +intercom.events.create(event_name: "placed-order", email: current_user.email, + created_at: 1403001013, + metadata: { + order_date: Time.now.to_i, + stripe_invoice: 'inv_3434343434', + order_number: { + value: '3434-3434', + url: 'https://example.org/orders/3434-3434' }, price: { - :currency => 'usd', - :amount => 2999 + currency: 'usd', + amount: 2999 } } ) @@ -450,10 +450,10 @@ Subscribe to events in Intercom to receive webhooks. ```ruby # create a subscription -intercom.subscriptions.create(:url => "http://example.com", :topics => ["user.created"]) +intercom.subscriptions.create(url: "http://example.com", topics: ["user.created"]) # fetch a subscription -intercom.subscriptions.find(:id => "nsub_123456789") +intercom.subscriptions.find(id: "nsub_123456789") # list subscriptions intercom.subscriptions.all From bd6a325ffc34a01488c67c57044affb1d422cb23 Mon Sep 17 00:00:00 2001 From: Thomas Dziedzic Date: Fri, 2 Dec 2016 13:15:48 -0800 Subject: [PATCH 054/245] add ruby 2.3.3 testing to travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 210b4d80..98ede9d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,3 +3,4 @@ sudo: false rvm: - 2.1.0 - 2.2.0 + - 2.3.3 From 5d23a94d93fe78c43b5a94b563c90e5919c9464b Mon Sep 17 00:00:00 2001 From: Brian Pearce Date: Sat, 10 Dec 2016 21:09:01 -0800 Subject: [PATCH 055/245] Match line ends with the lines around them In the top of the readme lines end with a period. In the lower list lines end without them. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2794baa7..05db31ce 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Ruby bindings for the Intercom API (https://developers.intercom.io/reference). [Gem Documentation](http://rubydoc.info/github/intercom/intercom-ruby/master/frames) -For generating Intercom javascript script tags for Rails, please see https://github.com/intercom/intercom-rails +For generating Intercom javascript script tags for Rails, please see https://github.com/intercom/intercom-rails. ## Upgrading information @@ -357,7 +357,7 @@ intercom.events.create(:event_name => "placed-order", :email => current_user.ema ``` The metadata key values in the example are treated as follows- -- order_date: a Date (key ends with '_date'). +- order_date: a Date (key ends with '_date') - stripe_invoice: The identifier of the Stripe invoice (has a 'stripe_invoice' key) - order_number: a Rich Link (value contains 'url' and 'value' keys) - price: An Amount in US Dollars (value contains 'amount' and 'currency' keys) From de044685f88ef9e165c371e016f419342dc34ecc Mon Sep 17 00:00:00 2001 From: Brian Pearce Date: Sat, 10 Dec 2016 21:09:55 -0800 Subject: [PATCH 056/245] Keep quotations consistent --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 05db31ce..91932879 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', "~> 3.5.9" + gem 'intercom', '~> 3.5.9' ## Basic Usage From 8364184b95a60bdebdd23050d89618c6e33087a6 Mon Sep 17 00:00:00 2001 From: Brian Pearce Date: Sat, 10 Dec 2016 21:10:31 -0800 Subject: [PATCH 057/245] Remove unneeded extra `=` sign --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 91932879..69aafe20 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ result = intercom.users.scroll.next # make a new request result.scroll_param => "0730e341-63ef-44da-ab9c-9113f886326d" -result = = intercom.users.scroll.next("0730e341-63ef-44da-ab9c-9113f886326d"); +result = intercom.users.scroll.next("0730e341-63ef-44da-ab9c-9113f886326d"); #Bulk operations. # Submit bulk job to create users. If any of the items in create_items match an existing user that user will be updated From af315fef8c1b2508e064524f2cad4436f5c29293 Mon Sep 17 00:00:00 2001 From: Brian Pearce Date: Sat, 10 Dec 2016 21:45:11 -0800 Subject: [PATCH 058/245] Adjust parameter line placement --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 69aafe20..1ca0dec3 100644 --- a/README.md +++ b/README.md @@ -322,7 +322,8 @@ intercom.messages.create({ #### Events ```ruby intercom.events.create( - :event_name => "invited-friend", :created_at => Time.now.to_i, + :event_name => "invited-friend", + :created_at => Time.now.to_i, :email => user.email, :metadata => { "invitee_email" => "pi@example.org", @@ -339,7 +340,9 @@ intercom.events.create( Metadata Objects support a few simple types that Intercom can present on your behalf ```ruby -intercom.events.create(:event_name => "placed-order", :email => current_user.email, +intercom.events.create( + :event_name => "placed-order", + :email => current_user.email, :created_at => 1403001013, :metadata => { :order_date => Time.now.to_i, From b6942f608c11434a82d8d6d519911a35ffbb8742 Mon Sep 17 00:00:00 2001 From: John Paul Keenan Date: Thu, 15 Dec 2016 13:39:38 -0800 Subject: [PATCH 059/245] Changed user_id to a string In the Bulk Operations section, the user_id was being treated like an int. I changed to string. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2794baa7..38d0294f 100644 --- a/README.md +++ b/README.md @@ -108,13 +108,13 @@ result = = intercom.users.scroll.next("0730e341-63ef-44da-ab9c-9113f886326d"); #Bulk operations. # Submit bulk job to create users. If any of the items in create_items match an existing user that user will be updated -intercom.users.submit_bulk_job(create_items: [{user_id: 25, email: "alice@example.com"}, {user_id: 25, email: "bob@example.com"}]) +intercom.users.submit_bulk_job(create_items: [{user_id: "25", email: "alice@example.com"}, {user_id: "25", email: "bob@example.com"}]) # Submit bulk job to create users with companies. Companies must be sent as an array of objects nested within each applicable user object -intercom.users.submit_bulk_job(create_items: [{user_id: 25, email: "alice@example.com", companies: [{:company_id => 9, :name => "Test Company"}]}]) +intercom.users.submit_bulk_job(create_items: [{user_id: "25", email: "alice@example.com", companies: [{:company_id => 9, :name => "Test Company"}]}]) # Submit bulk job, to delete users -intercom.users.submit_bulk_job(delete_items: [{user_id: 25, email: "alice@example.com"}, {user_id: 25, email: "bob@example.com"}]) +intercom.users.submit_bulk_job(delete_items: [{user_id: "25", email: "alice@example.com"}, {user_id: "25", email: "bob@example.com"}]) # Submit bulk job, to add items to existing job -intercom.users.submit_bulk_job(create_items: [{user_id: 25, email: "alice@example.com"}], delete_items: [{user_id: 25, email: "bob@example.com"}], job_id:'job_abcd1234') +intercom.users.submit_bulk_job(create_items: [{user_id: "25", email: "alice@example.com"}], delete_items: [{user_id: "25", email: "bob@example.com"}], job_id:'job_abcd1234') ``` #### Admins From cddfeba0625c2693de4cb46c63d32bc62fad0f03 Mon Sep 17 00:00:00 2001 From: choran Date: Thu, 5 Jan 2017 16:10:59 +0000 Subject: [PATCH 060/245] Removing reference to API Keys --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 8671d2d9..85a6c25f 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,7 @@ Using bundler: ### Configure your client -```ruby -intercom = Intercom::Client.new(app_id: 'my_app_id', api_key: 'my_api_key') -``` -> Warning: API Keys are being deprecated - you should use a [personal access token](https://app.intercom.io/a/apps/_/settings/personal-access-token) instead. [Learn more](https://developers.intercom.io/docs/personal-access-tokens) about API Keys deprecation - -You can get your `app_id` from the URL when you're logged into Intercom (it's the alphanumeric just after `/apps/`) and your API key from the API keys integration settings page (under your app settings - integrations in Intercom). +> If you already have a personal access token you can find it [here](https://app.intercom.io/a/apps/_/settings/personal-access-token). If you want to create or learn more about personal access token then see [heree](https://developers.intercom.io/docs/personal-access-tokens) about API Keys deprecation ```ruby # With an OAuth or Personal Access token: From 8449c7c96e1bd5be4874229ea5bc739361df764d Mon Sep 17 00:00:00 2001 From: choran Date: Thu, 5 Jan 2017 16:17:33 +0000 Subject: [PATCH 061/245] updating with info about API keys --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 85a6c25f..4874ead0 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Using bundler: ### Configure your client -> If you already have a personal access token you can find it [here](https://app.intercom.io/a/apps/_/settings/personal-access-token). If you want to create or learn more about personal access token then see [heree](https://developers.intercom.io/docs/personal-access-tokens) about API Keys deprecation +> If you already have a personal access token you can find it [here](https://app.intercom.io/a/apps/_/settings/personal-access-token). If you want to create or learn more about personal access tokens then you can find more info [here](https://developers.intercom.io/docs/personal-access-tokens). ```ruby # With an OAuth or Personal Access token: From bb418ec76abe112e97601182845e9228bc3693a6 Mon Sep 17 00:00:00 2001 From: Skaelv Date: Tue, 10 Jan 2017 17:25:07 +0000 Subject: [PATCH 062/245] Bump to 3.5.10 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index a8d8e450..31fec8a1 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.9" + VERSION = "3.5.10" end From 052ee377fc9fc614b298135d621573ffe73acf6c Mon Sep 17 00:00:00 2001 From: Kevin Antoine Date: Wed, 11 Jan 2017 15:40:39 +0000 Subject: [PATCH 063/245] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4874ead0..c29818d6 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.5.9' + gem 'intercom', '~> 3.5.10' ## Basic Usage From 31f823e402c9196e0571aa11f8d5c0578bca1293 Mon Sep 17 00:00:00 2001 From: John Keyes Date: Mon, 6 Feb 2017 22:05:09 +0000 Subject: [PATCH 064/245] Adding support for multi-page Event.find_all. --- lib/intercom/api_operations/find_all.rb | 2 +- lib/intercom/service/base_service.rb | 6 ++++++ lib/intercom/service/event.rb | 12 ++++++++++++ spec/spec_helper.rb | 14 +++++++++++++- spec/unit/intercom/event_spec.rb | 19 +++++++++++++++++++ 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/lib/intercom/api_operations/find_all.rb b/lib/intercom/api_operations/find_all.rb index 6f06f3b0..fff8423f 100644 --- a/lib/intercom/api_operations/find_all.rb +++ b/lib/intercom/api_operations/find_all.rb @@ -16,7 +16,7 @@ def find_all(params) finder_details[:url] = "/#{collection_name}" finder_details[:params] = params end - ClientCollectionProxy.new(collection_name, finder_details: finder_details, client: @client) + collection_proxy_class.new(collection_name, finder_details: finder_details, client: @client) end private diff --git a/lib/intercom/service/base_service.rb b/lib/intercom/service/base_service.rb index ee2c6f6a..6fbc2c8c 100644 --- a/lib/intercom/service/base_service.rb +++ b/lib/intercom/service/base_service.rb @@ -1,3 +1,5 @@ +require 'intercom/client_collection_proxy' + module Intercom module Service class BaseService @@ -11,6 +13,10 @@ def collection_class raise NotImplementedError end + def collection_proxy_class + Intercom::ClientCollectionProxy + end + def from_api(api_response) object = collection_class.new object.from_response(api_response) diff --git a/lib/intercom/service/event.rb b/lib/intercom/service/event.rb index a4799c11..33371014 100644 --- a/lib/intercom/service/event.rb +++ b/lib/intercom/service/event.rb @@ -1,8 +1,16 @@ +require 'intercom/client_collection_proxy' require 'intercom/service/base_service' require 'intercom/api_operations/save' require 'intercom/api_operations/bulk/submit' module Intercom + class EventCollectionProxy < ClientCollectionProxy + + def paging_info_present?(response_hash) + !!(response_hash['pages']) + end + end + module Service class Event < BaseService include ApiOperations::FindAll @@ -12,6 +20,10 @@ class Event < BaseService def collection_class Intercom::Event end + + def collection_proxy_class + Intercom::EventCollectionProxy + end end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1c412fee..14ed7ad6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -568,11 +568,23 @@ def test_event_list "type" => "event.list", "events" => [ test_event ], "pages" => { - "next" => "https =>//api.intercom.io/events?type=user&intercom_user_id=55a3b&before=144474756550" + "next" => "https://api.intercom.io/events?type=user&intercom_user_id=55a3b&before=144474756550" } } end +def page_of_events(include_next_link=false) + { + "type" => "event.list", + "events" => [ test_event ], + "pages" => + { + "next" => (include_next_link ? "https://api.intercom.io/events?type=user&intercom_user_id=55a3b&before=144474756550" : nil), + } + } +end + + def error_on_modify_frozen RUBY_VERSION =~ /1.8/ ? TypeError : RuntimeError end diff --git a/spec/unit/intercom/event_spec.rb b/spec/unit/intercom/event_spec.rb index 77274800..a87ce70d 100644 --- a/spec/unit/intercom/event_spec.rb +++ b/spec/unit/intercom/event_spec.rb @@ -11,6 +11,25 @@ client.events.find_all(type: 'user', email: 'joe@example.com').first end + it "has the correct collection proxy class" do + client.events.collection_proxy_class.must_equal Intercom::EventCollectionProxy + end + + it "stops iterating if no next link" do + client.expects(:get).with("/events", type: 'user', email: 'joe@example.com').returns(page_of_events(false)) + event_names = [] + client.events.find_all(type: 'user', email: 'joe@example.com').each { |event| event_names << event.event_name } + event_names.must_equal %W(invited-friend) + end + + it "keeps iterating if next link" do + client.expects(:get).with("/events", type: 'user', email: 'joe@example.com').returns(page_of_events(true)) + client.expects(:get).with("https://api.intercom.io/events?type=user&intercom_user_id=55a3b&before=144474756550", {}).returns(page_of_events(false)) + event_names = [] + client.events.find_all(type: 'user', email: 'joe@example.com').each { |event| event_names << event.event_name } + event_names.must_equal %W(invited-friend invited-friend) + end + it "creates an event with metadata" do client.expects(:post).with('/events', {'event_name' => 'Eventful 1', 'created_at' => created_time.to_i, 'email' => 'joe@example.com', 'metadata' => {'invitee_email' => 'pi@example.org', :invite_code => 'ADDAFRIEND', 'found_date' => 12909364407}}).returns(:status => 202) From ba97996786af3284910c6a9dca2474dd437acb4d Mon Sep 17 00:00:00 2001 From: Skaelv Date: Thu, 9 Feb 2017 15:31:30 +0000 Subject: [PATCH 065/245] add releasing file --- RELEASING.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 RELEASING.md diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 00000000..35f279fe --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,8 @@ +# Releasing Intercom + +We use https://github.com/svenfuchs/gem-release to tag, bump, and release new versions + + +``` +gem bump --tag --release +``` From 8351d3847466480057fb77604a9301d68767752b Mon Sep 17 00:00:00 2001 From: Skaelv Date: Thu, 9 Feb 2017 15:35:57 +0000 Subject: [PATCH 066/245] update changes.txt --- RELEASING.md | 1 + changes.txt | 3 +++ 2 files changed, 4 insertions(+) diff --git a/RELEASING.md b/RELEASING.md index 35f279fe..e1819060 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -2,6 +2,7 @@ We use https://github.com/svenfuchs/gem-release to tag, bump, and release new versions +Please add a line to changes.txt before you release a new version. ``` gem bump --tag --release diff --git a/changes.txt b/changes.txt index b5097557..fe2b1344 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.5.10 + - Add Support for find_all events pagination (@jkeyes) + 3.5.9 - Fix event create method From 1419719964d702f853d06370bb307a6a795c272f Mon Sep 17 00:00:00 2001 From: Skaelv Date: Thu, 9 Feb 2017 17:09:23 +0000 Subject: [PATCH 067/245] Bump to 3.5.11 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 31fec8a1..2fd886dd 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.10" + VERSION = "3.5.11" end From 7feed4f27a3c797c68e87e81e508198496544866 Mon Sep 17 00:00:00 2001 From: Tarek Khalil Date: Thu, 16 Feb 2017 11:28:01 +0000 Subject: [PATCH 068/245] Add company scroll api --- lib/intercom/service/company.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/intercom/service/company.rb b/lib/intercom/service/company.rb index d6645751..fd2e0b56 100644 --- a/lib/intercom/service/company.rb +++ b/lib/intercom/service/company.rb @@ -1,5 +1,6 @@ require 'intercom/service/base_service' require 'intercom/api_operations/list' +require 'intercom/api_operations/scroll' require 'intercom/api_operations/find' require 'intercom/api_operations/find_all' require 'intercom/api_operations/save' @@ -15,6 +16,7 @@ class Company < BaseService include ApiOperations::FindAll include ApiOperations::Load include ApiOperations::List + include ApiOperations::Scroll include ApiOperations::Save include ExtendedApiOperations::Users include ExtendedApiOperations::Tags From 63875ad7821307f182d864a31b6da7eb5e78007a Mon Sep 17 00:00:00 2001 From: Tarek Khalil Date: Thu, 16 Feb 2017 13:35:20 +0000 Subject: [PATCH 069/245] Add changes to new release --- changes.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changes.txt b/changes.txt index fe2b1344..0158fff7 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.5.11 +- Add scroll api for companies + 3.5.10 - Add Support for find_all events pagination (@jkeyes) From b9ee705c8b4e29beb3040e9eca6434fc58f221d8 Mon Sep 17 00:00:00 2001 From: Tarek Khalil Date: Thu, 16 Feb 2017 13:43:47 +0000 Subject: [PATCH 070/245] Bump to 3.5.12 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 2fd886dd..4df2f997 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.11" + VERSION = "3.5.12" end From 85971fa7250e722eafd1ef9fb1903cc6552dcbc7 Mon Sep 17 00:00:00 2001 From: Jaime Iniesta Date: Mon, 20 Feb 2017 16:43:27 +0100 Subject: [PATCH 071/245] raise a RateLimitExceeded error when the response code is 429 --- lib/intercom/request.rb | 2 ++ spec/unit/intercom/request_spec.rb | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index a49f3530..093fb010 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -115,6 +115,8 @@ def raise_errors_on_failure(res) raise Intercom::AuthenticationError.new('Unauthorized') elsif res.code.to_i.eql?(403) raise Intercom::AuthenticationError.new('Forbidden') + elsif res.code.to_i.eql?(429) + raise Intercom::RateLimitExceeded.new('Rate Limit Exceeded') elsif res.code.to_i.eql?(500) raise Intercom::ServerError.new('Server Error') elsif res.code.to_i.eql?(502) diff --git a/spec/unit/intercom/request_spec.rb b/spec/unit/intercom/request_spec.rb index 0c1b38d9..0a2fdf82 100644 --- a/spec/unit/intercom/request_spec.rb +++ b/spec/unit/intercom/request_spec.rb @@ -8,6 +8,12 @@ proc {req.parse_body('somethjing', response)}.must_raise(Intercom::ServerError) end + it 'raises a RateLimitExceeded error when the response code is 429' do + response = OpenStruct.new(:code => 429) + req = Intercom::Request.new('path/', 'GET') + proc {req.parse_body('somethjing', response)}.must_raise(Intercom::RateLimitExceeded) + end + it 'parse_body returns nil if decoded_body is nil' do response = OpenStruct.new(:code => 500) req = Intercom::Request.new('path/', 'GET') From be20c4f9a763026c6a27350cc23c519b4b33b35e Mon Sep 17 00:00:00 2001 From: Jaime Iniesta Date: Mon, 20 Feb 2017 17:22:35 +0100 Subject: [PATCH 072/245] ensures rate_limit_details are set on the client even when an exception is raised --- lib/intercom/client.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/intercom/client.rb b/lib/intercom/client.rb index 7b69498a..861bed83 100644 --- a/lib/intercom/client.rb +++ b/lib/intercom/client.rb @@ -108,9 +108,9 @@ def validate_credentials! end def execute_request(request) - result = request.execute(@base_url, username: @username_part, secret: @password_part) + request.execute(@base_url, username: @username_part, secret: @password_part) + ensure @rate_limit_details = request.rate_limit_details - result end def base_url=(new_url) From 28991b60713623c25b12a1e018a41c77a00af872 Mon Sep 17 00:00:00 2001 From: Tarek Khalil Date: Thu, 9 Mar 2017 17:23:39 +0000 Subject: [PATCH 073/245] Add code example to delete a Webhook Subscription --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index c29818d6..96252d4d 100644 --- a/README.md +++ b/README.md @@ -453,6 +453,10 @@ intercom.subscriptions.create(url: "http://example.com", topics: ["user.created" # fetch a subscription intercom.subscriptions.find(id: "nsub_123456789") +# delete a subscription +subscription = intercom.subscriptions.find(id: "nsub_123456789") +intercom.subscriptions.delete(subscription) + # list subscriptions intercom.subscriptions.all ``` From 279650977336de78addbcedf0fd2df6082e94c76 Mon Sep 17 00:00:00 2001 From: Brian White Date: Thu, 11 May 2017 14:07:31 +0100 Subject: [PATCH 074/245] Documentation update --- README.md | 68 ------------------------------------------------------- 1 file changed, 68 deletions(-) diff --git a/README.md b/README.md index 96252d4d..a6443ec5 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,6 @@ Resources this API supports: https://api.intercom.io/messages https://api.intercom.io/subscriptions https://api.intercom.io/jobs - https://api.intercom.io/bulk ### Examples @@ -100,16 +99,6 @@ result = intercom.users.scroll.next result.scroll_param => "0730e341-63ef-44da-ab9c-9113f886326d" result = intercom.users.scroll.next("0730e341-63ef-44da-ab9c-9113f886326d"); - -#Bulk operations. -# Submit bulk job to create users. If any of the items in create_items match an existing user that user will be updated -intercom.users.submit_bulk_job(create_items: [{user_id: "25", email: "alice@example.com"}, {user_id: "25", email: "bob@example.com"}]) -# Submit bulk job to create users with companies. Companies must be sent as an array of objects nested within each applicable user object -intercom.users.submit_bulk_job(create_items: [{user_id: "25", email: "alice@example.com", companies: [{company_id: 9, name: "Test Company"}]}]) -# Submit bulk job, to delete users -intercom.users.submit_bulk_job(delete_items: [{user_id: "25", email: "alice@example.com"}, {user_id: "25", email: "bob@example.com"}]) -# Submit bulk job, to add items to existing job -intercom.users.submit_bulk_job(create_items: [{user_id: "25", email: "alice@example.com"}], delete_items: [{user_id: "25", email: "bob@example.com"}], job_id:'job_abcd1234') ``` #### Admins @@ -362,54 +351,6 @@ The metadata key values in the example are treated as follows- *NB:* This version of the gem reserves the field name `type` in Event data. -Bulk operations. -```ruby -# Submit bulk job, to create events -intercom.events.submit_bulk_job(create_items: [ - { - event_name: "ordered-item", - created_at: 1438944980, - user_id: "314159", - metadata: { - order_date: 1438944980, - stripe_invoice: "inv_3434343434" - } - }, - { - event_name: "invited-friend", - created_at: 1438944979, - user_id: "314159", - metadata: { - invitee_email: "pi@example.org", - invite_code: "ADDAFRIEND" - } - } -]) - - -# Submit bulk job, to add items to existing job -intercom.events.submit_bulk_job(create_items: [ - { - event_name: "ordered-item", - created_at: 1438944980, - user_id: "314159", - metadata: { - order_date: 1438944980, - stripe_invoice: "inv_3434343434" - } - }, - { - event_name: "invited-friend", - created_at: 1438944979, - user_id: "314159", - metadata: { - invitee_email: "pi@example.org", - invite_code: "ADDAFRIEND" - } - } -], job_id:'job_abcd1234') -``` - ### Contacts `Contacts` represent logged out users of your application. @@ -460,15 +401,6 @@ intercom.subscriptions.delete(subscription) # list subscriptions intercom.subscriptions.all ``` -### Bulk jobs - -```ruby -# fetch a job -intercom.jobs.find(id: 'job_abcd1234') - -# fetch a job's error feed -intercom.jobs.errors(id: 'job_abcd1234') -``` ### Errors From c65be5fa301393246155640bea99422f34481037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miche=C3=A1l=20Looney?= Date: Thu, 11 May 2017 22:02:58 +0100 Subject: [PATCH 075/245] Clarify behaviour of the before query parameter --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a6443ec5..383f76a5 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,7 @@ intercom.conversations.find_all(type: 'admin', id: '7').each {|convo| ... } intercom.conversations.find_all(type: 'admin', id: 7, open: true).each {|convo| ... } # Iterate over closed conversations assigned to an admin intercom.conversations.find_all(type: 'admin', id: 7, open: false).each {|convo| ... } -# Iterate over closed conversations for assigned an admin, before a certain moment in time +# Iterate over closed conversations which are assigned to an admin, and where updated_at is before a certain moment in time intercom.conversations.find_all(type: 'admin', id: 7, open: false, before: 1374844930).each {|convo| ... } # FINDING CONVERSATIONS FOR A USER From 56d445e9a0c810060744706388809a1c628a0e5d Mon Sep 17 00:00:00 2001 From: Alexandre Passant Date: Tue, 16 May 2017 14:54:07 +0100 Subject: [PATCH 076/245] Use base_url in initialize parameter --- changes.txt | 3 +++ lib/intercom/client.rb | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/changes.txt b/changes.txt index 0158fff7..1adb0f2d 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.5.12 +- Use base_url in initialize parameter + 3.5.11 - Add scroll api for companies diff --git a/lib/intercom/client.rb b/lib/intercom/client.rb index 7b69498a..c0222a6b 100644 --- a/lib/intercom/client.rb +++ b/lib/intercom/client.rb @@ -14,7 +14,7 @@ def set_base_url(base_url) end end - def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil) + def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil, base_url:'https://api.intercom.io') if token @username_part = token @password_part = "" @@ -24,7 +24,7 @@ def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil) end validate_credentials! - @base_url = 'https://api.intercom.io' + @base_url = base_url @rate_limit_details = {} end From 65a1b7f8d3fdd17bd0ec48f5ce7796e7cfb15666 Mon Sep 17 00:00:00 2001 From: Brian White Date: Thu, 18 May 2017 16:27:15 +0100 Subject: [PATCH 077/245] Bump to 3.5.13 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 4df2f997..53243ba4 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.12" + VERSION = "3.5.13" end From 891802657b0bcee8353f27a70a948ab96edd58ce Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 19 May 2017 16:21:34 +0100 Subject: [PATCH 078/245] Bump to 3.5.14 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 53243ba4..ed9263a2 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.13" + VERSION = "3.5.14" end From 91b727f1e568e35e97c4321caf89deac38181d5d Mon Sep 17 00:00:00 2001 From: Skaelv Date: Wed, 7 Jun 2017 11:33:24 +0100 Subject: [PATCH 079/245] raise unauthorizedError on invalid token --- lib/intercom/errors.rb | 4 ++-- lib/intercom/request.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index 549b6864..d9d6d850 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -23,8 +23,8 @@ def to_hash end end - # Raised when the credentials you provide don't match a valid account on Intercom. - # Check that you have set Intercom.app_id= and Intercom.app_api_key= correctly. + # Raised when the token you provided is incorrect or not authorized to access certain type of data. + # Check that you have set Intercom.token correctly. class AuthenticationError < IntercomError; end # Raised when something goes wrong on within the Intercom API service. diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 093fb010..64dff40c 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -139,7 +139,7 @@ def raise_application_errors_on_failure(error_list_details, http_code) :request_id => error_list_details['request_id'] } case error_code - when 'unauthorized', 'forbidden' + when 'unauthorized', 'forbidden', 'token_not_found' raise Intercom::AuthenticationError.new(error_details['message'], error_context) when "bad_request", "missing_parameter", 'parameter_invalid', 'parameter_not_found' raise Intercom::BadRequestError.new(error_details['message'], error_context) From 96ddabbad35f4d13867a49fed91496a8c5909859 Mon Sep 17 00:00:00 2001 From: Skaelv Date: Wed, 7 Jun 2017 14:38:35 +0100 Subject: [PATCH 080/245] better error message when tryign to restore a blocked user --- lib/intercom/errors.rb | 4 ++++ lib/intercom/request.rb | 2 ++ 2 files changed, 6 insertions(+) diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index 549b6864..8a0afedb 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -54,6 +54,9 @@ class UnexpectedError < IntercomError; end # Raised when multiple users match the query (typically duplicate email addresses) class MultipleMatchingUsersError < IntercomError; end + # Raised when restoring a blocked user + class BlockedUserError < IntercomError ; end + # Raised when you try to call a non-setter method that does not exist on an object class Intercom::AttributeNotSetError < IntercomError ; end @@ -71,4 +74,5 @@ class IntercomInternalError < StandardError; end class Intercom::NoMethodMissingHandler < IntercomInternalError; end class Intercom::DeserializationError < IntercomInternalError; end + end diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 093fb010..87d2c7f1 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -143,6 +143,8 @@ def raise_application_errors_on_failure(error_list_details, http_code) raise Intercom::AuthenticationError.new(error_details['message'], error_context) when "bad_request", "missing_parameter", 'parameter_invalid', 'parameter_not_found' raise Intercom::BadRequestError.new(error_details['message'], error_context) + when "not_restorable" + raise Intercom::BlockedUser.new(error_details['message'], error_context) when "not_found" raise Intercom::ResourceNotFound.new(error_details['message'], error_context) when "rate_limit_exceeded" From ebc060320fc174fd7352e380a47fa5ccbc778769 Mon Sep 17 00:00:00 2001 From: Kevin Antoine Date: Wed, 7 Jun 2017 15:15:40 +0100 Subject: [PATCH 081/245] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 383f76a5..b6938872 100644 --- a/README.md +++ b/README.md @@ -416,6 +416,7 @@ Intercom::ServerError Intercom::ServiceUnavailableError Intercom::ServiceConnectionError Intercom::ResourceNotFound +Intercom::BlockedUserError Intercom::BadRequestError Intercom::RateLimitExceeded Intercom::AttributeNotSetError # Raised when you try to call a getter that does not exist on an object From ad2820346b1c97fbb2d2916e589d0db31453fbb1 Mon Sep 17 00:00:00 2001 From: Kevin Antoine Date: Wed, 7 Jun 2017 16:18:48 +0100 Subject: [PATCH 082/245] Update changes.txt --- changes.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/changes.txt b/changes.txt index 1adb0f2d..4f0fcb63 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,10 @@ +3.5.15 +- UnauthorizedError on invalid token +- BlockerUserError on restoring blocked user + +3.5.14 +- Rate Limit Exception (@jaimeiniesta) + 3.5.12 - Use base_url in initialize parameter From d96a5806d0b4313573e9f5120655a7a62da882ae Mon Sep 17 00:00:00 2001 From: Skaelv Date: Wed, 7 Jun 2017 16:19:23 +0100 Subject: [PATCH 083/245] Bump to 3.5.15 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index ed9263a2..c51773d5 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.14" + VERSION = "3.5.15" end From c4f4aa014336e062564343db424afa0a536b9b70 Mon Sep 17 00:00:00 2001 From: Shayon Mukherjee Date: Sun, 11 Jun 2017 13:53:37 -0700 Subject: [PATCH 084/245] Standardize comparison of attribute as string when input is Hash or JSON Adding `.to_s` properly follows the convention of not initializing `custom_attributes` or any other attribute which passes the reserve field `type`. Not having `.to_s`, meant if ApiResource is initialized by `User` or any other class, with a `Hash`, and the attribute contains the reserved `type` `Hash` attribute, it would incorrectly try to initialize the same property. Ex of how it would fail, if `User` class is initialized with custom_attributes that contains `type`: ```ruby Intercom::User.new(email: "john@doe.com", custom_attributes: {"type"=>"ping"}) => Intercom::AttributeNotSetError: 'each' called on Intercom::Ping but it has not been set an attribute or does not exist as a method ... ``` A workaround would be that all keys were passed as strings, however this PR attempts at standardizing the comparison of any `attribute` always as string, without being dependent on input type (`Hash` or `JSON`). I took additional liberty and applied these changes for `type_field?` and `typed_property?` check for `metadata` attribute as well. `metadata`, because as per the specs, it can contain the reserved `type` `Hash` as well. Happy to tweak/modify/close, if the changes raise any concern :). --- lib/intercom/traits/api_resource.rb | 10 ++--- .../unit/intercom/traits/api_resource_spec.rb | 37 +++++++++++++++++-- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/lib/intercom/traits/api_resource.rb b/lib/intercom/traits/api_resource.rb index 5dbca91e..95b8ccad 100644 --- a/lib/intercom/traits/api_resource.rb +++ b/lib/intercom/traits/api_resource.rb @@ -78,13 +78,13 @@ def set_property(attribute, value) end def custom_attribute_field?(attribute) - attribute == 'custom_attributes' + attribute.to_s == 'custom_attributes' end - + def message_from_field?(attribute, value) attribute.to_s == 'from' && value.is_a?(Hash) && value['type'] end - + def message_to_field?(attribute, value) attribute.to_s == 'to' && value.is_a?(Hash) && value['type'] end @@ -94,7 +94,7 @@ def typed_property?(attribute, value) !custom_attribute_field?(attribute) && !message_from_field?(attribute, value) && !message_to_field?(attribute, value) && - attribute != 'metadata' + attribute.to_s != 'metadata' end def typed_value?(value) @@ -107,7 +107,7 @@ def call_setter_for_attribute(attribute, value) end def type_field?(attribute) - attribute == 'type' + attribute.to_s == 'type' end def initialize_missing_flat_store_attributes diff --git a/spec/unit/intercom/traits/api_resource_spec.rb b/spec/unit/intercom/traits/api_resource_spec.rb index 04c956fe..843b0a18 100644 --- a/spec/unit/intercom/traits/api_resource_spec.rb +++ b/spec/unit/intercom/traits/api_resource_spec.rb @@ -17,6 +17,26 @@ "color"=>"cyan" }} end + + let(:object_hash) do + { + type: "company", + id: "aaaaaaaaaaaaaaaaaaaaaaaa", + app_id: "some-app-id", + name: "SuperSuite", + plan_id: 1, + remote_company_id: "8", + remote_created_at: 103201, + created_at: 1374056196, + user_count: 1, + custom_attributes: { type: "ping" }, + metadata: { + type: "user", + color: "cyan" + } + } + end + let(:api_resource) { DummyClass.new.extend(Intercom::Traits::ApiResource)} before(:each) { api_resource.from_response(object_json) } @@ -76,18 +96,29 @@ proc { api_resource.send(:flubber=, 'a', 'b') }.must_raise NoMethodError end - it "an initialized ApiResource is equal to on generated from a response" do + it "an initialized ApiResource is equal to one generated from a response" do class ConcreteApiResource; include Intercom::Traits::ApiResource; end initialized_api_resource = ConcreteApiResource.new(object_json) except(object_json, 'type').keys.each do |attribute| assert_equal initialized_api_resource.send(attribute), api_resource.send(attribute) end end - + + it "initialized ApiResource using hash is equal to one generated from response" do + class ConcreteApiResource; include Intercom::Traits::ApiResource; end + + api_resource.from_hash(object_hash) + initialized_api_resource = ConcreteApiResource.new(object_hash) + + except(object_json, 'type').keys.each do |attribute| + assert_equal initialized_api_resource.send(attribute), api_resource.send(attribute) + end + end + def except(h, *keys) keys.each { |key| h.delete(key) } h end - + class DummyClass; end end From d6fa93eed6cd6e17c7c96b8107fb9a00e777e750 Mon Sep 17 00:00:00 2001 From: Kevin Antoine Date: Mon, 12 Jun 2017 11:00:21 +0100 Subject: [PATCH 085/245] Create changes.txt --- changes.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changes.txt b/changes.txt index 4f0fcb63..954d49f6 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.5.16 + - Standardize comparison of attribute as string when input is Hash or JSON + 3.5.15 - UnauthorizedError on invalid token - BlockerUserError on restoring blocked user From c60068e7b999fb2f03a5f474b8f78ef90841ac41 Mon Sep 17 00:00:00 2001 From: Skaelv Date: Mon, 12 Jun 2017 11:14:43 +0100 Subject: [PATCH 086/245] Bump to 3.5.16 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index c51773d5..100162e0 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.15" + VERSION = "3.5.16" end From 9d01fdcab53b9de212c37323617a53cbf6837fdd Mon Sep 17 00:00:00 2001 From: Kevin Antoine Date: Tue, 11 Jul 2017 13:19:47 +0100 Subject: [PATCH 087/245] blockedUserError typo --- lib/intercom/request.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 26026f53..e5e72560 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -144,7 +144,7 @@ def raise_application_errors_on_failure(error_list_details, http_code) when "bad_request", "missing_parameter", 'parameter_invalid', 'parameter_not_found' raise Intercom::BadRequestError.new(error_details['message'], error_context) when "not_restorable" - raise Intercom::BlockedUser.new(error_details['message'], error_context) + raise Intercom::BlockedUserError.new(error_details['message'], error_context) when "not_found" raise Intercom::ResourceNotFound.new(error_details['message'], error_context) when "rate_limit_exceeded" From 923b2a46581c40fa77a81078b92ad844c476ffef Mon Sep 17 00:00:00 2001 From: Skaelv Date: Tue, 11 Jul 2017 13:30:21 +0100 Subject: [PATCH 088/245] Bump to 3.5.17 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 100162e0..e2f606c7 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.16" + VERSION = "3.5.17" end From d02264ffde78de14b19dfc0ed3c803ecf2ebbbb1 Mon Sep 17 00:00:00 2001 From: Kevin Antoine Date: Tue, 11 Jul 2017 13:30:39 +0100 Subject: [PATCH 089/245] Update changes.txt --- changes.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changes.txt b/changes.txt index 954d49f6..73bc00cf 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.5.17 + - Fix BlockedUserError typo + 3.5.16 - Standardize comparison of attribute as string when input is Hash or JSON From 59f2c96ec9e05e38fb011524f9cc34653c1f4d4c Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Thu, 17 Aug 2017 11:35:23 +0100 Subject: [PATCH 090/245] Adding lead and company scroll example --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index b6938872..0e7dc209 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,9 @@ intercom.companies.all.each {|company| puts %Q(#{company.name} - #{company.custo intercom.companies.all.map {|company| company.name } # Get a list of users in a company intercom.companies.users(company.id) +# Get a large list of companies using scroll +intercom.companies.scroll.each { |comp| puts comp.name} +# Please see users scroll for more details of how to use scroll ``` #### Tags @@ -371,6 +374,10 @@ intercom.contacts.convert(contact, user) # Delete a contact intercom.contacts.delete(contact) + +# Get a large list of contacts using scroll +intercom.contacts.scroll.each { |lead| puts lead.id} +# Please see users scroll for more details of how to use scroll ``` ### Counts From 72967659482204576921bc4b99ede062490c5621 Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Fri, 6 Oct 2017 16:20:58 +0100 Subject: [PATCH 091/245] Handle rate limits (#354) * Retry on RateLimitError after reset time * Make feature optional * Adding limit to number of retries after 429 * Minor change to retries limit variable * Corrected minor grammer error * Setting rate limit flag in init * Adding bundle updates to travis file * Adding bundle command to address travis failing issue --- .travis.yml | 4 +++ Gemfile | 1 + README.md | 6 ++++ lib/intercom/client.rb | 6 ++-- lib/intercom/request.rb | 11 +++++++- spec/spec_helper.rb | 2 ++ spec/unit/intercom/request_spec.rb | 45 +++++++++++++++++++++++++++++- 7 files changed, 71 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 98ede9d8..7a3c3d1d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,9 @@ language: ruby sudo: false +before_install: + - gem install bundler + - gem update --system + - gem --version rvm: - 2.1.0 - 2.2.0 diff --git a/Gemfile b/Gemfile index 743f2a99..d729c317 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,6 @@ source "http://rubygems.org" +gem 'webmock' gemspec group :development, :test do diff --git a/README.md b/README.md index 0e7dc209..9719e081 100644 --- a/README.md +++ b/README.md @@ -440,6 +440,12 @@ intercom.rate_limit_details #=> {:limit=>180, :remaining=>179, :reset_at=>2014-10-07 14:58:00 +0100} ``` +You can handle the rate limits yourself but a simple option is to use the handle_rate_limit flag. +This will automatically catch the 429 rate limit exceeded error and wait until the reset time to retry. + +``` +intercom = Intercom::Client.new(token: ENV['AT'], handle_rate_limit: true) +``` ### Pull Requests diff --git a/lib/intercom/client.rb b/lib/intercom/client.rb index d873f417..3128ea54 100644 --- a/lib/intercom/client.rb +++ b/lib/intercom/client.rb @@ -2,7 +2,7 @@ module Intercom class MisconfiguredClientError < StandardError; end class Client include Options - attr_reader :base_url, :rate_limit_details, :username_part, :password_part + attr_reader :base_url, :rate_limit_details, :username_part, :password_part, :handle_rate_limit class << self def set_base_url(base_url) @@ -14,7 +14,7 @@ def set_base_url(base_url) end end - def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil, base_url:'https://api.intercom.io') + def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil, base_url:'https://api.intercom.io', handle_rate_limit: false) if token @username_part = token @password_part = "" @@ -26,6 +26,7 @@ def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil, base_url: @base_url = base_url @rate_limit_details = {} + @handle_rate_limit = handle_rate_limit end def admins @@ -108,6 +109,7 @@ def validate_credentials! end def execute_request(request) + request.handle_rate_limit = handle_rate_limit request.execute(@base_url, username: @username_part, secret: @password_part) ensure @rate_limit_details = request.rate_limit_details diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index e5e72560..c8ef572e 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -3,11 +3,12 @@ module Intercom class Request - attr_accessor :path, :net_http_method, :rate_limit_details + attr_accessor :path, :net_http_method, :rate_limit_details, :handle_rate_limit def initialize(path, net_http_method) self.path = path self.net_http_method = net_http_method + self.handle_rate_limit = false end def set_common_headers(method, base_uri) @@ -58,6 +59,7 @@ def client(uri) end def execute(target_base_url=nil, username:, secret: nil) + retries = 3 base_uri = URI.parse(target_base_url) set_common_headers(net_http_method, base_uri) set_basic_auth(net_http_method, username, secret) @@ -70,6 +72,13 @@ def execute(target_base_url=nil, username:, secret: nil) parsed_body = parse_body(decoded_body, response) raise_errors_on_failure(response) parsed_body + rescue Intercom::RateLimitExceeded => e + if @handle_rate_limit + sleep (@rate_limit_details[:reset_at] - Time.now.utc).ceil + retry unless (retries -=1).zero? + else + raise e + end rescue Timeout::Error raise Intercom::ServiceUnavailableError.new('Service Unavailable [request timed out]') end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 14ed7ad6..81ca2f5b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,8 @@ require 'intercom' require 'minitest/autorun' require 'mocha/setup' +require 'webmock' +include WebMock::API def test_user(email="bob@example.com") { diff --git a/spec/unit/intercom/request_spec.rb b/spec/unit/intercom/request_spec.rb index 0a2fdf82..85139707 100644 --- a/spec/unit/intercom/request_spec.rb +++ b/spec/unit/intercom/request_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' require 'ostruct' +WebMock.enable! + describe 'Intercom::Request' do it 'raises an error when a html error page rendered' do response = OpenStruct.new(:code => 500) @@ -14,9 +16,50 @@ proc {req.parse_body('somethjing', response)}.must_raise(Intercom::RateLimitExceeded) end + describe 'Intercom::Client' do + let (:client) { Intercom::Client.new(token: 'foo', handle_rate_limit: true) } + let (:uri) {"https://api.intercom.io/users"} + + it 'should have handle_rate_limit set' do + client.handle_rate_limit.must_equal(true) + end + + it 'should call sleep for rate limit error three times' do + # Use webmock to mock the HTTP request + stub_request(:any, uri).\ + to_return(status: [429, "Too Many Requests"], headers: { 'X-RateLimit-Reset' => Time.now.utc + 10 }) + req = Intercom::Request.get(uri, "") + req.handle_rate_limit=true + req.expects(:sleep).times(3).with(any_parameters) + req.execute(target_base_url=uri, username: "ted", secret: "") + end + + it 'should not call sleep for rate limit error' do + # Use webmock to mock the HTTP request + stub_request(:any, uri).\ + to_return(status: [200, "OK"], headers: { 'X-RateLimit-Reset' => Time.now.utc + 10 }) + req = Intercom::Request.get(uri, "") + req.handle_rate_limit=true + req.expects(:sleep).never.with(any_parameters) + req.execute(target_base_url=uri, username: "ted", secret: "") + end + + it 'should call sleep for rate limit error just once' do + # Use webmock to mock the HTTP request + stub_request(:any, uri).\ + to_return(status: [429, "Too Many Requests"], headers: { 'X-RateLimit-Reset' => Time.now.utc + 10 }).\ + then.to_return(status: [200, "OK"]) + req = Intercom::Request.get(uri, "") + req.handle_rate_limit=true + req.expects(:sleep).with(any_parameters) + req.execute(target_base_url=uri, username: "ted", secret: "") + end + + end + it 'parse_body returns nil if decoded_body is nil' do response = OpenStruct.new(:code => 500) req = Intercom::Request.new('path/', 'GET') - req.parse_body(nil, response).must_equal(nil) + assert_nil(req.parse_body(nil, response)) end end From 3f1d59ef856a3854790276733a465c1919c4b353 Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Fri, 6 Oct 2017 16:41:22 +0100 Subject: [PATCH 092/245] Bumping version (#355) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9719e081..0c19e566 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.5.10' + gem 'intercom', '~> 3.5.18' ## Basic Usage From b88e11caca9feb50135e3d0a3187c185972d3882 Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Fri, 6 Oct 2017 15:46:26 +0000 Subject: [PATCH 093/245] Bump to 3.5.19 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index e2f606c7..d5e3b475 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.17" + VERSION = "3.5.19" end From 116b8f8906bc10fb9f2b659062e0d08e665f56ae Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Fri, 6 Oct 2017 16:55:59 +0100 Subject: [PATCH 094/245] Changing version, for some reason it skipped 18 (#356) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0c19e566..07e5e2e6 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.5.18' + gem 'intercom', '~> 3.5.19' ## Basic Usage From 042fb37450769e77ae19a06c19723a25ef7d7f13 Mon Sep 17 00:00:00 2001 From: Ian Candy Date: Fri, 13 Oct 2017 04:55:33 -0400 Subject: [PATCH 095/245] Fixing issue with NoMethodError in #339 --- lib/intercom/request.rb | 2 +- spec/unit/intercom/request_spec.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index c8ef572e..fee85289 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -100,7 +100,7 @@ def parse_body(decoded_body, response) rescue JSON::ParserError => _ raise_errors_on_failure(response) end - raise_application_errors_on_failure(parsed_body, response.code.to_i) if parsed_body['type'] == 'error.list' + raise_application_errors_on_failure(parsed_body, response.code.to_i) if parsed_body&['type'] == 'error.list' parsed_body end diff --git a/spec/unit/intercom/request_spec.rb b/spec/unit/intercom/request_spec.rb index 85139707..922d28d8 100644 --- a/spec/unit/intercom/request_spec.rb +++ b/spec/unit/intercom/request_spec.rb @@ -62,4 +62,10 @@ req = Intercom::Request.new('path/', 'GET') assert_nil(req.parse_body(nil, response)) end + + it 'parse_body returns nil if the decoded_body is "null"' do + response = OpenStruct.new(:code => 500) + req = Intercom::Request.new('path/', 'GET') + req.parse_body('null', response).must_equal(nil) + end end From c3d3253fa7a9fdae0d7524cd90a695ee4616dd08 Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Fri, 13 Oct 2017 10:04:54 +0100 Subject: [PATCH 096/245] Bumping release version (#357) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 07e5e2e6..2bdba150 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.5.19' + gem 'intercom', '~> 3.5.20' ## Basic Usage From 0830714127cc59cbd5f8015d6f392688f4e73dff Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Fri, 13 Oct 2017 09:08:39 +0000 Subject: [PATCH 097/245] Bump to 3.5.20 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index d5e3b475..bcfca96f 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.19" + VERSION = "3.5.20" end From 3cea877dd5ff4f2e304a4843194ca58015720109 Mon Sep 17 00:00:00 2001 From: Ian Candy Date: Mon, 16 Oct 2017 06:01:34 -0400 Subject: [PATCH 098/245] Bugfix parse body (#359) * remove lonely operator * raise error if body is nil --- lib/intercom/request.rb | 3 ++- spec/unit/intercom/request_spec.rb | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index fee85289..c8c4f46d 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -100,7 +100,8 @@ def parse_body(decoded_body, response) rescue JSON::ParserError => _ raise_errors_on_failure(response) end - raise_application_errors_on_failure(parsed_body, response.code.to_i) if parsed_body&['type'] == 'error.list' + raise_errors_on_failure(response) if parsed_body.nil? + raise_application_errors_on_failure(parsed_body, response.code.to_i) if parsed_body['type'] == 'error.list' parsed_body end diff --git a/spec/unit/intercom/request_spec.rb b/spec/unit/intercom/request_spec.rb index 922d28d8..d2c880be 100644 --- a/spec/unit/intercom/request_spec.rb +++ b/spec/unit/intercom/request_spec.rb @@ -16,6 +16,12 @@ proc {req.parse_body('somethjing', response)}.must_raise(Intercom::RateLimitExceeded) end + it 'parse_body raises an error if the decoded_body is "null"' do + response = OpenStruct.new(:code => 500) + req = Intercom::Request.new('path/', 'GET') + proc { req.parse_body('null', response)}.must_raise(Intercom::ServerError) + end + describe 'Intercom::Client' do let (:client) { Intercom::Client.new(token: 'foo', handle_rate_limit: true) } let (:uri) {"https://api.intercom.io/users"} @@ -62,10 +68,4 @@ req = Intercom::Request.new('path/', 'GET') assert_nil(req.parse_body(nil, response)) end - - it 'parse_body returns nil if the decoded_body is "null"' do - response = OpenStruct.new(:code => 500) - req = Intercom::Request.new('path/', 'GET') - req.parse_body('null', response).must_equal(nil) - end end From ec1d22b5baf2286668e4c1ce34e5286edf00a388 Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Mon, 16 Oct 2017 11:29:29 +0100 Subject: [PATCH 099/245] Fix for PR353 (#362) --- README.md | 2 +- changes.txt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2bdba150..7ac490d6 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.5.20' + gem 'intercom', '~> 3.5.21' ## Basic Usage diff --git a/changes.txt b/changes.txt index 73bc00cf..388875b6 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,8 @@ +3.5.21 + - Fix for PR-353 which addressed "NoMethodError in intercom/request" + - There were issues on older versions of Ruby (<2.3) + - This PR does not use lonely operator and instead simple checks for nil parsed_body + 3.5.17 - Fix BlockedUserError typo From 6f2dd2a32ccacdc0ca8364a87e8068e5bf2e631e Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Mon, 16 Oct 2017 10:31:31 +0000 Subject: [PATCH 100/245] Bump to 3.5.21 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index bcfca96f..6520bfb0 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.20" + VERSION = "3.5.21" end From c5ebd86b74c9528d6e98d8b356bdb014bb86d28f Mon Sep 17 00:00:00 2001 From: Alexandre Passant Date: Wed, 8 Nov 2017 11:15:15 +0000 Subject: [PATCH 101/245] Return object type in API --- lib/intercom/traits/api_resource.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/intercom/traits/api_resource.rb b/lib/intercom/traits/api_resource.rb index 95b8ccad..e13f9574 100644 --- a/lib/intercom/traits/api_resource.rb +++ b/lib/intercom/traits/api_resource.rb @@ -24,7 +24,6 @@ def from_response(response) def from_hash(hash) hash.each do |attribute, value| - next if type_field?(attribute) initialize_property(attribute, value) end initialize_missing_flat_store_attributes if respond_to? :flat_store_attributes @@ -106,10 +105,6 @@ def call_setter_for_attribute(attribute, value) self.send(setter_method, value) end - def type_field?(attribute) - attribute.to_s == 'type' - end - def initialize_missing_flat_store_attributes flat_store_attributes.each do |attribute| unless instance_variables_excluding_dirty_tracking_field.map(&:to_s).include? "@#{attribute}" From d26a8624808f6adbd94dfcf4d30fa4f071755904 Mon Sep 17 00:00:00 2001 From: Alexandre Passant Date: Wed, 8 Nov 2017 11:26:43 +0000 Subject: [PATCH 102/245] Update version number --- README.md | 2 +- changes.txt | 3 +++ lib/intercom/version.rb | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7ac490d6..2909cfa2 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.5.21' + gem 'intercom', '~> 3.5.22' ## Basic Usage diff --git a/changes.txt b/changes.txt index 388875b6..0c959f42 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.5.22 + - Do not bypass type in api_resource + 3.5.21 - Fix for PR-353 which addressed "NoMethodError in intercom/request" - There were issues on older versions of Ruby (<2.3) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 6520bfb0..4cffd6d4 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.21" + VERSION = "3.5.22" end From 0c242cc30f7f328017b8243867437f0de2782bcc Mon Sep 17 00:00:00 2001 From: Alexandre Passant Date: Wed, 8 Nov 2017 11:33:26 +0000 Subject: [PATCH 103/245] Revert "Update version number" This reverts commit d26a8624808f6adbd94dfcf4d30fa4f071755904. --- README.md | 2 +- changes.txt | 3 --- lib/intercom/version.rb | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2909cfa2..7ac490d6 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.5.22' + gem 'intercom', '~> 3.5.21' ## Basic Usage diff --git a/changes.txt b/changes.txt index 0c959f42..388875b6 100644 --- a/changes.txt +++ b/changes.txt @@ -1,6 +1,3 @@ -3.5.22 - - Do not bypass type in api_resource - 3.5.21 - Fix for PR-353 which addressed "NoMethodError in intercom/request" - There were issues on older versions of Ruby (<2.3) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 4cffd6d4..6520bfb0 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.22" + VERSION = "3.5.21" end From 4212acfd3ae3ce09f93127e765e77eef97cc7f7e Mon Sep 17 00:00:00 2001 From: Alexandre Passant Date: Wed, 8 Nov 2017 11:34:41 +0000 Subject: [PATCH 104/245] Revert "Return object type in API" This reverts commit c5ebd86b74c9528d6e98d8b356bdb014bb86d28f. --- lib/intercom/traits/api_resource.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/intercom/traits/api_resource.rb b/lib/intercom/traits/api_resource.rb index e13f9574..95b8ccad 100644 --- a/lib/intercom/traits/api_resource.rb +++ b/lib/intercom/traits/api_resource.rb @@ -24,6 +24,7 @@ def from_response(response) def from_hash(hash) hash.each do |attribute, value| + next if type_field?(attribute) initialize_property(attribute, value) end initialize_missing_flat_store_attributes if respond_to? :flat_store_attributes @@ -105,6 +106,10 @@ def call_setter_for_attribute(attribute, value) self.send(setter_method, value) end + def type_field?(attribute) + attribute.to_s == 'type' + end + def initialize_missing_flat_store_attributes flat_store_attributes.each do |attribute| unless instance_variables_excluding_dirty_tracking_field.map(&:to_s).include? "@#{attribute}" From 61c8bc3f749a2351026733178f3311028207cc27 Mon Sep 17 00:00:00 2001 From: Alexandre Passant Date: Wed, 8 Nov 2017 11:36:40 +0000 Subject: [PATCH 105/245] Return object type --- README.md | 2 +- changes.txt | 3 +++ lib/intercom/traits/api_resource.rb | 5 ----- lib/intercom/version.rb | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7ac490d6..2909cfa2 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.5.21' + gem 'intercom', '~> 3.5.22' ## Basic Usage diff --git a/changes.txt b/changes.txt index 388875b6..6608339c 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.5.22 + - Return object type + 3.5.21 - Fix for PR-353 which addressed "NoMethodError in intercom/request" - There were issues on older versions of Ruby (<2.3) diff --git a/lib/intercom/traits/api_resource.rb b/lib/intercom/traits/api_resource.rb index 95b8ccad..e13f9574 100644 --- a/lib/intercom/traits/api_resource.rb +++ b/lib/intercom/traits/api_resource.rb @@ -24,7 +24,6 @@ def from_response(response) def from_hash(hash) hash.each do |attribute, value| - next if type_field?(attribute) initialize_property(attribute, value) end initialize_missing_flat_store_attributes if respond_to? :flat_store_attributes @@ -106,10 +105,6 @@ def call_setter_for_attribute(attribute, value) self.send(setter_method, value) end - def type_field?(attribute) - attribute.to_s == 'type' - end - def initialize_missing_flat_store_attributes flat_store_attributes.each do |attribute| unless instance_variables_excluding_dirty_tracking_field.map(&:to_s).include? "@#{attribute}" diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 6520bfb0..4cffd6d4 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.21" + VERSION = "3.5.22" end From 4e209e32df3d8a949145d72ce8802cfc0f0c8329 Mon Sep 17 00:00:00 2001 From: Alexandre Passant Date: Wed, 8 Nov 2017 11:44:56 +0000 Subject: [PATCH 106/245] Add default ruby version to bypass bundler error --- circle.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 circle.yml diff --git a/circle.yml b/circle.yml new file mode 100644 index 00000000..7ee40137 --- /dev/null +++ b/circle.yml @@ -0,0 +1,3 @@ +machine: + ruby: + version: 2.1.0 From e03723c74a80a317ab58e3e6d13551b78fba2af9 Mon Sep 17 00:00:00 2001 From: Alexandre Passant Date: Wed, 8 Nov 2017 11:45:55 +0000 Subject: [PATCH 107/245] Fix unit tests --- spec/unit/intercom/traits/api_resource_spec.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spec/unit/intercom/traits/api_resource_spec.rb b/spec/unit/intercom/traits/api_resource_spec.rb index 843b0a18..dfb95d0c 100644 --- a/spec/unit/intercom/traits/api_resource_spec.rb +++ b/spec/unit/intercom/traits/api_resource_spec.rb @@ -41,10 +41,6 @@ before(:each) { api_resource.from_response(object_json) } - it "does not set type on parsing json" do - api_resource.wont_respond_to :type - end - it "coerces time on parsing json" do assert_equal Time.at(1374056196), api_resource.created_at end From 86818ad8ef4b7905c8764e2552834bf986ba6ed8 Mon Sep 17 00:00:00 2001 From: Alexandre Passant Date: Wed, 8 Nov 2017 11:56:10 +0000 Subject: [PATCH 108/245] Run tests on CircleCI --- circle.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/circle.yml b/circle.yml index 7ee40137..2f2640a9 100644 --- a/circle.yml +++ b/circle.yml @@ -1,3 +1,6 @@ machine: ruby: version: 2.1.0 +test: + post: + - bundle exec rake From 1420179cafb6863d52f958fe4fdc218b23580152 Mon Sep 17 00:00:00 2001 From: Alexandre Passant Date: Wed, 8 Nov 2017 12:29:40 +0000 Subject: [PATCH 109/245] Do not change version number --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 4cffd6d4..6520bfb0 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.22" + VERSION = "3.5.21" end From d3cc945cc78a136abd623900076e5ad2a6ede9bd Mon Sep 17 00:00:00 2001 From: Alexandre Passant Date: Wed, 8 Nov 2017 13:12:08 +0000 Subject: [PATCH 110/245] Bump to 3.5.22 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 6520bfb0..4cffd6d4 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.21" + VERSION = "3.5.22" end From ee70f7682b21fb27e6a4e0ad84d8ee6dfe8cf0f7 Mon Sep 17 00:00:00 2001 From: sofia Date: Mon, 4 Dec 2017 15:35:24 +0000 Subject: [PATCH 111/245] ResourceNotUnique error when trying to create a resource that already exists in Intercom. (Intercom HTTP error code: resource_conflict) --- lib/intercom/errors.rb | 9 ++++++--- lib/intercom/request.rb | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index 39a50b72..6d8c2de7 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -42,6 +42,9 @@ class ServiceConnectionError < IntercomError; end # Raised when requesting resources on behalf of a user that doesn't exist in your application on Intercom. class ResourceNotFound < IntercomError; end + # Raised when trying to create a resource that already exists in Intercom. + class ResourceNotUniqueError < IntercomError; end + # Raised when the request has bad syntax class BadRequestError < IntercomError; end @@ -55,13 +58,13 @@ class UnexpectedError < IntercomError; end class MultipleMatchingUsersError < IntercomError; end # Raised when restoring a blocked user - class BlockedUserError < IntercomError ; end + class BlockedUserError < IntercomError; end # Raised when you try to call a non-setter method that does not exist on an object - class Intercom::AttributeNotSetError < IntercomError ; end + class Intercom::AttributeNotSetError < IntercomError; end # Raised when unexpected nil returned from server - class Intercom::HttpError < IntercomError ; end + class Intercom::HttpError < IntercomError; end # # Non-public errors (internal to the gem) diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index c8c4f46d..6bc5617b 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -163,6 +163,8 @@ def raise_application_errors_on_failure(error_list_details, http_code) raise Intercom::ServiceUnavailableError.new(error_details['message'], error_context) when 'conflict', 'unique_user_constraint' raise Intercom::MultipleMatchingUsersError.new(error_details['message'], error_context) + when 'resource_conflict' + raise Intercom::ResourceNotUniqueError.new(error_details['message'], error_context) when nil, '' raise Intercom::UnexpectedError.new(message_for_unexpected_error_without_type(error_details, parsed_http_code), error_context) else From 13ecae74b2cca0b0c5831cd9a474f49dbab17186 Mon Sep 17 00:00:00 2001 From: sofia Date: Mon, 4 Dec 2017 15:50:24 +0000 Subject: [PATCH 112/245] Revert previous changes after unintentendly merging to master --- lib/intercom/errors.rb | 5 +---- lib/intercom/request.rb | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index 6d8c2de7..49a5aefb 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -41,10 +41,7 @@ class ServiceConnectionError < IntercomError; end # Raised when requesting resources on behalf of a user that doesn't exist in your application on Intercom. class ResourceNotFound < IntercomError; end - - # Raised when trying to create a resource that already exists in Intercom. - class ResourceNotUniqueError < IntercomError; end - + # Raised when the request has bad syntax class BadRequestError < IntercomError; end diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 6bc5617b..c8c4f46d 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -163,8 +163,6 @@ def raise_application_errors_on_failure(error_list_details, http_code) raise Intercom::ServiceUnavailableError.new(error_details['message'], error_context) when 'conflict', 'unique_user_constraint' raise Intercom::MultipleMatchingUsersError.new(error_details['message'], error_context) - when 'resource_conflict' - raise Intercom::ResourceNotUniqueError.new(error_details['message'], error_context) when nil, '' raise Intercom::UnexpectedError.new(message_for_unexpected_error_without_type(error_details, parsed_http_code), error_context) else From 90d9a97faa393b3d28762beba010bf3f2bb81ac0 Mon Sep 17 00:00:00 2001 From: sofia Date: Mon, 4 Dec 2017 15:59:33 +0000 Subject: [PATCH 113/245] added new error for specific error code --- lib/intercom/errors.rb | 5 ++++- lib/intercom/request.rb | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index 49a5aefb..6d8c2de7 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -41,7 +41,10 @@ class ServiceConnectionError < IntercomError; end # Raised when requesting resources on behalf of a user that doesn't exist in your application on Intercom. class ResourceNotFound < IntercomError; end - + + # Raised when trying to create a resource that already exists in Intercom. + class ResourceNotUniqueError < IntercomError; end + # Raised when the request has bad syntax class BadRequestError < IntercomError; end diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index c8c4f46d..6bc5617b 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -163,6 +163,8 @@ def raise_application_errors_on_failure(error_list_details, http_code) raise Intercom::ServiceUnavailableError.new(error_details['message'], error_context) when 'conflict', 'unique_user_constraint' raise Intercom::MultipleMatchingUsersError.new(error_details['message'], error_context) + when 'resource_conflict' + raise Intercom::ResourceNotUniqueError.new(error_details['message'], error_context) when nil, '' raise Intercom::UnexpectedError.new(message_for_unexpected_error_without_type(error_details, parsed_http_code), error_context) else From 803d2b128db9a69dc2524808f033112c18718357 Mon Sep 17 00:00:00 2001 From: sofia Date: Mon, 4 Dec 2017 16:04:04 +0000 Subject: [PATCH 114/245] Changes.txt and Readme --- README.md | 2 +- changes.txt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2909cfa2..1b7e595a 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.5.22' + gem 'intercom', '~> 3.5.23' ## Basic Usage diff --git a/changes.txt b/changes.txt index 6608339c..7a6c9334 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.5.23 + - New type of error (ResourceNotUniqueError). Thrown when trying to create a resource that already exists in Intercom + 3.5.22 - Return object type From ed982fc1f6a7357592462cbba346c4715db89ccd Mon Sep 17 00:00:00 2001 From: sofia Date: Mon, 4 Dec 2017 16:58:30 +0000 Subject: [PATCH 115/245] Added specs --- spec/unit/intercom/request_spec.rb | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/spec/unit/intercom/request_spec.rb b/spec/unit/intercom/request_spec.rb index d2c880be..5ca6239d 100644 --- a/spec/unit/intercom/request_spec.rb +++ b/spec/unit/intercom/request_spec.rb @@ -7,13 +7,13 @@ it 'raises an error when a html error page rendered' do response = OpenStruct.new(:code => 500) req = Intercom::Request.new('path/', 'GET') - proc {req.parse_body('somethjing', response)}.must_raise(Intercom::ServerError) + proc {req.parse_body('something', response)}.must_raise(Intercom::ServerError) end it 'raises a RateLimitExceeded error when the response code is 429' do response = OpenStruct.new(:code => 429) req = Intercom::Request.new('path/', 'GET') - proc {req.parse_body('somethjing', response)}.must_raise(Intercom::RateLimitExceeded) + proc {req.parse_body('something', response)}.must_raise(Intercom::RateLimitExceeded) end it 'parse_body raises an error if the decoded_body is "null"' do @@ -23,8 +23,8 @@ end describe 'Intercom::Client' do - let (:client) { Intercom::Client.new(token: 'foo', handle_rate_limit: true) } - let (:uri) {"https://api.intercom.io/users"} + let(:client) { Intercom::Client.new(token: 'foo', handle_rate_limit: true) } + let(:uri) {"https://api.intercom.io/users"} it 'should have handle_rate_limit set' do client.handle_rate_limit.must_equal(true) @@ -63,6 +63,18 @@ end + + describe "Application errors on failure" do + let(:uri) {"https://api.intercom.io/conversations/reply"} + it 'should raise error' do + # Use webmock to mock the HTTP request + stub_request(:put, uri).\ + to_return(status: [409, "Resource Already Exists"], headers: { 'X-RateLimit-Reset' => Time.now.utc + 10 }, body: {type: "error.list", errors: [ code: "resource_conflict" ]}.to_json) + req = Intercom::Request.put(uri, "") + expect { req.execute(target_base_url=uri, username: "ted", secret: "") }.must_raise(Intercom::ResourceNotUniqueError) + end + end + it 'parse_body returns nil if decoded_body is nil' do response = OpenStruct.new(:code => 500) req = Intercom::Request.new('path/', 'GET') From 161e1c0184f84c45ccecda07e35d7c9ac3f1fdf0 Mon Sep 17 00:00:00 2001 From: sofia Date: Mon, 4 Dec 2017 17:02:34 +0000 Subject: [PATCH 116/245] amended commend on spec --- spec/unit/intercom/request_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/intercom/request_spec.rb b/spec/unit/intercom/request_spec.rb index 5ca6239d..68abfeb3 100644 --- a/spec/unit/intercom/request_spec.rb +++ b/spec/unit/intercom/request_spec.rb @@ -66,7 +66,7 @@ describe "Application errors on failure" do let(:uri) {"https://api.intercom.io/conversations/reply"} - it 'should raise error' do + it 'should raise ResourceNotUniqueError error on resource_conflict code' do # Use webmock to mock the HTTP request stub_request(:put, uri).\ to_return(status: [409, "Resource Already Exists"], headers: { 'X-RateLimit-Reset' => Time.now.utc + 10 }, body: {type: "error.list", errors: [ code: "resource_conflict" ]}.to_json) From 0fda77324201e5090fcbfbccb0a2744d96c73397 Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Mon, 4 Dec 2017 17:14:38 +0000 Subject: [PATCH 117/245] Bump to 3.5.23 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 4cffd6d4..1fe3d411 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.22" + VERSION = "3.5.23" end From 9dccf6ace4878543bb391d9cd9ff3d9a7b6d89a4 Mon Sep 17 00:00:00 2001 From: Masataka Pocke Kuwabara Date: Tue, 5 Dec 2017 10:23:00 +0900 Subject: [PATCH 118/245] Suppress unused variable warnings in the spec Currently, the spec has warnings about unused variables. The warnings are displayed by warning option of Ruby. ``` $ RUBYOPT='-w' bundle exec rake /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:41: warning: assigned but unused variable - per_page /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:41: warning: assigned but unused variable - page /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:41: warning: assigned but unused variable - total_pages /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:41: warning: assigned but unused variable - total_count /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:41: warning: assigned but unused variable - user_list /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:48: warning: assigned but unused variable - per_page /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:48: warning: assigned but unused variable - page /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:48: warning: assigned but unused variable - total_pages /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:48: warning: assigned but unused variable - total_count /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:48: warning: assigned but unused variable - user_list /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:55: warning: assigned but unused variable - per_page /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:55: warning: assigned but unused variable - page /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:55: warning: assigned but unused variable - total_pages /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:55: warning: assigned but unused variable - total_count /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:55: warning: assigned but unused variable - user_list /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:65: warning: assigned but unused variable - per_page /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:65: warning: assigned but unused variable - page /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:65: warning: assigned but unused variable - total_pages /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:65: warning: assigned but unused variable - total_count /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:65: warning: assigned but unused variable - user_list /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:75: warning: assigned but unused variable - per_page /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:75: warning: assigned but unused variable - page /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:75: warning: assigned but unused variable - total_pages /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:75: warning: assigned but unused variable - total_count /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/client_collection_proxy_spec.rb:75: warning: assigned but unused variable - user_list ... ``` This change will suppress theses warnings. Note: The spec has other warnings, but this change does not fix other warnings. Because I'm not sure how to fix these problems. --- spec/spec_helper.rb | 2 +- spec/unit/intercom/client_collection_proxy_spec.rb | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 81ca2f5b..4b7611a2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -235,7 +235,7 @@ def users_scroll(include_users= false) } end -def users_pagination(include_next_link=false, per_page=0, page=0, total_pages=0, total_count=0, user_list=[]) +def users_pagination(include_next_link:, per_page:, page:, total_pages:, total_count:, user_list:) { "type"=>"user.list", "pages"=> diff --git a/spec/unit/intercom/client_collection_proxy_spec.rb b/spec/unit/intercom/client_collection_proxy_spec.rb index ee682deb..88d231b9 100644 --- a/spec/unit/intercom/client_collection_proxy_spec.rb +++ b/spec/unit/intercom/client_collection_proxy_spec.rb @@ -38,21 +38,21 @@ test_user("user4@example.com"), test_user("user5@example.com"), test_user("user6@example.com"), test_user("user7@example.com"), test_user("user8@example.com"), test_user("user9@example.com"), test_user("user10@example.com")] - client.expects(:get).with("/users", {:type=>'users', :per_page => 10, :page => 1}).returns(users_pagination(false, per_page=10, page=1, total_pages=1, total_count=10, user_list=users)) + client.expects(:get).with("/users", {:type=>'users', :per_page => 10, :page => 1}).returns(users_pagination(include_next_link: false, per_page: 10, page: 1, total_pages: 1, total_count: 10, user_list: users)) result = client.users.find_all(:type=>'users', :per_page => 10, :page => 1).map {|user| user.email } result.must_equal %W(user1@example.com user2@example.com user3@example.com user4@example.com user5@example.com user6@example.com user7@example.com user8@example.com user9@example.com user10@example.com) end it "supports multi page pagination" do users = [test_user("user3@example.com"), test_user("user4@example.com")] - client.expects(:get).with("/users", {:type=>'users', :per_page => 2, :page => 3}).returns(users_pagination(true, per_page=2, page=3, total_pages=5, total_count=10, user_list=users)) + client.expects(:get).with("/users", {:type=>'users', :per_page => 2, :page => 3}).returns(users_pagination(include_next_link: true, per_page: 2, page: 3, total_pages: 5, total_count: 10, user_list: users)) result = client.users.find_all(:type=>'users', :per_page => 2, :page => 3).map {|user| user.email } result.must_equal %W(user3@example.com user4@example.com) end it "works with page out of range request" do users = [] - client.expects(:get).with("/users", {:type=>'users', :per_page => 2, :page => 30}).returns(users_pagination(true, per_page=2, page=30, total_pages=2, total_count=3, user_list=users)) + client.expects(:get).with("/users", {:type=>'users', :per_page => 2, :page => 30}).returns(users_pagination(include_next_link: true, per_page: 2, page: 30, total_pages: 2, total_count: 3, user_list: users)) result = client.users.find_all(:type=>'users', :per_page => 2, :page => 30).map {|user| user.email } result.must_equal %W() end @@ -62,7 +62,7 @@ time_increment=1000 users = [test_user_dates(email="user1@example.com", created_at=test_date), test_user_dates(email="user2@example.com", created_at=test_date-time_increment), test_user_dates(email="user3@example.com", created_at=test_date-2*time_increment), test_user_dates(email="user4@example.com", created_at=test_date-3*time_increment)] - client.expects(:get).with("/users", {:type=>'users', :per_page => 4, :page => 5, :order => "asc", :sort => "created_at"}).returns(users_pagination(true, per_page=4, page=5, total_pages=6, total_count=30, user_list=users)) + client.expects(:get).with("/users", {:type=>'users', :per_page => 4, :page => 5, :order => "asc", :sort => "created_at"}).returns(users_pagination(include_next_link: true, per_page: 4, page: 5, total_pages: 6, total_count: 30, user_list: users)) result = client.users.find_all(:type=>'users', :per_page => 4, :page => 5, :order => "asc", :sort => "created_at").map(&:email) result.must_equal %W(user1@example.com user2@example.com user3@example.com user4@example.com) end @@ -72,7 +72,7 @@ time_increment=1000 users = [test_user_dates(email="user4@example.com", created_at=3*test_date), test_user_dates(email="user3@example.com", created_at=test_date-2*time_increment), test_user_dates(email="user2@example.com", created_at=test_date-time_increment), test_user_dates(email="user1@example.com", created_at=test_date)] - client.expects(:get).with("/users", {:type=>'users', :per_page => 4, :page => 5, :order => "desc", :sort => "created_at"}).returns(users_pagination(true, per_page=4, page=5, total_pages=6, total_count=30, user_list=users)) + client.expects(:get).with("/users", {:type=>'users', :per_page => 4, :page => 5, :order => "desc", :sort => "created_at"}).returns(users_pagination(include_next_link: true, per_page: 4, page: 5, total_pages: 6, total_count: 30, user_list: users)) result = client.users.find_all(:type=>'users', :per_page => 4, :page => 5, :order => "desc", :sort => "created_at").map {|user| user.email } result.must_equal %W(user4@example.com user3@example.com user2@example.com user1@example.com) end From 50133ef90f692921d00e33902280f1c11757b45d Mon Sep 17 00:00:00 2001 From: Masataka Pocke Kuwabara Date: Tue, 5 Dec 2017 10:35:51 +0900 Subject: [PATCH 119/245] Use `assert_nil` instead of `must_equal nil` Because it is deprecated. ``` $ bundle exec rake ... DEPRECATED: Use assert_nil if expecting nil from /home/pocke/ghq/github.com/intercom/intercom-ruby/spec/unit/intercom/user_spec.rb:242. This will fail in Minitest 6. ... ``` --- spec/unit/intercom/user_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/intercom/user_spec.rb b/spec/unit/intercom/user_spec.rb index e536d3a2..d1249cf6 100644 --- a/spec/unit/intercom/user_spec.rb +++ b/spec/unit/intercom/user_spec.rb @@ -239,7 +239,7 @@ it "allows setting dates to nil without converting them to 0" do client.expects(:post).with("/users", {"email" => "jo@example.com", 'custom_attributes' => {}, "remote_created_at" => nil}).returns({"email" => "jo@example.com"}) user = client.users.create("email" => "jo@example.com", "remote_created_at" => nil) - user.remote_created_at.must_equal nil + assert_nil user.remote_created_at end it "sets/gets rw keys" do From 07fefab3efe51bf86c5deeeddb6fd4257599851c Mon Sep 17 00:00:00 2001 From: Masataka Pocke Kuwabara Date: Wed, 6 Dec 2017 11:24:38 +0900 Subject: [PATCH 120/245] Remove gem dependency for Ruby 1.8 Because this gem no longer supports Ruby 1.8. https://github.com/intercom/intercom-ruby/blob/0fda77324201e5090fcbfbccb0a2744d96c73397/intercom.gemspec#L29 --- Gemfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Gemfile b/Gemfile index d729c317..0990e61b 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,4 @@ group :development, :test do gem 'json-jruby' gem 'jruby-openssl' end - platforms :ruby_18 do - gem 'json_pure' - end end From 8fe4e7e65a36d1931d225fd87e7c0befbbc7f974 Mon Sep 17 00:00:00 2001 From: Timothy Lim Date: Thu, 1 Feb 2018 11:14:01 +0800 Subject: [PATCH 121/245] Update link to access tokens --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b7e595a..80799434 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Using bundler: ### Configure your client -> If you already have a personal access token you can find it [here](https://app.intercom.io/a/apps/_/settings/personal-access-token). If you want to create or learn more about personal access tokens then you can find more info [here](https://developers.intercom.io/docs/personal-access-tokens). +> If you already have a personal access token you can find it [here](https://app.intercom.io/developers/_/access-token). If you want to create or learn more about personal access tokens then you can find more info [here](https://developers.intercom.io/docs/personal-access-tokens). ```ruby # With an OAuth or Personal Access token: From dd1ea5719f394378efa75084e6d62eb5d7d60ef5 Mon Sep 17 00:00:00 2001 From: Andrew Kim Date: Thu, 22 Mar 2018 18:28:54 -0700 Subject: [PATCH 122/245] handle negative cases fix test and past dates --- lib/intercom/request.rb | 5 +++-- spec/unit/intercom/request_spec.rb | 11 +++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 6bc5617b..d3cc2c61 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -74,7 +74,8 @@ def execute(target_base_url=nil, username:, secret: nil) parsed_body rescue Intercom::RateLimitExceeded => e if @handle_rate_limit - sleep (@rate_limit_details[:reset_at] - Time.now.utc).ceil + seconds_to_retry = (@rate_limit_details[:reset_at] - Time.now.utc).ceil + sleep seconds_to_retry unless seconds_to_retry < 0 retry unless (retries -=1).zero? else raise e @@ -109,7 +110,7 @@ def set_rate_limit_details(response) rate_limit_details = {} rate_limit_details[:limit] = response['X-RateLimit-Limit'].to_i if response['X-RateLimit-Limit'] rate_limit_details[:remaining] = response['X-RateLimit-Remaining'].to_i if response['X-RateLimit-Remaining'] - rate_limit_details[:reset_at] = Time.at(response['X-RateLimit-Reset'].to_i) if response['X-RateLimit-Reset'] + rate_limit_details[:reset_at] = Time.parse(response['X-RateLimit-Reset']) if response['X-RateLimit-Reset'] @rate_limit_details = rate_limit_details end diff --git a/spec/unit/intercom/request_spec.rb b/spec/unit/intercom/request_spec.rb index 68abfeb3..6e2cdd89 100644 --- a/spec/unit/intercom/request_spec.rb +++ b/spec/unit/intercom/request_spec.rb @@ -61,6 +61,17 @@ req.execute(target_base_url=uri, username: "ted", secret: "") end + it 'should not sleep if rate limit reset time has passed' do + # Use webmock to mock the HTTP request + stub_request(:any, uri).\ + to_return(status: [429, "Too Many Requests"], headers: { 'X-RateLimit-Reset' => Time.parse("February 25 2010").utc }).\ + then.to_return(status: [200, "OK"]) + req = Intercom::Request.get(uri, "") + req.handle_rate_limit=true + req.expects(:sleep).never.with(any_parameters) + req.execute(target_base_url=uri, username: "ted", secret: "") + end + end From a4ac2f6d702fee349622ad5667c6cddeb5fb9d12 Mon Sep 17 00:00:00 2001 From: Angelo Skibola Date: Mon, 9 Apr 2018 16:37:48 +0100 Subject: [PATCH 123/245] Migrate to Circle2.0 --- .circleci/config.yml | 12 ++++++++++++ circle.yml | 6 ------ 2 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 circle.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..76625cbd --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,12 @@ +version: 2 +jobs: + build: + docker: + - image: circleci/ruby:2.3.7 + + working_directory: ~/intercom-ruby + + steps: + - checkout + - run: bundle install + - run: bundle exec rake diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 2f2640a9..00000000 --- a/circle.yml +++ /dev/null @@ -1,6 +0,0 @@ -machine: - ruby: - version: 2.1.0 -test: - post: - - bundle exec rake From de8bcd8eedde888823721adf8c5c0216adcdf8b8 Mon Sep 17 00:00:00 2001 From: Jonny Date: Wed, 9 May 2018 11:20:56 +0100 Subject: [PATCH 124/245] Adding error to catch suspended apps --- lib/intercom/errors.rb | 9 +++++++++ lib/intercom/request.rb | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index 6d8c2de7..69f7b044 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -27,6 +27,15 @@ def to_hash # Check that you have set Intercom.token correctly. class AuthenticationError < IntercomError; end + # Raised when the token provided is linked to a deleted application. + class AppSuspendedError < AuthenticationError; end + + # Raised when the token provided has been revoked. + class TokenRevokedError < AuthenticationError; end + + # Raised when the token provided can't be decoded, and is most likely invalid. + class TokenUnauthorizedError < AuthenticationError; end + # Raised when something goes wrong on within the Intercom API service. class ServerError < IntercomError; end diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index d3cc2c61..f061ee68 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -152,6 +152,12 @@ def raise_application_errors_on_failure(error_list_details, http_code) case error_code when 'unauthorized', 'forbidden', 'token_not_found' raise Intercom::AuthenticationError.new(error_details['message'], error_context) + when 'token_suspended' + raise Intercom::AppSuspendedError.new(error_details['message'], error_context) + when 'token_revoked' + raise Intercom::TokenRevokedError.new(error_details['message'], error_context) + when 'token_unauthorized' + raise Intercom::TokenUnauthorizedError.new(error_details['message'], error_context) when "bad_request", "missing_parameter", 'parameter_invalid', 'parameter_not_found' raise Intercom::BadRequestError.new(error_details['message'], error_context) when "not_restorable" From 5e6e69d2b157c3375822e25af85ffbe1e926328c Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Thu, 10 May 2018 10:18:55 +0100 Subject: [PATCH 125/245] version bump (#397) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 80799434..47d6eead 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.5.23' + gem 'intercom', '~> 3.5.24' ## Basic Usage From 1592139afe9aabce3f210e5aa68a40115c5bd2dd Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Thu, 10 May 2018 09:28:36 +0000 Subject: [PATCH 126/245] Bump to 3.5.24 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 1fe3d411..f80ac3fb 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.23" + VERSION = "3.5.24" end From cb226975a1a8c8ab14b0b7b716ac6d92597e6de7 Mon Sep 17 00:00:00 2001 From: Kirby Cool Date: Thu, 10 May 2018 15:51:25 -0700 Subject: [PATCH 127/245] Fix X-RateLimit-Reset parsing. --- lib/intercom/request.rb | 2 +- spec/unit/intercom/request_spec.rb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index f061ee68..86e853e4 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -110,7 +110,7 @@ def set_rate_limit_details(response) rate_limit_details = {} rate_limit_details[:limit] = response['X-RateLimit-Limit'].to_i if response['X-RateLimit-Limit'] rate_limit_details[:remaining] = response['X-RateLimit-Remaining'].to_i if response['X-RateLimit-Remaining'] - rate_limit_details[:reset_at] = Time.parse(response['X-RateLimit-Reset']) if response['X-RateLimit-Reset'] + rate_limit_details[:reset_at] = Time.at(response['X-RateLimit-Reset'].to_i) if response['X-RateLimit-Reset'] @rate_limit_details = rate_limit_details end diff --git a/spec/unit/intercom/request_spec.rb b/spec/unit/intercom/request_spec.rb index 6e2cdd89..28e45060 100644 --- a/spec/unit/intercom/request_spec.rb +++ b/spec/unit/intercom/request_spec.rb @@ -33,7 +33,7 @@ it 'should call sleep for rate limit error three times' do # Use webmock to mock the HTTP request stub_request(:any, uri).\ - to_return(status: [429, "Too Many Requests"], headers: { 'X-RateLimit-Reset' => Time.now.utc + 10 }) + to_return(status: [429, "Too Many Requests"], headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }) req = Intercom::Request.get(uri, "") req.handle_rate_limit=true req.expects(:sleep).times(3).with(any_parameters) @@ -53,7 +53,7 @@ it 'should call sleep for rate limit error just once' do # Use webmock to mock the HTTP request stub_request(:any, uri).\ - to_return(status: [429, "Too Many Requests"], headers: { 'X-RateLimit-Reset' => Time.now.utc + 10 }).\ + to_return(status: [429, "Too Many Requests"], headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }).\ then.to_return(status: [200, "OK"]) req = Intercom::Request.get(uri, "") req.handle_rate_limit=true @@ -64,7 +64,7 @@ it 'should not sleep if rate limit reset time has passed' do # Use webmock to mock the HTTP request stub_request(:any, uri).\ - to_return(status: [429, "Too Many Requests"], headers: { 'X-RateLimit-Reset' => Time.parse("February 25 2010").utc }).\ + to_return(status: [429, "Too Many Requests"], headers: { 'X-RateLimit-Reset' => Time.parse("February 25 2010").utc.to_i.to_s }).\ then.to_return(status: [200, "OK"]) req = Intercom::Request.get(uri, "") req.handle_rate_limit=true @@ -80,7 +80,7 @@ it 'should raise ResourceNotUniqueError error on resource_conflict code' do # Use webmock to mock the HTTP request stub_request(:put, uri).\ - to_return(status: [409, "Resource Already Exists"], headers: { 'X-RateLimit-Reset' => Time.now.utc + 10 }, body: {type: "error.list", errors: [ code: "resource_conflict" ]}.to_json) + to_return(status: [409, "Resource Already Exists"], headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }, body: {type: "error.list", errors: [ code: "resource_conflict" ]}.to_json) req = Intercom::Request.put(uri, "") expect { req.execute(target_base_url=uri, username: "ted", secret: "") }.must_raise(Intercom::ResourceNotUniqueError) end From 7f22dae4853750c2016ad76e1c2f3cec5dc1ca14 Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Fri, 11 May 2018 09:43:10 +0100 Subject: [PATCH 128/245] bump version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 47d6eead..45e97a84 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.5.24' + gem 'intercom', '~> 3.5.25' ## Basic Usage From f3448ffe01e934e771ed7af818034330e837e48b Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Fri, 11 May 2018 09:55:18 +0100 Subject: [PATCH 129/245] Bump to 3.5.25 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index f80ac3fb..81321728 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.24" + VERSION = "3.5.25" end From 6a27fa3870bebcce847a973acb0050d0fbe4c410 Mon Sep 17 00:00:00 2001 From: theandrewykim Date: Thu, 21 Jun 2018 16:22:25 -0700 Subject: [PATCH 130/245] add exception after three failed retries make changes' --- README.md | 2 +- lib/intercom/request.rb | 8 ++++++-- spec/unit/intercom/request_spec.rb | 17 +++++++++-------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 45e97a84..1b25811c 100644 --- a/README.md +++ b/README.md @@ -441,7 +441,7 @@ intercom.rate_limit_details ``` You can handle the rate limits yourself but a simple option is to use the handle_rate_limit flag. -This will automatically catch the 429 rate limit exceeded error and wait until the reset time to retry. +This will automatically catch the 429 rate limit exceeded error and wait until the reset time to retry. After three retries a rate limit exception will be raised. Encountering this error frequently may require a revisiting of your usage of the API. ``` intercom = Intercom::Client.new(token: ENV['AT'], handle_rate_limit: true) diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 86e853e4..c939bb98 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -75,8 +75,12 @@ def execute(target_base_url=nil, username:, secret: nil) rescue Intercom::RateLimitExceeded => e if @handle_rate_limit seconds_to_retry = (@rate_limit_details[:reset_at] - Time.now.utc).ceil - sleep seconds_to_retry unless seconds_to_retry < 0 - retry unless (retries -=1).zero? + if (retries-=1) < 0 + raise Intercom::RateLimitExceeded.new('armbar from the back') + else + sleep seconds_to_retry unless seconds_to_retry < 0 + retry + end else raise e end diff --git a/spec/unit/intercom/request_spec.rb b/spec/unit/intercom/request_spec.rb index 28e45060..e1922fdb 100644 --- a/spec/unit/intercom/request_spec.rb +++ b/spec/unit/intercom/request_spec.rb @@ -30,14 +30,15 @@ client.handle_rate_limit.must_equal(true) end - it 'should call sleep for rate limit error three times' do - # Use webmock to mock the HTTP request - stub_request(:any, uri).\ - to_return(status: [429, "Too Many Requests"], headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }) - req = Intercom::Request.get(uri, "") - req.handle_rate_limit=true - req.expects(:sleep).times(3).with(any_parameters) - req.execute(target_base_url=uri, username: "ted", secret: "") + it 'should call sleep for rate limit error three times and raise a rate limit error otherwise' do + expect { + stub_request(:any, uri).\ + to_return(status: [429, "Too Many Requests"], headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }) + req = Intercom::Request.get(uri, "") + req.handle_rate_limit=true + req.expects(:sleep).times(3).with(any_parameters) + req.execute(target_base_url=uri, username: "ted", secret: "") + }.must_raise(Intercom::RateLimitExceeded) end it 'should not call sleep for rate limit error' do From d80935ab035fe8f3b41975e349cc736c29a3530f Mon Sep 17 00:00:00 2001 From: theandrewykim Date: Sun, 24 Jun 2018 17:31:08 -0700 Subject: [PATCH 131/245] add ability to hard delete --- lib/intercom/api_operations/request_hard_delete.rb | 12 ++++++++++++ lib/intercom/service/contact.rb | 2 ++ lib/intercom/service/user.rb | 2 ++ spec/unit/intercom/contact_spec.rb | 6 ++++++ spec/unit/intercom/user_spec.rb | 6 ++++++ 5 files changed, 28 insertions(+) create mode 100644 lib/intercom/api_operations/request_hard_delete.rb diff --git a/lib/intercom/api_operations/request_hard_delete.rb b/lib/intercom/api_operations/request_hard_delete.rb new file mode 100644 index 00000000..77ae09bd --- /dev/null +++ b/lib/intercom/api_operations/request_hard_delete.rb @@ -0,0 +1,12 @@ +require 'intercom/utils' + +module Intercom + module ApiOperations + module RequestHardDelete + def request_hard_delete(object) + @client.post("/user_delete_requests", {intercom_user_id: object.id}) + object + end + end + end +end diff --git a/lib/intercom/service/contact.rb b/lib/intercom/service/contact.rb index 508f4fac..871a6da2 100644 --- a/lib/intercom/service/contact.rb +++ b/lib/intercom/service/contact.rb @@ -7,6 +7,7 @@ require 'intercom/api_operations/scroll' require 'intercom/api_operations/convert' require 'intercom/api_operations/delete' +require 'intercom/api_operations/request_hard_delete' module Intercom module Service @@ -19,6 +20,7 @@ class Contact < BaseService include ApiOperations::Scroll include ApiOperations::Convert include ApiOperations::Delete + include ApiOperations::RequestHardDelete def collection_class Intercom::Contact diff --git a/lib/intercom/service/user.rb b/lib/intercom/service/user.rb index bb040d2b..e0ff2aee 100644 --- a/lib/intercom/service/user.rb +++ b/lib/intercom/service/user.rb @@ -7,6 +7,7 @@ require 'intercom/api_operations/save' require 'intercom/api_operations/delete' require 'intercom/api_operations/bulk/submit' +require 'intercom/api_operations/request_hard_delete' require 'intercom/extended_api_operations/tags' require 'intercom/extended_api_operations/segments' @@ -20,6 +21,7 @@ class User < BaseService include ApiOperations::FindAll include ApiOperations::Save include ApiOperations::Delete + include ApiOperations::RequestHardDelete include ApiOperations::Bulk::Submit include ExtendedApiOperations::Tags include ExtendedApiOperations::Segments diff --git a/spec/unit/intercom/contact_spec.rb b/spec/unit/intercom/contact_spec.rb index dbabf2ab..c08615e6 100644 --- a/spec/unit/intercom/contact_spec.rb +++ b/spec/unit/intercom/contact_spec.rb @@ -50,4 +50,10 @@ client.contacts.delete(contact) end + it "sends a request for a hard deletion" do + contact = Intercom::Contact.new("id" => "1") + client.expects(:post).with("/user_delete_requests", {intercom_user_id: "1"}).returns({id: contact.id}) + client.contacts.request_hard_delete(contact) + end + end diff --git a/spec/unit/intercom/user_spec.rb b/spec/unit/intercom/user_spec.rb index d1249cf6..c3621693 100644 --- a/spec/unit/intercom/user_spec.rb +++ b/spec/unit/intercom/user_spec.rb @@ -224,6 +224,12 @@ client.users.delete(user) end + it "sends a request for a hard deletion" do + user = Intercom::User.new("id" => "1") + client.expects(:post).with("/user_delete_requests", {intercom_user_id: "1"}).returns({id: user.id}) + client.users.request_hard_delete(user) + end + it "can use client.users.create for convenience" do client.expects(:post).with("/users", {'custom_attributes' => {}, "email" => "jo@example.com", "user_id" => "i-1224242"}).returns({"email" => "jo@example.com", "user_id" => "i-1224242"}) user = client.users.create("email" => "jo@example.com", :user_id => "i-1224242") From 5e5cc534f925db60d9dd8f58e5bced64688445ae Mon Sep 17 00:00:00 2001 From: theandrewykim Date: Tue, 31 Jul 2018 15:21:00 -0700 Subject: [PATCH 132/245] add descriptive error --- lib/intercom/request.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index c939bb98..0c587ec3 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -75,8 +75,8 @@ def execute(target_base_url=nil, username:, secret: nil) rescue Intercom::RateLimitExceeded => e if @handle_rate_limit seconds_to_retry = (@rate_limit_details[:reset_at] - Time.now.utc).ceil - if (retries-=1) < 0 - raise Intercom::RateLimitExceeded.new('armbar from the back') + if (retries -= 1) < 0 + raise Intercom::RateLimitExceeded.new('Rate limit retries exceeded. Please examine current API Usage.') else sleep seconds_to_retry unless seconds_to_retry < 0 retry From cb848b41cd66836dbec869060b9dc198dff0adbd Mon Sep 17 00:00:00 2001 From: Jonny Date: Tue, 7 Aug 2018 15:39:23 +0100 Subject: [PATCH 133/245] Adding PR template --- .github/PULL_REQUEST_TEMPLATE.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..7c94e728 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,5 @@ +#### Why? +Why are you making this change? + +#### How? +Technical details on your change From cb16d94ca9afe3dbc599f73a2deb427f5ee17508 Mon Sep 17 00:00:00 2001 From: SeanHealy33 Date: Fri, 10 Aug 2018 11:23:43 +0100 Subject: [PATCH 134/245] Remove Travis CI --- .travis.yml | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7a3c3d1d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: ruby -sudo: false -before_install: - - gem install bundler - - gem update --system - - gem --version -rvm: - - 2.1.0 - - 2.2.0 - - 2.3.3 From 32124066dbc8dceffb75822fe50d6e53c5af22ab Mon Sep 17 00:00:00 2001 From: Yuki Nishijima Date: Tue, 14 Aug 2018 17:05:27 -0400 Subject: [PATCH 135/245] Remove json dependency > All modern Rubies ship JSON as part of stdlib. Using the gem actually hurts multi-platform support due to build difficulties on Windows. From https://github.com/rails/rails/pull/23453 --- Gemfile | 1 - intercom.gemspec | 1 - 2 files changed, 2 deletions(-) diff --git a/Gemfile b/Gemfile index 0990e61b..4516ffc0 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,6 @@ gemspec group :development, :test do platforms :jruby do - gem 'json-jruby' gem 'jruby-openssl' end end diff --git a/intercom.gemspec b/intercom.gemspec index 07ee4dc9..ba48cc01 100644 --- a/intercom.gemspec +++ b/intercom.gemspec @@ -25,7 +25,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency "fakeweb", ["~> 1.3"] spec.add_development_dependency "pry" - spec.add_dependency 'json', '>= 1.8' spec.required_ruby_version = '>= 2.1.0' spec.add_development_dependency 'gem-release' end From e804603a33b4d03e1f49c15b8c83dbdc976e32a9 Mon Sep 17 00:00:00 2001 From: Andrew Havens Date: Tue, 21 Aug 2018 16:01:29 -0700 Subject: [PATCH 136/245] Update documentation for upgrading lead to be a user --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 45e97a84..c392e4bb 100644 --- a/README.md +++ b/README.md @@ -370,7 +370,7 @@ intercom.contacts.save(contact) contacts = intercom.contacts.find_all(email: "some_contact@example.com") # Convert a contact into a user -intercom.contacts.convert(contact, user) +intercom.contacts.convert(contact, Intercom::User.new(email: email)) # Delete a contact intercom.contacts.delete(contact) From 67c4fdd76c0998d3cc929edc9d776537f8dfde6f Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Tue, 28 Aug 2018 13:17:01 +0100 Subject: [PATCH 137/245] Updating readme to make it clearer how to convert a lead into a user (#421) --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index c392e4bb..f492bd91 100644 --- a/README.md +++ b/README.md @@ -369,8 +369,31 @@ intercom.contacts.save(contact) # Find contacts by email contacts = intercom.contacts.find_all(email: "some_contact@example.com") +# Using find to search for contacts by email +contact_list = intercom.contacts.find(email: "some_contact@example.com") +# This returns a Contact object with type contact.list +# Note: Multiple contacts can be returned in this list if there are multiple matching contacts found +# #, +# @contacts= +# [{"type"=>"contact", +# "id"=>"5b7fd9b683681ac52274b9c7", +# "user_id"=>"05bc4d17-72cc-433e-88ae-0bf88db5d0e6", +# "anonymous"=>true, +# "email"=>"some_contact@example.com", +# ...}], +# @custom_attributes={}, +# @limited=false, +# @pages=#, @next=nil, @page=1, @per_page=50, @total_pages=1, @type="pages">, +# @total_count=1, +# @type="contact.list"> +# Access the contact's data +contact_list.contacts.first + # Convert a contact into a user +contact = intercom.contacts.find(id: "536e564f316c83104c000020") intercom.contacts.convert(contact, Intercom::User.new(email: email)) +# Using find with email will not work here. See https://github.com/intercom/intercom-ruby/issues/419 for more information # Delete a contact intercom.contacts.delete(contact) From 8821f3429fc88ca59584bddb0d38d72168f8686b Mon Sep 17 00:00:00 2001 From: Jonny Date: Mon, 3 Sep 2018 15:26:54 +0100 Subject: [PATCH 138/245] Adding clearer instructions for find_all with contacts --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index f492bd91..d3a7d298 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,11 @@ intercom.conversations.find_all(email: 'joe@example.com', type: 'user').each {|c intercom.conversations.find_all(email: 'joe@example.com', type: 'user', unread: false).each {|convo| ... } # Iterate over all unread conversations with a user based on the users email intercom.conversations.find_all(email: 'joe@example.com', type: 'user', unread: true).each {|convo| ... } +# Iterate over all conversations for a user with their Intercom user ID +intercom.conversations.find_all(intercom_user_id: '536e564f316c83104c000020', type: 'user').each {|convo| ... } +# Iterate over all conversations for a lead +# NOTE: to iterate over a lead's conversations you MUST use their Intercom User ID and type User +intercom.conversations.find_all(intercom_user_id: lead.id, type: 'user').each {|convo| ... } # FINDING A SINGLE CONVERSATION conversation = intercom.conversations.find(id: '1') From 3e38bb40c0d64b36ed5736a93d61f89c96bcbfe6 Mon Sep 17 00:00:00 2001 From: Damir Date: Mon, 10 Sep 2018 13:17:51 +0200 Subject: [PATCH 139/245] Make network timeouts configurable (#412) --- lib/intercom/client.rb | 23 +++++++++++++++++++++-- lib/intercom/request.rb | 10 +++++----- spec/unit/intercom/client_spec.rb | 21 +++++++++++++++++++++ 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/lib/intercom/client.rb b/lib/intercom/client.rb index 3128ea54..982d6475 100644 --- a/lib/intercom/client.rb +++ b/lib/intercom/client.rb @@ -2,7 +2,7 @@ module Intercom class MisconfiguredClientError < StandardError; end class Client include Options - attr_reader :base_url, :rate_limit_details, :username_part, :password_part, :handle_rate_limit + attr_reader :base_url, :rate_limit_details, :username_part, :password_part, :handle_rate_limit, :timeouts class << self def set_base_url(base_url) @@ -12,6 +12,17 @@ def set_base_url(base_url) Proc.new { |obj| set_base_url(old_url).call(o) } end end + + def set_timeouts(open_timeout: nil, read_timeout: nil) + return Proc.new do |o| + old_timeouts = o.timeouts + timeouts = {} + timeouts[:open_timeout] = open_timeout if open_timeout + timeouts[:read_timeout] = read_timeout if read_timeout + o.send(:timeouts=, timeouts) + Proc.new { |obj| set_timeouts(old_timeouts).call(o) } + end + end end def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil, base_url:'https://api.intercom.io', handle_rate_limit: false) @@ -27,6 +38,10 @@ def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil, base_url: @base_url = base_url @rate_limit_details = {} @handle_rate_limit = handle_rate_limit + @timeouts = { + open_timeout: 30, + read_timeout: 90 + } end def admins @@ -110,7 +125,7 @@ def validate_credentials! def execute_request(request) request.handle_rate_limit = handle_rate_limit - request.execute(@base_url, username: @username_part, secret: @password_part) + request.execute(@base_url, username: @username_part, secret: @password_part, **timeouts) ensure @rate_limit_details = request.rate_limit_details end @@ -118,5 +133,9 @@ def execute_request(request) def base_url=(new_url) @base_url = new_url end + + def timeouts=(timeouts) + @timeouts = @timeouts.merge(timeouts) + end end end diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 86e853e4..c3266113 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -46,25 +46,25 @@ def self.default_headers {'Accept-Encoding' => 'gzip, deflate', 'Accept' => 'application/vnd.intercom.3+json', 'User-Agent' => "Intercom-Ruby/#{Intercom::VERSION}"} end - def client(uri) + def client(uri, read_timeout:, open_timeout:) net = Net::HTTP.new(uri.host, uri.port) if uri.is_a?(URI::HTTPS) net.use_ssl = true net.verify_mode = OpenSSL::SSL::VERIFY_PEER net.ca_file = File.join(File.dirname(__FILE__), '../data/cacert.pem') end - net.read_timeout = 90 - net.open_timeout = 30 + net.read_timeout = read_timeout + net.open_timeout = open_timeout net end - def execute(target_base_url=nil, username:, secret: nil) + def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_timeout: 30) retries = 3 base_uri = URI.parse(target_base_url) set_common_headers(net_http_method, base_uri) set_basic_auth(net_http_method, username, secret) begin - client(base_uri).start do |http| + client(base_uri, read_timeout: read_timeout, open_timeout: open_timeout).start do |http| begin response = http.request(net_http_method) set_rate_limit_details(response) diff --git a/spec/unit/intercom/client_spec.rb b/spec/unit/intercom/client_spec.rb index 076df659..a62685d2 100644 --- a/spec/unit/intercom/client_spec.rb +++ b/spec/unit/intercom/client_spec.rb @@ -17,6 +17,27 @@ module Intercom client.base_url.must_equal('https://api.intercom.io') end + it 'should be able to change the timeouts' do + prev = client.options(Intercom::Client.set_timeouts(open_timeout: 10, read_timeout: 15)) + client.timeouts.must_equal(open_timeout: 10, read_timeout: 15) + client.options(prev) + client.timeouts.must_equal(open_timeout: 30, read_timeout: 90) + end + + it 'should be able to change the open timeout individually' do + prev = client.options(Intercom::Client.set_timeouts(open_timeout: 50)) + client.timeouts.must_equal(open_timeout: 50, read_timeout: 90) + client.options(prev) + client.timeouts.must_equal(open_timeout: 30, read_timeout: 90) + end + + it 'should be able to change the read timeout individually' do + prev = client.options(Intercom::Client.set_timeouts(read_timeout: 50)) + client.timeouts.must_equal(open_timeout: 30, read_timeout: 50) + client.options(prev) + client.timeouts.must_equal(open_timeout: 30, read_timeout: 90) + end + it 'should raise on nil credentials' do proc { Client.new(app_id: nil, api_key: nil) }.must_raise MisconfiguredClientError end From 5519a12ffa7462ad22fe1ad1c0f92e0c45fb72db Mon Sep 17 00:00:00 2001 From: Nick Cox Date: Fri, 21 Sep 2018 05:51:19 -0700 Subject: [PATCH 140/245] Change name of hash key in tagging docs (#420) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d3a7d298..08cb5798 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ intercom.tags.untag(name: 'blue', users: [{user_id: "42ea2f1b93891f6a99000427"} intercom.tags.all.each {|tag| "#{tag.id} - #{tag.name}" } intercom.tags.all.map {|tag| tag.name } # Tag companies -tag = intercom.tags.tag(name: 'blue', companies: [{id: "42ea2f1b93891f6a99000427"}]) +tag = intercom.tags.tag(name: 'blue', companies: [{company_id: "42ea2f1b93891f6a99000427"}]) ``` #### Segments From 93a546df513ddb4edce66e035554806392b04ed7 Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Tue, 2 Oct 2018 08:24:56 +0100 Subject: [PATCH 141/245] Allowing company users to be listed by company_id (#428) * Allowing company users to be listed by company_id * Removing require --- README.md | 6 ++-- lib/intercom/extended_api_operations/users.rb | 16 --------- lib/intercom/service/company.rb | 14 ++++++-- spec/unit/intercom/company_spec.rb | 36 ++++++++++--------- 4 files changed, 36 insertions(+), 36 deletions(-) delete mode 100644 lib/intercom/extended_api_operations/users.rb diff --git a/README.md b/README.md index a8d01fcd..edd62a01 100644 --- a/README.md +++ b/README.md @@ -132,8 +132,10 @@ intercom.companies.save(company) # Iterate over all companies intercom.companies.all.each {|company| puts %Q(#{company.name} - #{company.custom_attributes["referral_source"]}) } intercom.companies.all.map {|company| company.name } -# Get a list of users in a company -intercom.companies.users(company.id) +# Get a list of users in a company by Intercom Company ID +intercom.companies.users_by_intercom_company_id(company.id) +# Get a list of users in a company by external company_id +intercom.companies.users_by_company_id(company.company_id) # Get a large list of companies using scroll intercom.companies.scroll.each { |comp| puts comp.name} # Please see users scroll for more details of how to use scroll diff --git a/lib/intercom/extended_api_operations/users.rb b/lib/intercom/extended_api_operations/users.rb deleted file mode 100644 index 55756531..00000000 --- a/lib/intercom/extended_api_operations/users.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'intercom/client_collection_proxy' -require 'intercom/utils' - -module Intercom - module ExtendedApiOperations - module Users - def users(id) - collection_name = Utils.resource_class_to_collection_name(collection_class) - finder_details = {} - finder_details[:url] = "/#{collection_name}/#{id}/users" - finder_details[:params] = {} - ClientCollectionProxy.new("users", finder_details: finder_details, client: @client) - end - end - end -end diff --git a/lib/intercom/service/company.rb b/lib/intercom/service/company.rb index fd2e0b56..38367a0a 100644 --- a/lib/intercom/service/company.rb +++ b/lib/intercom/service/company.rb @@ -5,7 +5,6 @@ require 'intercom/api_operations/find_all' require 'intercom/api_operations/save' require 'intercom/api_operations/load' -require 'intercom/extended_api_operations/users' require 'intercom/extended_api_operations/tags' require 'intercom/extended_api_operations/segments' @@ -18,13 +17,24 @@ class Company < BaseService include ApiOperations::List include ApiOperations::Scroll include ApiOperations::Save - include ExtendedApiOperations::Users include ExtendedApiOperations::Tags include ExtendedApiOperations::Segments def collection_class Intercom::Company end + + def users_by_intercom_company_id(id) + get_users(url: "/companies/#{id}/users") + end + + def users_by_company_id(id) + get_users(url: "/companies", params: { company_id: id, type: "user" }) + end + + private def get_users(url:, params: {}) + ClientCollectionProxy.new("users", finder_details: { url: url, params: params }, client: @client) + end end end end diff --git a/spec/unit/intercom/company_spec.rb b/spec/unit/intercom/company_spec.rb index 38ba98ba..71fc41b2 100644 --- a/spec/unit/intercom/company_spec.rb +++ b/spec/unit/intercom/company_spec.rb @@ -1,36 +1,40 @@ -require 'spec_helper' +require "spec_helper" describe Intercom::Company do - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } + let (:client) { Intercom::Client.new(app_id: "app_id", api_key: "api_key") } - describe 'when no response raises error' do - it 'on find' do - client.expects(:get).with("/companies", {:company_id => '4'}).returns(nil) - proc {client.companies.find(:company_id => '4')}.must_raise Intercom::HttpError + describe "when no response raises error" do + it "on find" do + client.expects(:get).with("/companies", {:company_id => "4"}).returns(nil) + proc {client.companies.find(:company_id => "4")}.must_raise Intercom::HttpError end - it 'on find_all' do + it "on find_all" do client.expects(:get).with("/companies", {}).returns(nil) proc {client.companies.all.each {|company| }}.must_raise Intercom::HttpError end - it 'on load' do - client.expects(:get).with("/companies", {:company_id => '4'}).returns({'type' =>'user', 'id' =>'aaaaaaaaaaaaaaaaaaaaaaaa', 'company_id' => '4', 'name' => 'MyCo'}) - company = client.companies.find(:company_id => '4') - client.expects(:get).with('/companies/aaaaaaaaaaaaaaaaaaaaaaaa', {}).returns(nil) + it "on load" do + client.expects(:get).with("/companies", {:company_id => "4"}).returns({"type" =>"user", "id" =>"aaaaaaaaaaaaaaaaaaaaaaaa", "company_id" => "4", "name" => "MyCo"}) + company = client.companies.find(:company_id => "4") + client.expects(:get).with("/companies/aaaaaaaaaaaaaaaaaaaaaaaa", {}).returns(nil) proc {client.companies.load(company)}.must_raise Intercom::HttpError end end - it 'gets users in a company' do + it "gets users in a company by intercom ID" do client.expects(:get).with("/companies/abc123/users", {}).returns(page_of_users(false)) - client.companies.users('abc123').each do |u| - end + client.companies.users_by_intercom_company_id("abc123").each { |u| } + end + + it "gets users in a company by external company ID" do + client.expects(:get).with("/companies", { company_id: "abc123", type: "user" }).returns(page_of_users(false)) + client.companies.users_by_company_id("abc123").each { |u| } end - it 'finds a company' do + it "finds a company" do client.expects(:get).with("/companies/531ee472cce572a6ec000006", {}).returns(test_company) - company = client.companies.find(id: '531ee472cce572a6ec000006') + company = client.companies.find(id: "531ee472cce572a6ec000006") company.name.must_equal("Blue Sun") end end From 400c6fce7f308b041ee33635d3a33ae4007804e8 Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Tue, 2 Oct 2018 17:00:59 +0100 Subject: [PATCH 142/245] Updating version to 3.6.0 (#429) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index edd62a01..3c1a4bfa 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.5.25' + gem 'intercom', '~> 3.6.0' ## Basic Usage From 80450e86827c96d6fa152b18a8e681118ca5e2d3 Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Wed, 3 Oct 2018 09:18:11 +0100 Subject: [PATCH 143/245] Allow all conversations to be listed (#430) --- README.md | 3 ++ lib/intercom/service/conversation.rb | 1 + spec/spec_helper.rb | 50 +++++++++++++++++++++++++ spec/unit/intercom/conversation_spec.rb | 5 +++ 4 files changed, 59 insertions(+) diff --git a/README.md b/README.md index 3c1a4bfa..31838736 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,9 @@ intercom.notes.find_all(user_id: '123').each {|note| puts note.body} #### Conversations ```ruby +# Iterate over all conversations for your app +intercom.conversations.all.each { |convo| ... } + # FINDING CONVERSATIONS FOR AN ADMIN # Iterate over all conversations (open and closed) assigned to an admin intercom.conversations.find_all(type: 'admin', id: '7').each {|convo| ... } diff --git a/lib/intercom/service/conversation.rb b/lib/intercom/service/conversation.rb index 31e51486..ecab5228 100644 --- a/lib/intercom/service/conversation.rb +++ b/lib/intercom/service/conversation.rb @@ -9,6 +9,7 @@ module Intercom module Service class Conversation < BaseService include ApiOperations::FindAll + include ApiOperations::List include ApiOperations::Find include ApiOperations::Load include ApiOperations::Save diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4b7611a2..46baeb38 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -291,6 +291,56 @@ def test_conversation } end +def test_conversation_list + { + "type" => "conversation.list", + "pages" => { + "type" => "pages", + "page" => 1, + "per_page" => 20, + "total_pages" => 1 + }, + "conversations" => [ + { + "type" => "conversation", + "id" => "147", + "created_at" => 1400850973, + "updated_at" => 1400857494, + "conversation_message" => { + "type" => "conversation_message", + "subject" => "", + "body" => "

Hi Alice,

\n\n

We noticed you using our Product, do you have any questions?

\n

- Jane

", + "author" => { + "type" => "admin", + "id" => "25" + }, + "attachments" => [ + { + "name" => "signature", + "url" => "http =>//someurl.com/signature.jpg" + } + ] + }, + "user" => { + "type" => "user", + "id" => "536e564f316c83104c000020" + }, + "assignee" => { + "type" => "admin", + "id" => "25" + }, + "open" => true, + "read" => true, + "conversation_parts" => { + "type" => "conversation_part.list", + "conversation_parts" => [ + ] + } + } + ] + } +end + def segment { "type" => "segment", diff --git a/spec/unit/intercom/conversation_spec.rb b/spec/unit/intercom/conversation_spec.rb index c87c4304..24d5172b 100644 --- a/spec/unit/intercom/conversation_spec.rb +++ b/spec/unit/intercom/conversation_spec.rb @@ -8,6 +8,11 @@ client.conversations.find(:id => "147") end + it "gets all conversations" do + client.expects(:get).with("/conversations", {}).returns(test_conversation_list) + client.conversations.all.each { |c| } + end + it 'marks a conversation as read' do client.expects(:put).with('/conversations/147', { read: true }) client.conversations.mark_read('147') From d4dbf8dd02736606d4771989baea564245ce8859 Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Wed, 3 Oct 2018 09:26:20 +0100 Subject: [PATCH 144/245] Bump intercom to 3.6.0 (#431) --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 81321728..f2c2a515 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.5.25" + VERSION = "3.6.0" end From 8040bfd8f42a259de6be136d521ecbc374fba1e2 Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Wed, 3 Oct 2018 10:35:28 +0100 Subject: [PATCH 145/245] Adding new version 3.6.1 (#432) --- README.md | 2 +- changes.txt | 22 ++++++++++++++++++++++ lib/intercom/version.rb | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 31838736..b167292c 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.6.0' + gem 'intercom', '~> 3.6.1' ## Basic Usage diff --git a/changes.txt b/changes.txt index 7a6c9334..c8c36dbc 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,25 @@ +3.6.1 +#430 Allow all conversations to be listed +You can now iterate over all conversations for your app via: +intercom.conversations.all.each { |convo| ... } + +3.6.0 +BREAKING CHANGE companies +We updated companies to be able to list users via company_id as well as id (#428 ) +Note that this is a breaking change as we had to remove the old way of listing users via company. + +Previously it was: +intercom.companies.users(company.id) + +Now you get a list of users in a company by Intercom Company ID +intercom.companies.users_by_intercom_company_id(company.id) + +Now you get a list of users in a company by external company_id +intercom.companies.users_by_company_id(company.company_id) + +Rate limit handling +We also improved the way we handle rate limits in PR #409 which was related to issue #405 + 3.5.23 - New type of error (ResourceNotUniqueError). Thrown when trying to create a resource that already exists in Intercom diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index f2c2a515..f4b60e39 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.6.0" + VERSION = "3.6.1" end From 92ab978a85fde062adafaea90fb44645f4a5e434 Mon Sep 17 00:00:00 2001 From: Andrew Kim Date: Wed, 3 Oct 2018 06:19:26 -0700 Subject: [PATCH 146/245] Hahmed snooze conversation (#384) * Snooze conversation * make recommended changes make suggested changes --- README.md | 3 +++ lib/intercom/service/conversation.rb | 5 +++++ spec/spec_helper.rb | 5 +++++ spec/unit/intercom/conversation_spec.rb | 5 +++++ 4 files changed, 18 insertions(+) diff --git a/README.md b/README.md index b167292c..37350c76 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,9 @@ intercom.conversations.close(id: conversation.id, admin_id: '123') # Assign intercom.conversations.assign(id: conversation.id, admin_id: '123', assignee_id: '124') +# Snooze +intercom.conversations.snooze(id: conversation.id, admin_id: '123', snoozed_until: 9999999999) + # Reply and Open intercom.conversations.reply(id: conversation.id, type: 'admin', admin_id: '123', message_type: 'open', body: 'bar') diff --git a/lib/intercom/service/conversation.rb b/lib/intercom/service/conversation.rb index ecab5228..40049576 100644 --- a/lib/intercom/service/conversation.rb +++ b/lib/intercom/service/conversation.rb @@ -37,6 +37,11 @@ def close(reply_data) reply reply_data.merge(message_type: 'close', type: 'admin') end + def snooze(reply_data) + snoozed_until = reply_data.fetch(:snoozed_until) { fail 'snoozed_until field is required' } + reply reply_data.merge(message_type: 'snoozed', type: 'admin') + end + def assign(reply_data) assignee_id = reply_data.fetch(:assignee_id) { fail 'assignee_id is required' } reply reply_data.merge(message_type: 'assignment', assignee_id: assignee_id, type: 'admin') diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 46baeb38..e7655e43 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,7 @@ require 'minitest/autorun' require 'mocha/setup' require 'webmock' +require 'time' include WebMock::API def test_user(email="bob@example.com") @@ -625,6 +626,10 @@ def test_event_list } end +def tomorrow + (DateTime.now.to_time + 1).to_i +end + def page_of_events(include_next_link=false) { "type" => "event.list", diff --git a/spec/unit/intercom/conversation_spec.rb b/spec/unit/intercom/conversation_spec.rb index 24d5172b..037ac2bc 100644 --- a/spec/unit/intercom/conversation_spec.rb +++ b/spec/unit/intercom/conversation_spec.rb @@ -43,6 +43,11 @@ client.conversations.assign(id: '147', admin_id: '123', assignee_id: '124') end + it 'snoozes a conversation' do + client.expects(:post).with('/conversations/147/reply', { type: 'admin', message_type: 'snoozed', conversation_id: '147', admin_id: '123', snoozed_until: tomorrow}).returns(test_conversation) + client.conversations.snooze(id: '147', admin_id: '123', snoozed_until: tomorrow) + end + # it "creates a subscription" do # client.expects(:post).with("/subscriptions", {'url' => "http://example.com", 'topics' => ["user.created"]}).returns(test_subscription) # subscription = client.subscriptions.create(:url => "http://example.com", :topics => ["user.created"]) From f2440c1a11f228fd0c3612772f6d600cfb90dda8 Mon Sep 17 00:00:00 2001 From: Gabriel Tanase Date: Fri, 5 Oct 2018 10:17:30 +0100 Subject: [PATCH 147/245] Update README.md (#434) * Update README.md Update README.md with extra info for assigning conversations to teams https://github.com/intercom/intercom-ruby/issues/433 * Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 37350c76..24d5f623 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,8 @@ intercom.conversations.open(id: conversation.id, admin_id: '123') intercom.conversations.close(id: conversation.id, admin_id: '123') # Assign +# Note: Conversations can be assigned to teams. However, the entity that performs the operation of assigning the conversation has to be an existing teammate. +# You can use `intercom.admins.all.each {|a| puts a.inspect if a.type == 'admin' }` to list all of your teammates. intercom.conversations.assign(id: conversation.id, admin_id: '123', assignee_id: '124') # Snooze From ad5e0ce45c76dc7149c9ad7b9beae3045487e8f7 Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Fri, 5 Oct 2018 10:52:28 +0100 Subject: [PATCH 148/245] Jonny/bump version (#435) --- README.md | 2 +- changes.txt | 5 +++++ lib/intercom/version.rb | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 24d5f623..80be27f5 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.6.1' + gem 'intercom', '~> 3.6.2' ## Basic Usage diff --git a/changes.txt b/changes.txt index c8c36dbc..1687c8ce 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,8 @@ +3.6.2 +#384 Add ability to snooze conversation +You can now snooze conversations in your app via: +intercom.conversations.snooze(...) + 3.6.1 #430 Allow all conversations to be listed You can now iterate over all conversations for your app via: diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index f4b60e39..158fa971 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.6.1" + VERSION = "3.6.2" end From c03a2449c0bc75c81b02aed4a46744c782b483a4 Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Mon, 5 Nov 2018 13:50:34 +0000 Subject: [PATCH 149/245] Updating readme in relation to #404 (#438) --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 80be27f5..eb77dab0 100644 --- a/README.md +++ b/README.md @@ -372,12 +372,19 @@ The metadata key values in the example are treated as follows- ### Contacts `Contacts` represent logged out users of your application. +Note that `contacts` are referred to as `leads` in the [Intercom](https://developers.intercom.com/intercom-api-reference/reference#leads) ```ruby # Create a contact contact = intercom.contacts.create(email: "some_contact@example.com") -# Update a contact +# Update a contact (via create method) +# You can update a contact by calling the create method but you MUST provide an id or user_id +# If you just provide an email, for example, it will create a new contact +# See the [API reference](https://developers.intercom.com/intercom-api-reference/reference#update-lead) for more detail +contact = intercom.contacts.create(email: "rubytest1@example.com", id: "3be0398668071a6bc6850413", name:"update_contact") + +# Update a contact (via contact object) contact.custom_attributes['foo'] = 'bar' intercom.contacts.save(contact) From 5905676e1c94ba94969aa0e08fa5b7801cf710bc Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Mon, 5 Nov 2018 15:08:39 +0000 Subject: [PATCH 150/245] Issue 404 (#439) * Updating readme in relation to #404 * Updating readme in relation to #404 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eb77dab0..cede6b9e 100644 --- a/README.md +++ b/README.md @@ -381,8 +381,8 @@ contact = intercom.contacts.create(email: "some_contact@example.com") # Update a contact (via create method) # You can update a contact by calling the create method but you MUST provide an id or user_id # If you just provide an email, for example, it will create a new contact -# See the [API reference](https://developers.intercom.com/intercom-api-reference/reference#update-lead) for more detail -contact = intercom.contacts.create(email: "rubytest1@example.com", id: "3be0398668071a6bc6850413", name:"update_contact") +# See https://developers.intercom.com/intercom-api-reference/reference#update-lead for more detail +contact = intercom.contacts.create(email: "some_contact@example.com", id: "3be0398668071a6bc6850413", name:"update_contact") # Update a contact (via contact object) contact.custom_attributes['foo'] = 'bar' From fe134518f01dd5bbe0120da7193b9ac9ca71e61c Mon Sep 17 00:00:00 2001 From: Andrew Kim Date: Fri, 16 Nov 2018 05:25:37 -0800 Subject: [PATCH 151/245] add an alias for archiving users and contacts and update readme accordingly (#442) remove delete file replace calls to delete with archive --- README.md | 17 ++++++++++------- .../api_operations/{delete.rb => archive.rb} | 6 ++++-- lib/intercom/service/contact.rb | 4 ++-- lib/intercom/service/subscription.rb | 4 ++-- lib/intercom/service/tag.rb | 2 +- lib/intercom/service/user.rb | 4 ++-- lib/intercom/service/visitor.rb | 4 ++-- spec/unit/intercom/user_spec.rb | 10 ++++++++-- 8 files changed, 31 insertions(+), 20 deletions(-) rename lib/intercom/api_operations/{delete.rb => archive.rb} (75%) diff --git a/README.md b/README.md index cede6b9e..724917de 100644 --- a/README.md +++ b/README.md @@ -68,9 +68,12 @@ user = intercom.users.find(user_id: "1") user = intercom.users.find(id: "1") # Create a user user = intercom.users.create(email: "bob@example.com", name: "Bob Smith", signed_up_at: Time.now.to_i) -# Delete a user +# archive a user +user = intercom.users.find(id: "1") user = intercom.users.find(id: "1") +deleted_user = intercom.users.delete(user) archived_user = intercom.users.archive(user) +# request a hard delete for a user(https://developers.intercom.com/intercom-api-reference/reference#delete-users) user = intercom.users.find(id: "1") -deleted_user = intercom.users.delete(user) +deleted_user = intercom.users.request_hard_delete(user) # Update custom_attributes for a user user.custom_attributes["average_monthly_spend"] = 1234.56 intercom.users.save(user) @@ -135,7 +138,7 @@ intercom.companies.all.map {|company| company.name } # Get a list of users in a company by Intercom Company ID intercom.companies.users_by_intercom_company_id(company.id) # Get a list of users in a company by external company_id -intercom.companies.users_by_company_id(company.company_id) +intercom.companies.users_by_company_id(company.company_id) # Get a large list of companies using scroll intercom.companies.scroll.each { |comp| puts comp.name} # Please see users scroll for more details of how to use scroll @@ -198,7 +201,7 @@ intercom.conversations.find_all(email: 'joe@example.com', type: 'user', unread: intercom.conversations.find_all(email: 'joe@example.com', type: 'user', unread: true).each {|convo| ... } # Iterate over all conversations for a user with their Intercom user ID intercom.conversations.find_all(intercom_user_id: '536e564f316c83104c000020', type: 'user').each {|convo| ... } -# Iterate over all conversations for a lead +# Iterate over all conversations for a lead # NOTE: to iterate over a lead's conversations you MUST use their Intercom User ID and type User intercom.conversations.find_all(intercom_user_id: lead.id, type: 'user').each {|convo| ... } @@ -228,7 +231,7 @@ intercom.conversations.open(id: conversation.id, admin_id: '123') intercom.conversations.close(id: conversation.id, admin_id: '123') # Assign -# Note: Conversations can be assigned to teams. However, the entity that performs the operation of assigning the conversation has to be an existing teammate. +# Note: Conversations can be assigned to teams. However, the entity that performs the operation of assigning the conversation has to be an existing teammate. # You can use `intercom.admins.all.each {|a| puts a.inspect if a.type == 'admin' }` to list all of your teammates. intercom.conversations.assign(id: conversation.id, admin_id: '123', assignee_id: '124') @@ -417,8 +420,8 @@ contact = intercom.contacts.find(id: "536e564f316c83104c000020") intercom.contacts.convert(contact, Intercom::User.new(email: email)) # Using find with email will not work here. See https://github.com/intercom/intercom-ruby/issues/419 for more information -# Delete a contact -intercom.contacts.delete(contact) +# archive a contact +intercom.contacts.archive(contact) # Get a large list of contacts using scroll intercom.contacts.scroll.each { |lead| puts lead.id} diff --git a/lib/intercom/api_operations/delete.rb b/lib/intercom/api_operations/archive.rb similarity index 75% rename from lib/intercom/api_operations/delete.rb rename to lib/intercom/api_operations/archive.rb index dbb466be..0e8db101 100644 --- a/lib/intercom/api_operations/delete.rb +++ b/lib/intercom/api_operations/archive.rb @@ -2,12 +2,14 @@ module Intercom module ApiOperations - module Delete - def delete(object) + module Archive + def archive(object) collection_name = Utils.resource_class_to_collection_name(collection_class) @client.delete("/#{collection_name}/#{object.id}", {}) object end + + alias_method 'delete', 'archive' end end end diff --git a/lib/intercom/service/contact.rb b/lib/intercom/service/contact.rb index 871a6da2..3a8ce974 100644 --- a/lib/intercom/service/contact.rb +++ b/lib/intercom/service/contact.rb @@ -6,7 +6,7 @@ require 'intercom/api_operations/save' require 'intercom/api_operations/scroll' require 'intercom/api_operations/convert' -require 'intercom/api_operations/delete' +require 'intercom/api_operations/archive' require 'intercom/api_operations/request_hard_delete' module Intercom @@ -19,7 +19,7 @@ class Contact < BaseService include ApiOperations::Save include ApiOperations::Scroll include ApiOperations::Convert - include ApiOperations::Delete + include ApiOperations::Archive include ApiOperations::RequestHardDelete def collection_class diff --git a/lib/intercom/service/subscription.rb b/lib/intercom/service/subscription.rb index eb5ea139..59840213 100644 --- a/lib/intercom/service/subscription.rb +++ b/lib/intercom/service/subscription.rb @@ -2,7 +2,7 @@ require 'intercom/api_operations/find_all' require 'intercom/api_operations/find' require 'intercom/api_operations/save' -require 'intercom/api_operations/delete' +require 'intercom/api_operations/archive' module Intercom module Service @@ -11,7 +11,7 @@ class Subscription < BaseService include ApiOperations::Find include ApiOperations::FindAll include ApiOperations::Save - include ApiOperations::Delete + include ApiOperations::Archive def collection_class Intercom::Subscription diff --git a/lib/intercom/service/tag.rb b/lib/intercom/service/tag.rb index cfb2c1fe..5044eada 100644 --- a/lib/intercom/service/tag.rb +++ b/lib/intercom/service/tag.rb @@ -9,7 +9,7 @@ class Tag < BaseService include ApiOperations::Save include ApiOperations::List include ApiOperations::FindAll - include ApiOperations::Delete + include ApiOperations::Archive def collection_class Intercom::Tag diff --git a/lib/intercom/service/user.rb b/lib/intercom/service/user.rb index e0ff2aee..63198862 100644 --- a/lib/intercom/service/user.rb +++ b/lib/intercom/service/user.rb @@ -5,7 +5,7 @@ require 'intercom/api_operations/find' require 'intercom/api_operations/find_all' require 'intercom/api_operations/save' -require 'intercom/api_operations/delete' +require 'intercom/api_operations/archive' require 'intercom/api_operations/bulk/submit' require 'intercom/api_operations/request_hard_delete' require 'intercom/extended_api_operations/tags' @@ -20,7 +20,7 @@ class User < BaseService include ApiOperations::Find include ApiOperations::FindAll include ApiOperations::Save - include ApiOperations::Delete + include ApiOperations::Archive include ApiOperations::RequestHardDelete include ApiOperations::Bulk::Submit include ExtendedApiOperations::Tags diff --git a/lib/intercom/service/visitor.rb b/lib/intercom/service/visitor.rb index ed557f47..0deac9cd 100644 --- a/lib/intercom/service/visitor.rb +++ b/lib/intercom/service/visitor.rb @@ -5,7 +5,7 @@ require 'intercom/api_operations/find_all' require 'intercom/api_operations/save' require 'intercom/api_operations/convert' -require 'intercom/api_operations/delete' +require 'intercom/api_operations/archive' module Intercom module Service @@ -16,7 +16,7 @@ class Visitor < BaseService include ApiOperations::FindAll include ApiOperations::Save include ApiOperations::Convert - include ApiOperations::Delete + include ApiOperations::Archive def collection_class Intercom::Visitor diff --git a/spec/unit/intercom/user_spec.rb b/spec/unit/intercom/user_spec.rb index c3621693..99ff42bb 100644 --- a/spec/unit/intercom/user_spec.rb +++ b/spec/unit/intercom/user_spec.rb @@ -218,11 +218,17 @@ client.users.save(user) end - it "deletes a user" do + it "archives a user" do user = Intercom::User.new("id" => "1") client.expects(:delete).with("/users/1", {}).returns(user) - client.users.delete(user) + client.users.archive(user) end + it "has an alias to archive a user" do + user = Intercom::User.new("id" => "1") + client.expects(:delete).with("/users/1", {}).returns(user) + client.users.delete(user) + end + it "sends a request for a hard deletion" do user = Intercom::User.new("id" => "1") From 7fbb67a5803ae9ad90ed159a108ff2f4784979a4 Mon Sep 17 00:00:00 2001 From: Andrew Kim Date: Fri, 16 Nov 2018 05:35:39 -0800 Subject: [PATCH 152/245] Residently conversations last reply (#443) * Added the missing conversation last reply action * updated readme to include * Cleanup stray comment * refactor test and update readme --- README.md | 9 +++++++++ lib/intercom/service/conversation.rb | 6 ++++++ spec/unit/intercom/conversation_spec.rb | 5 +++++ 3 files changed, 20 insertions(+) diff --git a/README.md b/README.md index 724917de..1443e86a 100644 --- a/README.md +++ b/README.md @@ -224,6 +224,9 @@ intercom.conversations.reply(id: conversation.id, type: 'admin', admin_id: '123' # User (identified by email) replies with a comment and attachment intercom.conversations.reply(id: conversation.id, type: 'user', email: 'joe@example.com', message_type: 'comment', body: 'foo', attachment_urls: ['http://www.example.com/attachment.jpg']) +#reply to a user's last conversation +intercom.conversations.reply_to_last(type: 'user', body: 'Thanks again', message_type: 'comment', user_id: '12345', admin_id: '123') + # Open intercom.conversations.open(id: conversation.id, admin_id: '123') @@ -244,6 +247,12 @@ intercom.conversations.reply(id: conversation.id, type: 'admin', admin_id: '123' # Reply and Close intercom.conversations.reply(id: conversation.id, type: 'admin', admin_id: '123', message_type: 'close', body: 'bar') +# Admin reply to last conversation +intercom.conversations.reply_to_last(intercom_user_id: '5678', type: 'admin', admin_id: '123', message_type: 'comment', body: 'bar') + +# User reply to last conversation +intercom.conversations.reply_to_last(intercom_user_id: '5678', type: 'user', message_type: 'comment', body: 'bar') + # ASSIGNING CONVERSATIONS TO ADMINS intercom.conversations.reply(id: conversation.id, type: 'admin', assignee_id: assignee_admin.id, admin_id: admin.id, message_type: 'assignment') diff --git a/lib/intercom/service/conversation.rb b/lib/intercom/service/conversation.rb index 40049576..95c4bd2c 100644 --- a/lib/intercom/service/conversation.rb +++ b/lib/intercom/service/conversation.rb @@ -29,6 +29,12 @@ def reply(reply_data) collection_class.new.from_response(response) end + def reply_to_last(reply_data) + collection_name = Utils.resource_class_to_collection_name(collection_class) + response = @client.post("/#{collection_name}/last/reply", reply_data) + collection_class.new.from_response(response) + end + def open(reply_data) reply reply_data.merge(message_type: 'open', type: 'admin') end diff --git a/spec/unit/intercom/conversation_spec.rb b/spec/unit/intercom/conversation_spec.rb index 037ac2bc..0f0c74cf 100644 --- a/spec/unit/intercom/conversation_spec.rb +++ b/spec/unit/intercom/conversation_spec.rb @@ -28,6 +28,11 @@ client.conversations.reply(id: '147', type: 'user', body: 'Thanks again', message_type: 'comment', user_id: 'ac4', attachment_urls: ["http://www.example.com/attachment.jpg"]) end + it 'sends a reply to a users last conversation from an admin' do + client.expects(:post).with('/conversations/last/reply', { type: 'admin', body: 'Thanks again', message_type: 'comment', user_id: 'ac4', admin_id: '123' }).returns(test_conversation) + client.conversations.reply_to_last(type: 'admin', body: 'Thanks again', message_type: 'comment', user_id: 'ac4', admin_id: '123') + end + it 'opens a conversation' do client.expects(:post).with('/conversations/147/reply', { type: 'admin', message_type: 'open', conversation_id: '147', admin_id: '123'}).returns(test_conversation) client.conversations.open(id: '147', admin_id: '123') From c749686ce1077a3ad70f5d957a2762f8c0bbd1c0 Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Fri, 16 Nov 2018 13:55:30 +0000 Subject: [PATCH 153/245] Version 3 7 0 (#444) * Bump to 3.7.0 * Version bump to 3.7.0 --- README.md | 9 +++++---- changes.txt | 12 ++++++++++++ lib/intercom/version.rb | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1443e86a..0622e9a2 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.6.2' + gem 'intercom', '~> 3.7.0' ## Basic Usage @@ -69,9 +69,10 @@ user = intercom.users.find(id: "1") # Create a user user = intercom.users.create(email: "bob@example.com", name: "Bob Smith", signed_up_at: Time.now.to_i) # archive a user -user = intercom.users.find(id: "1") user = intercom.users.find(id: "1") -deleted_user = intercom.users.delete(user) archived_user = intercom.users.archive(user) -# request a hard delete for a user(https://developers.intercom.com/intercom-api-reference/reference#delete-users) +user = intercom.users.find(id: "1") +archived_user = intercom.users.archive(user) +# request a hard delete for a user +(https://developers.intercom.com/intercom-api-reference/reference#delete-users) user = intercom.users.find(id: "1") deleted_user = intercom.users.request_hard_delete(user) # Update custom_attributes for a user diff --git a/changes.txt b/changes.txt index 1687c8ce..1a64dc28 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,15 @@ +3.7.0 +Providing the ability to hard delete users as described here: +https://developers.intercom.com/intercom-api-reference/reference#archive-a-user + +This chaged the previous delete action to an archive action and added a new hard delete option +You can still use the delete method but it will archive a user, we added an alias for delete. +#442 archiving alias +#410 add ability to hard delete users + +Alos enabling reply to last from the SDK +#443 Residently conversations last reply + 3.6.2 #384 Add ability to snooze conversation You can now snooze conversations in your app via: diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 158fa971..3b02228e 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.6.2" + VERSION = "3.7.0" end From 8541b68606d1cd4e471fbaf549b65bed20c5bd92 Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Wed, 21 Nov 2018 17:51:32 +0000 Subject: [PATCH 154/245] Bump to 3.7.1 (#447) --- README.md | 2 +- changes.txt | 3 +++ lib/intercom/version.rb | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0622e9a2..c31b61c5 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.7.0' + gem 'intercom', '~> 3.7.1' ## Basic Usage diff --git a/changes.txt b/changes.txt index 1a64dc28..5ff0c01c 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.7.1 +Extra version bump after faulty previous bump + 3.7.0 Providing the ability to hard delete users as described here: https://developers.intercom.com/intercom-api-reference/reference#archive-a-user diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 3b02228e..8fb1da3a 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.7.0" + VERSION = "3.7.1" end From b8140786e47d9413ae736412a1f937756cf23c7e Mon Sep 17 00:00:00 2001 From: Jonny Date: Tue, 15 Jan 2019 11:13:03 +0000 Subject: [PATCH 155/245] Add CDA Limit Reached Error handling --- lib/intercom/errors.rb | 3 +++ lib/intercom/request.rb | 2 ++ 2 files changed, 5 insertions(+) diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index 69f7b044..0a4b01e0 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -63,6 +63,9 @@ class RateLimitExceeded < IntercomError; end # Raised when the request throws an error not accounted for class UnexpectedError < IntercomError; end + # Raised when the CDA limit for the app has been reached + class CDALimitReachedError < IntercomError; end + # Raised when multiple users match the query (typically duplicate email addresses) class MultipleMatchingUsersError < IntercomError; end diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 031924e9..0044233a 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -170,6 +170,8 @@ def raise_application_errors_on_failure(error_list_details, http_code) raise Intercom::ResourceNotFound.new(error_details['message'], error_context) when "rate_limit_exceeded" raise Intercom::RateLimitExceeded.new(error_details['message'], error_context) + when "custom_data_limit_reached" + raise Intercom::CDALimitReachedError.new(error_details['message'], error_context) when 'service_unavailable' raise Intercom::ServiceUnavailableError.new(error_details['message'], error_context) when 'conflict', 'unique_user_constraint' From 20dcfc5df0783f997140332e4232fb6b4ee825b6 Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Tue, 15 Jan 2019 11:44:05 +0000 Subject: [PATCH 156/245] Jonny/bump to 3.7.2 (#455) --- README.md | 2 +- changes.txt | 3 +++ lib/intercom/version.rb | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c31b61c5..39e2919f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.7.1' + gem 'intercom', '~> 3.7.2' ## Basic Usage diff --git a/changes.txt b/changes.txt index 5ff0c01c..a2f542af 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.7.2 +Added error handling for when an app's custom attribute limits have been reached. + 3.7.1 Extra version bump after faulty previous bump diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 8fb1da3a..ab1eb79c 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.7.1" + VERSION = "3.7.2" end From 8a89cb62c5314ca8a29995777e51aaaa489c2bce Mon Sep 17 00:00:00 2001 From: Cathal Horan Date: Wed, 16 Jan 2019 11:12:49 +0000 Subject: [PATCH 157/245] Adding example to query multiple users (#456) --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 39e2919f..bf2761eb 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,14 @@ intercom.users.all.map {|user| user.email } intercom.users.find_all(type: 'users', page: 1, per_page: 10, created_since: 2, order: :asc).to_a.each_with_index {|usr, i| puts "#{i+1}: #{usr.name}"}; # Paginate through your list of users choosing how many to return per page (default and max is 50 per page) intercom.users.find_all(type: 'users', page: 1, per_page: 10, order: :asc).to_a.each_with_index {|usr, i| puts "#{i+1}: #{usr.name}"} + +# Duplicate users? If you have duplicate users you can search for them via their email address. +# Note this feature is only available from version 1.1 of the API so you will need to switch to that version +# This will return multiple users if they have the same email address +usrs = intercom.users.find_all(type: 'users', email: 'myemail@example.com', page: 1, per_page: 10, order: :asc) +# This returns a user.list so you can access it via +usrs.to_a.each_with_index {|usr, i| puts "#{i+1}: #{usr.id}"}; + # If you have over 10,000 users then you will need to use the scroll function to list your users # otherwise you will encounter a page limit with list all your users # You can use the scroll method to list all your users From f55d263445910d91865d8e1d6ccccc2a7924d692 Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Fri, 18 Jan 2019 15:52:25 +0000 Subject: [PATCH 158/245] Raise admin not found error (#457) --- lib/intercom/errors.rb | 3 +++ lib/intercom/request.rb | 2 ++ 2 files changed, 5 insertions(+) diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index 0a4b01e0..e07fae5a 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -51,6 +51,9 @@ class ServiceConnectionError < IntercomError; end # Raised when requesting resources on behalf of a user that doesn't exist in your application on Intercom. class ResourceNotFound < IntercomError; end + # Raised when requesting an admin that doesn't exist in your Intercom workspace. + class AdminNotFound < IntercomError; end + # Raised when trying to create a resource that already exists in Intercom. class ResourceNotUniqueError < IntercomError; end diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 0044233a..c4e29b3e 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -168,6 +168,8 @@ def raise_application_errors_on_failure(error_list_details, http_code) raise Intercom::BlockedUserError.new(error_details['message'], error_context) when "not_found" raise Intercom::ResourceNotFound.new(error_details['message'], error_context) + when "admin_not_found" + raise Intercom::AdminNotFound.new(error_details['message'], error_context) when "rate_limit_exceeded" raise Intercom::RateLimitExceeded.new(error_details['message'], error_context) when "custom_data_limit_reached" From 7020bf0df93e681ba4626a4249ecc09c2d716343 Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Fri, 18 Jan 2019 15:58:37 +0000 Subject: [PATCH 159/245] Jonny/bump intercom to 3.7.3 (#458) --- README.md | 2 +- changes.txt | 3 +++ lib/intercom/version.rb | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bf2761eb..04c868a4 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.7.2' + gem 'intercom', '~> 3.7.3' ## Basic Usage diff --git a/changes.txt b/changes.txt index a2f542af..46012821 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.7.3 +Added error handling for when an admin cannot be found. + 3.7.2 Added error handling for when an app's custom attribute limits have been reached. diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index ab1eb79c..b9e03110 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.7.2" + VERSION = "3.7.3" end From d51cfaa78934d0ac278663ef7001d3d3278c2dba Mon Sep 17 00:00:00 2001 From: Kyle Conarro Date: Fri, 1 Mar 2019 05:24:26 -0500 Subject: [PATCH 160/245] Add API version support (#459) --- lib/intercom/client.rb | 14 +++++++++++--- lib/intercom/errors.rb | 3 +++ lib/intercom/request.rb | 9 ++++++++- spec/unit/intercom/client_spec.rb | 23 +++++++++++++++++++++++ spec/unit/intercom/request_spec.rb | 9 ++++++++- 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/lib/intercom/client.rb b/lib/intercom/client.rb index 982d6475..bbc5b8e7 100644 --- a/lib/intercom/client.rb +++ b/lib/intercom/client.rb @@ -2,7 +2,7 @@ module Intercom class MisconfiguredClientError < StandardError; end class Client include Options - attr_reader :base_url, :rate_limit_details, :username_part, :password_part, :handle_rate_limit, :timeouts + attr_reader :base_url, :rate_limit_details, :username_part, :password_part, :handle_rate_limit, :timeouts, :api_version class << self def set_base_url(base_url) @@ -25,7 +25,7 @@ def set_timeouts(open_timeout: nil, read_timeout: nil) end end - def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil, base_url:'https://api.intercom.io', handle_rate_limit: false) + 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) if token @username_part = token @password_part = "" @@ -35,6 +35,9 @@ def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil, base_url: end validate_credentials! + @api_version = api_version + validate_api_version! + @base_url = base_url @rate_limit_details = {} @handle_rate_limit = handle_rate_limit @@ -123,9 +126,14 @@ def validate_credentials! fail error if @username_part.nil? end + def validate_api_version! + error = MisconfiguredClientError.new("api_version must be either nil or a valid API version") + fail error if (@api_version && Gem::Version.new(@api_version) < Gem::Version.new('1.0')) + end + def execute_request(request) request.handle_rate_limit = handle_rate_limit - request.execute(@base_url, username: @username_part, secret: @password_part, **timeouts) + request.execute(@base_url, username: @username_part, secret: @password_part, api_version: @api_version, **timeouts) ensure @rate_limit_details = request.rate_limit_details end diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index e07fae5a..ace67d7b 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -81,6 +81,9 @@ class Intercom::AttributeNotSetError < IntercomError; end # Raised when unexpected nil returned from server class Intercom::HttpError < IntercomError; end + # Raised when an invalid api version is used + class ApiVersionInvalid < IntercomError; end + # # Non-public errors (internal to the gem) # diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index c4e29b3e..7e15c9c5 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -19,6 +19,10 @@ def set_basic_auth(method, username, secret) method.basic_auth(CGI.unescape(username), CGI.unescape(secret)) end + def set_api_version(method, api_version) + method.add_field('Intercom-Version', api_version) + end + def self.get(path, params) new(path, Net::HTTP::Get.new(append_query_string_to_url(path, params), default_headers)) end @@ -58,11 +62,12 @@ def client(uri, read_timeout:, open_timeout:) net end - def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_timeout: 30) + def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_timeout: 30, api_version: nil) retries = 3 base_uri = URI.parse(target_base_url) set_common_headers(net_http_method, base_uri) set_basic_auth(net_http_method, username, secret) + set_api_version(net_http_method, api_version) if api_version begin client(base_uri, read_timeout: read_timeout, open_timeout: open_timeout).start do |http| begin @@ -180,6 +185,8 @@ def raise_application_errors_on_failure(error_list_details, http_code) raise Intercom::MultipleMatchingUsersError.new(error_details['message'], error_context) when 'resource_conflict' raise Intercom::ResourceNotUniqueError.new(error_details['message'], error_context) + when 'intercom_version_invalid' + raise Intercom::ApiVersionInvalid.new(error_details['message'], error_context) when nil, '' raise Intercom::UnexpectedError.new(message_for_unexpected_error_without_type(error_details, parsed_http_code), error_context) else diff --git a/spec/unit/intercom/client_spec.rb b/spec/unit/intercom/client_spec.rb index a62685d2..ace7be05 100644 --- a/spec/unit/intercom/client_spec.rb +++ b/spec/unit/intercom/client_spec.rb @@ -42,6 +42,29 @@ module Intercom proc { Client.new(app_id: nil, api_key: nil) }.must_raise MisconfiguredClientError end + describe 'API version' do + it 'does not set the api version by default' do + assert_nil(client.api_version) + end + + it 'allows api version to be provided' do + Client.new(app_id: app_id, api_key: api_key, api_version: '1.0').api_version.must_equal('1.0') + end + + it 'allows api version to be nil' do + # matches default behavior, and will honor version set in the Developer Hub + assert_nil(Client.new(app_id: app_id, api_key: api_key, api_version: nil).api_version) + end + + it 'raises on invalid api version' do + proc { Client.new(app_id: app_id, api_key: api_key, api_version: '0.2') }.must_raise MisconfiguredClientError + end + + it 'raises on empty api version' do + proc { Client.new(app_id: app_id, api_key: api_key, api_version: '') }.must_raise MisconfiguredClientError + end + end + describe 'OAuth clients' do it 'supports "token"' do client = Client.new(token: 'foo') diff --git a/spec/unit/intercom/request_spec.rb b/spec/unit/intercom/request_spec.rb index e1922fdb..6904659f 100644 --- a/spec/unit/intercom/request_spec.rb +++ b/spec/unit/intercom/request_spec.rb @@ -72,7 +72,6 @@ req.expects(:sleep).never.with(any_parameters) req.execute(target_base_url=uri, username: "ted", secret: "") end - end @@ -85,6 +84,14 @@ req = Intercom::Request.put(uri, "") expect { req.execute(target_base_url=uri, username: "ted", secret: "") }.must_raise(Intercom::ResourceNotUniqueError) end + + it 'should raise ApiVersionInvalid error on intercom_version_invalid code' do + # Use webmock to mock the HTTP request + stub_request(:put, uri).\ + to_return(status: [400, "Bad Request"], headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }, body: {type: "error.list", errors: [ code: "intercom_version_invalid" ]}.to_json) + req = Intercom::Request.put(uri, "") + expect { req.execute(uri, username: "ted", secret: "") }.must_raise(Intercom::ApiVersionInvalid) + end end it 'parse_body returns nil if decoded_body is nil' do From 1a7ab5b1b0834104fb49c44dc497c01800a7f08f Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Fri, 1 Mar 2019 10:36:28 +0000 Subject: [PATCH 161/245] Bump intercom to 3.7.4 (#461) --- README.md | 9 +++++++-- changes.txt | 4 ++++ lib/intercom/version.rb | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 04c868a4..46041b45 100644 --- a/README.md +++ b/README.md @@ -22,19 +22,24 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.7.3' + gem 'intercom', '~> 3.7.4' ## Basic Usage ### Configure your client -> If you already have a personal access token you can find it [here](https://app.intercom.io/developers/_/access-token). If you want to create or learn more about personal access tokens then you can find more info [here](https://developers.intercom.io/docs/personal-access-tokens). +> If you already have a personal access token you can find it [here](https://app.intercom.io/a/apps/_/developer_hub/). If you want to create or learn more about personal access tokens then you can find more info [here](https://developers.intercom.io/docs/personal-access-tokens). ```ruby # With an OAuth or Personal Access token: intercom = Intercom::Client.new(token: 'my_token') ``` +```ruby +# With a versioned app: +intercom = Intercom::Client.new(token: 'my_token', api_version: '1.0') +``` + If you are building a third party application you can get your access_tokens by [setting-up-oauth](https://developers.intercom.io/page/setting-up-oauth) for Intercom. You can also use the [omniauth-intercom lib](https://github.com/intercom/omniauth-intercom) which is a middleware helping you to handle the authentication process with Intercom. diff --git a/changes.txt b/changes.txt index 46012821..8c9a2fa0 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,7 @@ +3.7.4 +Added support for API versioning via +Intercom::Client.new(token: "token", api_version "1.1") + 3.7.3 Added error handling for when an admin cannot be found. diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index b9e03110..0f150763 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.7.3" + VERSION = "3.7.4" end From 34bc972baf8c30050bcd65e8f46d3074a75511ca Mon Sep 17 00:00:00 2001 From: Devin McGloin Date: Fri, 1 Mar 2019 13:04:17 +0000 Subject: [PATCH 162/245] Added error handling for scroll existing already --- lib/intercom/errors.rb | 3 +++ lib/intercom/request.rb | 2 ++ 2 files changed, 5 insertions(+) diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index ace67d7b..f58646b9 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -84,6 +84,9 @@ class Intercom::HttpError < IntercomError; end # Raised when an invalid api version is used class ApiVersionInvalid < IntercomError; end + # Raised when an creating a scroll if one already exists + class ScrollAlreadyExistsError < IntercomError; end + # # Non-public errors (internal to the gem) # diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 7e15c9c5..aac65c3e 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -187,6 +187,8 @@ def raise_application_errors_on_failure(error_list_details, http_code) raise Intercom::ResourceNotUniqueError.new(error_details['message'], error_context) when 'intercom_version_invalid' raise Intercom::ApiVersionInvalid.new(error_details['message'], error_context) + when 'scroll_exists' + raise Intercom::ScrollAlreadyExistsError.new(error_details['message'], error_context) when nil, '' raise Intercom::UnexpectedError.new(message_for_unexpected_error_without_type(error_details, parsed_http_code), error_context) else From c48838fecc5e80d25f7c608f48a8d1604fb1cf48 Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Fri, 1 Mar 2019 13:47:38 +0000 Subject: [PATCH 163/245] Bump intercom to 3.7.5 (#464) --- README.md | 2 +- changes.txt | 3 +++ lib/intercom/version.rb | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 46041b45..9969c7b1 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.7.4' + gem 'intercom', '~> 3.7.5' ## Basic Usage diff --git a/changes.txt b/changes.txt index 8c9a2fa0..2941cfe6 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.7.5 +Added error handling for scroll_exists error state + 3.7.4 Added support for API versioning via Intercom::Client.new(token: "token", api_version "1.1") diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 0f150763..763cd699 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.7.4" + VERSION = "3.7.5" end From 01885eebb229c9022a1236f0881bd04d196ecd61 Mon Sep 17 00:00:00 2001 From: Jonny Date: Tue, 26 Mar 2019 11:19:45 +0000 Subject: [PATCH 164/245] Add invalid document error handling --- lib/intercom/errors.rb | 3 +++ lib/intercom/request.rb | 2 ++ 2 files changed, 5 insertions(+) diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index f58646b9..567160b8 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -87,6 +87,9 @@ class ApiVersionInvalid < IntercomError; end # Raised when an creating a scroll if one already exists class ScrollAlreadyExistsError < IntercomError; end + # Raised when a CDA is invalid + class InvalidDocumentError < IntercomError; end + # # Non-public errors (internal to the gem) # diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index aac65c3e..ebbd8f2d 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -179,6 +179,8 @@ def raise_application_errors_on_failure(error_list_details, http_code) raise Intercom::RateLimitExceeded.new(error_details['message'], error_context) when "custom_data_limit_reached" raise Intercom::CDALimitReachedError.new(error_details['message'], error_context) + when "invalid_document" + raise Intercom::InvalidDocumentError.new(error_details['message'], error_context) when 'service_unavailable' raise Intercom::ServiceUnavailableError.new(error_details['message'], error_context) when 'conflict', 'unique_user_constraint' From 7f9290049fa2af49d964fe5ada269712ed594d2e Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Tue, 26 Mar 2019 11:29:58 +0000 Subject: [PATCH 165/245] Jonny/bump intercom to 3.7.6 (#466) --- README.md | 2 +- changes.txt | 3 +++ lib/intercom/version.rb | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9969c7b1..23390b2f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.7.5' + gem 'intercom', '~> 3.7.6' ## Basic Usage diff --git a/changes.txt b/changes.txt index 2941cfe6..e017ce2b 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.7.6 +Added error handling for invalid_document error state + 3.7.5 Added error handling for scroll_exists error state diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 763cd699..d6ab77f3 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.7.5" + VERSION = "3.7.6" end From 929ca70762ed822a82902433c0e47ad5f0660afd Mon Sep 17 00:00:00 2001 From: Darragh O'Toole Date: Thu, 4 Apr 2019 12:34:23 +0100 Subject: [PATCH 166/245] This commit was generated by GemUpdater --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 76625cbd..695cc363 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 jobs: build: docker: - - image: circleci/ruby:2.3.7 + - image: circleci/ruby:2.4.6 working_directory: ~/intercom-ruby From 7514ef756372f20cbcb57f3f6ce558e165d17985 Mon Sep 17 00:00:00 2001 From: Kevin Ebaugh Date: Thu, 9 May 2019 09:36:12 -0400 Subject: [PATCH 167/245] Fix developer hub link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 23390b2f..58531419 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Using bundler: ### Configure your client -> If you already have a personal access token you can find it [here](https://app.intercom.io/a/apps/_/developer_hub/). If you want to create or learn more about personal access tokens then you can find more info [here](https://developers.intercom.io/docs/personal-access-tokens). +> If you already have a personal access token you can find it [here](https://app.intercom.io/a/apps/_/developer-hub/). If you want to create or learn more about personal access tokens then you can find more info [here](https://developers.intercom.io/docs/personal-access-tokens). ```ruby # With an OAuth or Personal Access token: From d750e48aa89b9ce66d3cd206f38c1eda49941a1e Mon Sep 17 00:00:00 2001 From: Paulo Ancheta Date: Fri, 21 Jun 2019 14:51:33 -0700 Subject: [PATCH 168/245] create an event using a user_id instead of email --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 58531419..9fd2ee04 100644 --- a/README.md +++ b/README.md @@ -360,6 +360,13 @@ intercom.events.create( } ) +# Alternatively, use "user_id" in case your app allows multiple accounts having the same email +intercom.events.create( + event_name: "invited-friend", + created_at: Time.now.to_i, + user_id: user.uuid, +) + # Retrieve event list for user with id:'123abc' intercom.events.find_all("type" => "user", "intercom_user_id" => "123abc") From 98b7cf718a673f31e704083dc56caa1c8e2ada52 Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Sun, 14 Jul 2019 18:13:37 +0200 Subject: [PATCH 169/245] Gemspec: drop EOL'd property rubyforge_project --- intercom.gemspec | 1 - 1 file changed, 1 deletion(-) diff --git a/intercom.gemspec b/intercom.gemspec index ba48cc01..d797f951 100644 --- a/intercom.gemspec +++ b/intercom.gemspec @@ -12,7 +12,6 @@ Gem::Specification.new do |spec| spec.summary = %q{Ruby bindings for the Intercom API} spec.description = %Q{Intercom (https://www.intercom.io) is a customer relationship management and messaging tool for web app owners. This library wraps the api provided by Intercom. See http://docs.intercom.io/api for more details. } spec.license = "MIT" - spec.rubyforge_project = "intercom" spec.files = `git ls-files`.split("\n") spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") From 43a0b77a0eb4b2598379a706829639126e6d6233 Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Mon, 15 Jul 2019 11:10:28 +0100 Subject: [PATCH 170/245] Bump Intercom to 3.7.7 (#478) * Bump intercom to 3.7.7 * Include readme and changes --- README.md | 2 +- changes.txt | 3 +++ lib/intercom/version.rb | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9fd2ee04..dc019db4 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.7.6' + gem 'intercom', '~> 3.7.7' ## Basic Usage diff --git a/changes.txt b/changes.txt index e017ce2b..5e56756b 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.7.7 +Remove deprecated features from Gemspec + 3.7.6 Added error handling for invalid_document error state diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index d6ab77f3..9c1853df 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.7.6" + VERSION = "3.7.7" end From f641a577d53f0d06969cf037f644a36154bb64a2 Mon Sep 17 00:00:00 2001 From: Alexandre Passant Date: Tue, 30 Jul 2019 22:06:47 +0200 Subject: [PATCH 171/245] Added support for Customer Search API --- README.md | 14 +++- lib/intercom.rb | 2 + lib/intercom/api_operations/search.rb | 17 ++++ lib/intercom/client.rb | 4 + lib/intercom/customer.rb | 10 +++ lib/intercom/search_collection_proxy.rb | 82 +++++++++++++++++++ lib/intercom/service/customer.rb | 14 ++++ spec/spec_helper.rb | 23 ++++++ .../intercom/search_collection_proxy_spec.rb | 56 +++++++++++++ 9 files changed, 218 insertions(+), 4 deletions(-) create mode 100644 lib/intercom/api_operations/search.rb create mode 100644 lib/intercom/customer.rb create mode 100644 lib/intercom/search_collection_proxy.rb create mode 100644 lib/intercom/service/customer.rb create mode 100644 spec/unit/intercom/search_collection_proxy_spec.rb diff --git a/README.md b/README.md index dc019db4..7e2c2d1b 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ intercom.users.find_all(type: 'users', page: 1, per_page: 10, created_since: 2, # Paginate through your list of users choosing how many to return per page (default and max is 50 per page) intercom.users.find_all(type: 'users', page: 1, per_page: 10, order: :asc).to_a.each_with_index {|usr, i| puts "#{i+1}: #{usr.name}"} -# Duplicate users? If you have duplicate users you can search for them via their email address. +# Duplicate users? If you have duplicate users you can search for them via their email address. # Note this feature is only available from version 1.1 of the API so you will need to switch to that version # This will return multiple users if they have the same email address usrs = intercom.users.find_all(type: 'users', email: 'myemail@example.com', page: 1, per_page: 10, order: :asc) @@ -402,7 +402,7 @@ The metadata key values in the example are treated as follows- *NB:* This version of the gem reserves the field name `type` in Event data. -### Contacts +#### Contacts `Contacts` represent logged out users of your application. Note that `contacts` are referred to as `leads` in the [Intercom](https://developers.intercom.com/intercom-api-reference/reference#leads) @@ -458,7 +458,13 @@ intercom.contacts.scroll.each { |lead| puts lead.id} # Please see users scroll for more details of how to use scroll ``` -### Counts +#### Customers + +# Search for customers +customers = intercom.customers.search(query: { "field": "name", "operator": "=", "value": "Alice"}, per_page: 50, sort_field: "name", sort_order: "ascending") +customers.each { |customer| p customer.name } + +#### Counts ```ruby # App-wide counts @@ -468,7 +474,7 @@ intercom.counts.for_app intercom.counts.for_type(type: 'user', count: 'segment') ``` -### Subscriptions +#### Subscriptions Subscribe to events in Intercom to receive webhooks. diff --git a/lib/intercom.rb b/lib/intercom.rb index ac89b7d5..0277c208 100644 --- a/lib/intercom.rb +++ b/lib/intercom.rb @@ -4,6 +4,7 @@ require 'intercom/service/contact' require 'intercom/service/conversation' require 'intercom/service/count' +require 'intercom/service/customer' require 'intercom/service/event' require 'intercom/service/message' require 'intercom/service/note' @@ -17,6 +18,7 @@ require 'intercom/client' require "intercom/contact" require "intercom/count" +require "intercom/customer" require "intercom/user" require "intercom/company" require "intercom/note" diff --git a/lib/intercom/api_operations/search.rb b/lib/intercom/api_operations/search.rb new file mode 100644 index 00000000..40406f64 --- /dev/null +++ b/lib/intercom/api_operations/search.rb @@ -0,0 +1,17 @@ +require 'intercom/search_collection_proxy' +require 'intercom/utils' + +module Intercom + module ApiOperations + module Search + def search(params) + collection_name = Utils.resource_class_to_collection_name(collection_class) + search_details = { + url: "/#{collection_name}/search", + params: params + } + SearchCollectionProxy.new(collection_name, search_details: search_details, client: @client) + end + end + end +end diff --git a/lib/intercom/client.rb b/lib/intercom/client.rb index bbc5b8e7..00e2b02c 100644 --- a/lib/intercom/client.rb +++ b/lib/intercom/client.rb @@ -67,6 +67,10 @@ def counts Intercom::Service::Counts.new(self) end + def customers + Intercom::Service::Customer.new(self) + end + def events Intercom::Service::Event.new(self) end diff --git a/lib/intercom/customer.rb b/lib/intercom/customer.rb new file mode 100644 index 00000000..e996a9d9 --- /dev/null +++ b/lib/intercom/customer.rb @@ -0,0 +1,10 @@ +require 'intercom/traits/api_resource' + +module Intercom + class Customer + include Traits::ApiResource + + def identity_vars ; [:id, :email, :user_id] ; end + def flat_store_attributes ; [:custom_attributes] ; end + end +end diff --git a/lib/intercom/search_collection_proxy.rb b/lib/intercom/search_collection_proxy.rb new file mode 100644 index 00000000..78c064c5 --- /dev/null +++ b/lib/intercom/search_collection_proxy.rb @@ -0,0 +1,82 @@ +require "intercom/utils" + +module Intercom + class SearchCollectionProxy + + attr_reader :resource_name, :resource_class + + def initialize(resource_name, search_details: {}, client:) + @resource_name = resource_name + @resource_class = Utils.constantize_resource_name(resource_name) + @search_url = search_details[:url] + @search_params = search_details[:params] + @client = client + end + + def each(&block) + loop do + response_hash = @client.post(@search_url, payload) + raise Intercom::HttpError.new('Http Error - No response entity returned') unless response_hash + deserialize_response_hash(response_hash, block) + break unless has_next_link?(response_hash) + end + self + end + + def [](target_index) + self.each_with_index do |item, index| + return item if index == target_index + end + nil + end + + include Enumerable + + private + + def deserialize_response_hash(response_hash, block) + top_level_type = response_hash.delete('type') + top_level_entity_key = Utils.entity_key_from_type(top_level_type) + response_hash[top_level_entity_key].each do |object_json| + block.call Lib::TypedJsonDeserializer.new(object_json).deserialize + end + end + + def has_next_link?(response_hash) + paging_info = response_hash.delete('pages') + paging_next = paging_info["next"] + if paging_next + @search_params[:starting_after] = paging_next["starting_after"] + return true + else + return false + end + end + + def payload + payload = { + query: @search_params[:query] + } + if @search_params[:sort_field] || @search_params[:sort_order] + payload[:sort] = {} + if @search_params[:sort_field] + payload[:sort][:field] = @search_params[:sort_field] + end + if @search_params[:sort_order] + payload[:sort][:order] = @search_params[:sort_order] + end + end + if @search_params[:per_page] || @search_params[:starting_after] + payload[:pagination] = {} + if @search_params[:per_page] + payload[:pagination][:per_page] = @search_params[:per_page] + end + if @search_params[:starting_after] + payload[:pagination][:starting_after] = @search_params[:starting_after] + end + end + return payload + end + + end +end diff --git a/lib/intercom/service/customer.rb b/lib/intercom/service/customer.rb new file mode 100644 index 00000000..48fdad95 --- /dev/null +++ b/lib/intercom/service/customer.rb @@ -0,0 +1,14 @@ +require 'intercom/service/base_service' +require 'intercom/api_operations/search' + +module Intercom + module Service + class Customer < BaseService + include ApiOperations::Search + + def collection_class + Intercom::Customer + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e7655e43..5be39bc8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,6 +5,13 @@ require 'time' include WebMock::API +def test_customer(email="bob@example.com") + customer = test_user(email) + customer["type"] = "customer" + customer["role"] = "user" + customer +end + def test_user(email="bob@example.com") { "type" =>"user", @@ -228,6 +235,22 @@ def page_of_users(include_next_link= false) } end +def page_of_customers(include_starting_after= false) + { + "type"=>"customer.list", + "pages"=> + { + "type"=>"pages", + "next"=> (include_starting_after ? { "page" => 2, "starting_after" => "EnCrYpTeDsTrInG" } : nil), + "page"=>1, + "per_page"=>50, + "total_pages"=>7 + }, + "customers"=> [test_customer("user1@example.com"), test_customer("user2@example.com"), test_customer("user3@example.com")], + "total_count"=>314 + } +end + def users_scroll(include_users= false) { "type"=>"user.list", diff --git a/spec/unit/intercom/search_collection_proxy_spec.rb b/spec/unit/intercom/search_collection_proxy_spec.rb new file mode 100644 index 00000000..3088c06e --- /dev/null +++ b/spec/unit/intercom/search_collection_proxy_spec.rb @@ -0,0 +1,56 @@ +require "spec_helper" + +describe Intercom::SearchCollectionProxy do + let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } + + it "send query to the customer search endpoint" do + client.expects(:post).with("/customers/search", { query: {} }). returns(page_of_customers(false)) + client.customers.search(query: {}).first + end + + it "send query to the customer search endpoint with sort_field" do + client.expects(:post).with("/customers/search", { query: {}, sort: { field: "name" } }). returns(page_of_customers(false)) + client.customers.search(query: {}, sort_field: "name").first + end + + it "send query to the customer search endpoint with sort_field and sort_order" do + client.expects(:post).with("/customers/search", { query: {}, sort: { field: "name", order: "ascending" } }). returns(page_of_customers(false)) + client.customers.search(query: {}, sort_field: "name", sort_order: "ascending").first + end + + it "send query to the customer search endpoint with per_page" do + client.expects(:post).with("/customers/search", { query: {}, pagination: { per_page: 10 }}). returns(page_of_customers(false)) + client.customers.search(query: {}, per_page: 10).first + end + + it "send query to the customer search endpoint with starting_after" do + client.expects(:post).with("/customers/search", { query: {}, pagination: { starting_after: "EnCrYpTeDsTrInG" }}). returns(page_of_customers(false)) + client.customers.search(query: {}, starting_after: "EnCrYpTeDsTrInG").first + end + + it "stops iterating if no starting_after value" do + client.expects(:post).with("/customers/search", { query: {} }). returns(page_of_customers(false)) + emails = [] + client.customers.search(query: {}).each { |user| emails << user.email } + emails.must_equal %W(user1@example.com user2@example.com user3@example.com) + end + + it "keeps iterating if starting_after value" do + client.expects(:post).with("/customers/search", { query: {} }).returns(page_of_customers(true)) + client.expects(:post).with("/customers/search", { query: {}, pagination: { starting_after: "EnCrYpTeDsTrInG" }}).returns(page_of_customers(false)) + emails = [] + client.customers.search(query: {}).each { |user| emails << user.email } + end + + it "supports indexed array access" do + client.expects(:post).with("/customers/search", { query: {} }).returns(page_of_customers(false)) + client.customers.search(query: {})[0].email.must_equal 'user1@example.com' + end + + it "supports map" do + client.expects(:post).with("/customers/search", { query: {} }).returns(page_of_customers(false)) + emails = client.customers.search(query: {}).map { |user| user.email } + emails.must_equal %W(user1@example.com user2@example.com user3@example.com) + end + +end From c635d0491ad5b10833bde470a80fa685e39de001 Mon Sep 17 00:00:00 2001 From: Alexandre Passant Date: Thu, 1 Aug 2019 11:16:51 +0200 Subject: [PATCH 172/245] Bump intercom to 3.8.0 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 9c1853df..57047f7c 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.7.7" + VERSION = "3.8.0" end From 3214b5ad7f876434c6b61fee67b3492272c8e93e Mon Sep 17 00:00:00 2001 From: Alexandre Passant Date: Thu, 1 Aug 2019 11:18:32 +0200 Subject: [PATCH 173/245] BUmp to 3.8.0 --- README.md | 2 +- changes.txt | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e2c2d1b..61b106db 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.7.7' + gem 'intercom', '~> 3.8.0' ## Basic Usage diff --git a/changes.txt b/changes.txt index 5e56756b..04385914 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,7 @@ +3.8.0 +Add support for Customer Search (currently in Unstable API Version) +https://developers.intercom.com/intercom-api-reference/v0/reference#customers + 3.7.7 Remove deprecated features from Gemspec From 4d20387374762a61318dcc311a8d5279ca75cb75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nath=C3=A1lia=20Oliveira?= Date: Thu, 3 Oct 2019 00:00:34 -0300 Subject: [PATCH 174/245] Raise ResourceNotFound when company is not found (fixes #484) Also removed unused variable from request spec --- lib/intercom/request.rb | 2 +- spec/unit/intercom/request_spec.rb | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index ebbd8f2d..d9f8a107 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -171,7 +171,7 @@ def raise_application_errors_on_failure(error_list_details, http_code) raise Intercom::BadRequestError.new(error_details['message'], error_context) when "not_restorable" raise Intercom::BlockedUserError.new(error_details['message'], error_context) - when "not_found" + when "not_found", "company_not_found" raise Intercom::ResourceNotFound.new(error_details['message'], error_context) when "admin_not_found" raise Intercom::AdminNotFound.new(error_details['message'], error_context) diff --git a/spec/unit/intercom/request_spec.rb b/spec/unit/intercom/request_spec.rb index 6904659f..7f317264 100644 --- a/spec/unit/intercom/request_spec.rb +++ b/spec/unit/intercom/request_spec.rb @@ -37,7 +37,7 @@ req = Intercom::Request.get(uri, "") req.handle_rate_limit=true req.expects(:sleep).times(3).with(any_parameters) - req.execute(target_base_url=uri, username: "ted", secret: "") + req.execute(uri, username: "ted", secret: "") }.must_raise(Intercom::RateLimitExceeded) end @@ -48,7 +48,7 @@ req = Intercom::Request.get(uri, "") req.handle_rate_limit=true req.expects(:sleep).never.with(any_parameters) - req.execute(target_base_url=uri, username: "ted", secret: "") + req.execute(uri, username: "ted", secret: "") end it 'should call sleep for rate limit error just once' do @@ -59,7 +59,7 @@ req = Intercom::Request.get(uri, "") req.handle_rate_limit=true req.expects(:sleep).with(any_parameters) - req.execute(target_base_url=uri, username: "ted", secret: "") + req.execute(uri, username: "ted", secret: "") end it 'should not sleep if rate limit reset time has passed' do @@ -70,7 +70,7 @@ req = Intercom::Request.get(uri, "") req.handle_rate_limit=true req.expects(:sleep).never.with(any_parameters) - req.execute(target_base_url=uri, username: "ted", secret: "") + req.execute(uri, username: "ted", secret: "") end end @@ -82,7 +82,7 @@ stub_request(:put, uri).\ to_return(status: [409, "Resource Already Exists"], headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }, body: {type: "error.list", errors: [ code: "resource_conflict" ]}.to_json) req = Intercom::Request.put(uri, "") - expect { req.execute(target_base_url=uri, username: "ted", secret: "") }.must_raise(Intercom::ResourceNotUniqueError) + expect { req.execute(uri, username: "ted", secret: "") }.must_raise(Intercom::ResourceNotUniqueError) end it 'should raise ApiVersionInvalid error on intercom_version_invalid code' do @@ -92,6 +92,13 @@ req = Intercom::Request.put(uri, "") expect { req.execute(uri, username: "ted", secret: "") }.must_raise(Intercom::ApiVersionInvalid) end + + it 'should raise ResourceNotFound error on company_not_found code' do + stub_request(:put, uri).\ + to_return(status: [404, "Not Found"], headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }, body: {type: "error.list", errors: [ code: "company_not_found" ]}.to_json) + req = Intercom::Request.put(uri, "") + expect { req.execute(uri, username: "ted", secret: "") }.must_raise(Intercom::ResourceNotFound) + end end it 'parse_body returns nil if decoded_body is nil' do From 8042ec30c22102ddf70965040c3d2bbcf952cfa1 Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Thu, 3 Oct 2019 13:57:37 +0100 Subject: [PATCH 175/245] Jonny/bump version (#486) * Bump intercom to 3.8.1 * Include changes and readme --- README.md | 3 ++- changes.txt | 3 +++ lib/intercom/version.rb | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 61b106db..573a657c 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,12 @@ This version of the gem is compatible with `Ruby 2.1` and above. ## Installation + gem install intercom Using bundler: - gem 'intercom', '~> 3.8.0' + gem 'intercom', '~> 3.8.1' ## Basic Usage diff --git a/changes.txt b/changes.txt index 04385914..9166f71b 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.8.1 +Added error handling for company_not_found + 3.8.0 Add support for Customer Search (currently in Unstable API Version) https://developers.intercom.com/intercom-api-reference/v0/reference#customers diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 57047f7c..0d1df30a 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.8.0" + VERSION = "3.8.1" end From c23878fea4138ddad9cb5d8501528adbab584a44 Mon Sep 17 00:00:00 2001 From: Jason Ball Date: Wed, 9 Oct 2019 21:21:10 +1100 Subject: [PATCH 176/245] add teams endpoint (#488) * add teams endpoint * fix newlines --- README.md | 9 +++++++++ lib/intercom.rb | 2 ++ lib/intercom/client.rb | 4 ++++ lib/intercom/service/team.rb | 17 +++++++++++++++++ lib/intercom/team.rb | 7 +++++++ spec/spec_helper.rb | 29 +++++++++++++++++++++++++++++ spec/unit/intercom/team_spec.rb | 21 +++++++++++++++++++++ 7 files changed, 89 insertions(+) create mode 100644 lib/intercom/service/team.rb create mode 100644 lib/intercom/team.rb create mode 100644 spec/unit/intercom/team_spec.rb diff --git a/README.md b/README.md index 573a657c..ed6797f7 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ You can also use the [omniauth-intercom lib](https://github.com/intercom/omniaut Resources this API supports: https://api.intercom.io/users + https://api.intercom.io/teams https://api.intercom.io/contacts https://api.intercom.io/companies https://api.intercom.io/counts @@ -129,6 +130,14 @@ intercom.admins.find(id: admin_id) intercom.admins.all.each {|admin| puts admin.email } ``` +#### Teams +```ruby +# Find a team by id +intercom.teams.find(id: team_id) +# Iterate over all teams +intercom.teams.all.each {|team| puts team.name } +``` + #### Companies ```ruby # Add a user to one or more companies diff --git a/lib/intercom.rb b/lib/intercom.rb index 0277c208..524cb706 100644 --- a/lib/intercom.rb +++ b/lib/intercom.rb @@ -12,6 +12,7 @@ require 'intercom/service/subscription' require 'intercom/service/segment' require 'intercom/service/tag' +require 'intercom/service/team' require 'intercom/service/user' require 'intercom/service/visitor' require 'intercom/options' @@ -31,6 +32,7 @@ require "intercom/admin" require "intercom/request" require "intercom/subscription" +require "intercom/team" require "intercom/errors" require "intercom/visitor" require "json" diff --git a/lib/intercom/client.rb b/lib/intercom/client.rb index 00e2b02c..a7586798 100644 --- a/lib/intercom/client.rb +++ b/lib/intercom/client.rb @@ -95,6 +95,10 @@ def tags Intercom::Service::Tag.new(self) end + def teams + Intercom::Service::Team.new(self) + end + def users Intercom::Service::User.new(self) end diff --git a/lib/intercom/service/team.rb b/lib/intercom/service/team.rb new file mode 100644 index 00000000..f5dea932 --- /dev/null +++ b/lib/intercom/service/team.rb @@ -0,0 +1,17 @@ +require 'intercom/service/base_service' +require 'intercom/api_operations/list' +require 'intercom/api_operations/find' + +module Intercom + module Service + class Team < BaseService + include ApiOperations::List + include ApiOperations::Find + + def collection_class + Intercom::Team + end + + end + end +end diff --git a/lib/intercom/team.rb b/lib/intercom/team.rb new file mode 100644 index 00000000..d476ae8b --- /dev/null +++ b/lib/intercom/team.rb @@ -0,0 +1,7 @@ +require 'intercom/traits/api_resource' + +module Intercom + class Team + include Traits::ApiResource + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5be39bc8..0381244b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -145,6 +145,35 @@ def test_admin } end +def test_team_list + { + "type"=> "team.list", + "teams" => [ + { + "type"=> "team", + "id"=> "2744328", + "name"=> "the_a_team", + "admin_ids"=> [646303, 814860], + }, + { + "type"=> "team", + "id"=> "814865", + "name"=> "BA_App", + "admin_ids" => [492881, 1195856] + }, + ] + } +end + +def test_team + { + "type" => "team", + "id" => "2744328", + "name" => "the_a_team", + "admin_ids" => [646303, 814860] + } +end + def test_company { "type" => "company", diff --git a/spec/unit/intercom/team_spec.rb b/spec/unit/intercom/team_spec.rb new file mode 100644 index 00000000..1a5fc126 --- /dev/null +++ b/spec/unit/intercom/team_spec.rb @@ -0,0 +1,21 @@ +require "spec_helper" + +describe "Intercom::Team" do + let (:client) { Intercom::Client.new(token: 'token') } + + it "returns a CollectionProxy for all without making any requests" do + client.expects(:execute_request).never + all = client.teams.all + all.must_be_instance_of(Intercom::ClientCollectionProxy) + end + + it 'gets an team list' do + client.expects(:get).with("/teams", {}).returns(test_team_list) + client.teams.all.each { |t| } + end + + it "gets an team" do + client.expects(:get).with("/teams/1234", {}).returns(test_team) + client.teams.find(:id => "1234") + end +end From 3f3a25ab06bf7fb9c9f272598ed45d2a1538eabe Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Wed, 9 Oct 2019 11:44:22 +0100 Subject: [PATCH 177/245] Jonny/bump minor version (#489) --- README.md | 2 +- changes.txt | 3 +++ lib/intercom/version.rb | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ed6797f7..38970d00 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.8.1' + gem 'intercom', '~> 3.9.0' ## Basic Usage diff --git a/changes.txt b/changes.txt index 9166f71b..4dfc4695 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.9.0 +Added Teams endpoint functionality + 3.8.1 Added error handling for company_not_found diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 0d1df30a..f25ca676 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.8.1" + VERSION = "3.9.0" end From cce0859d7961d34919aa63b227a022b761391734 Mon Sep 17 00:00:00 2001 From: Matthew Murray Date: Mon, 4 Nov 2019 14:18:20 +0000 Subject: [PATCH 178/245] Add code block + docs link to README file --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 38970d00..ef919789 100644 --- a/README.md +++ b/README.md @@ -470,9 +470,13 @@ intercom.contacts.scroll.each { |lead| puts lead.id} #### Customers +Our Customer API is a central place for all the information on your customers, whether they're users or leads. More detail in our API documentation on [Customers](https://developers.intercom.com/intercom-api-reference/v0/reference#customers). + +```ruby # Search for customers customers = intercom.customers.search(query: { "field": "name", "operator": "=", "value": "Alice"}, per_page: 50, sort_field: "name", sort_order: "ascending") customers.each { |customer| p customer.name } +``` #### Counts From dc7d65b872cd574077c2d7f5ada6010ea00cf222 Mon Sep 17 00:00:00 2001 From: Adam Steel Date: Mon, 4 Nov 2019 08:04:17 -0700 Subject: [PATCH 179/245] [bugfix] handle unexpected response bodies (#492) * [refactor] add "m" for easier testing * [refactor] address minitest deprecations in request_spec.rb * [refactor] remove unnecessary raise on failure If the JSON parsing errors, `parsed_body` is `nil` and the following line raises the error. * [refactor] DRY code formatting in if/else * [refactor] allow access to pry in tests * [refactor] clarify public methods in Request This will help us better reason about which methods should have tests, and which methods other objects are depending on specific behavior from. * [refactor] clean up request specs Done as a step toward fixing a bug in `#parse_body` - Moves client test to client_spec.rb - Replaces parse_body tests with higher level tests - Makes parse_body private - Organizes and cleans up the request_spec file generally * [refactor] simplify response body parsing `parse_body` was doing three things: - checking for response errors - handling parse errors - actually parsing the response body We get some cleaner code by separating the parsing from the response state checking. * [bugfix] raise an error on unexpected response Some responses do not have an expected error code and have non-JSON bodies that fail to parse. The unexpected `nil` caused runtime errors. This fix raises an error on those responses with more information that will allow developers to find a path forward, either by more fixes to this gem or by handling the new error in their own code. I'm giving this version bump a patch level bump because it looks like all existing functionality is preserved. We will now simply get better information instead of `undefined method '[]' for nil:NilClass`. Addresses https://github.com/intercom/intercom-ruby/issues/491 * [refactor] inline private methods in Request. --- README.md | 21 ++++ intercom.gemspec | 1 + lib/intercom/errors.rb | 3 + lib/intercom/request.rb | 188 +++++++++++++++------------- lib/intercom/version.rb | 2 +- spec/spec_helper.rb | 1 + spec/unit/intercom/client_spec.rb | 12 +- spec/unit/intercom/request_spec.rb | 191 +++++++++++++++++------------ 8 files changed, 257 insertions(+), 162 deletions(-) diff --git a/README.md b/README.md index ef919789..a3d8541f 100644 --- a/README.md +++ b/README.md @@ -560,3 +560,24 @@ intercom = Intercom::Client.new(token: ENV['AT'], handle_rate_limit: true) - **Send coherent history**. Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before sending them to us. + +### Development + +#### Running tests + +```bash +# all tests +bundle exec spec + +# unit tests +bundle exec spec:unit + +# integration tests +bundle exec spec:integration + +# single test file +bundle exec m spec/unit/intercom/job_spec.rb + +# single test +bundle exec m spec/unit/intercom/job_spec.rb:49 +``` diff --git a/intercom.gemspec b/intercom.gemspec index d797f951..a6878d55 100644 --- a/intercom.gemspec +++ b/intercom.gemspec @@ -19,6 +19,7 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_development_dependency 'minitest', '~> 5.4' + spec.add_development_dependency "m", "~> 1.5.0" spec.add_development_dependency 'rake', '~> 10.3' spec.add_development_dependency 'mocha', '~> 1.0' spec.add_development_dependency "fakeweb", ["~> 1.3"] diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index 567160b8..6e968eb6 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -63,6 +63,9 @@ class BadRequestError < IntercomError; end # Raised when you have exceeded the API rate limit class RateLimitExceeded < IntercomError; end + # Raised when some attribute of the response cannot be handled + class UnexpectedResponseError < IntercomError; end + # Raised when the request throws an error not accounted for class UnexpectedError < IntercomError; end diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index d9f8a107..64f8114e 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -3,64 +3,48 @@ module Intercom class Request - attr_accessor :path, :net_http_method, :rate_limit_details, :handle_rate_limit - - def initialize(path, net_http_method) - self.path = path - self.net_http_method = net_http_method - self.handle_rate_limit = false - end - - def set_common_headers(method, base_uri) - method.add_field('AcceptEncoding', 'gzip, deflate') - end - - def set_basic_auth(method, username, secret) - method.basic_auth(CGI.unescape(username), CGI.unescape(secret)) - end + class << self + def get(path, params) + new(path, Net::HTTP::Get.new(append_query_string_to_url(path, params), default_headers)) + end - def set_api_version(method, api_version) - method.add_field('Intercom-Version', api_version) - end + def post(path, form_data) + new(path, method_with_body(Net::HTTP::Post, path, form_data)) + end - def self.get(path, params) - new(path, Net::HTTP::Get.new(append_query_string_to_url(path, params), default_headers)) - end + def delete(path, params) + new(path, method_with_body(Net::HTTP::Delete, path, params)) + end - def self.post(path, form_data) - new(path, method_with_body(Net::HTTP::Post, path, form_data)) - end + def put(path, form_data) + new(path, method_with_body(Net::HTTP::Put, path, form_data)) + end - def self.delete(path, params) - new(path, method_with_body(Net::HTTP::Delete, path, params)) - end + private def method_with_body(http_method, path, params) + request = http_method.send(:new, path, default_headers) + request.body = params.to_json + request["Content-Type"] = "application/json" + request + end - def self.put(path, form_data) - new(path, method_with_body(Net::HTTP::Put, path, form_data)) - end + private def default_headers + {'Accept-Encoding' => 'gzip, deflate', 'Accept' => 'application/vnd.intercom.3+json', 'User-Agent' => "Intercom-Ruby/#{Intercom::VERSION}"} + end - def self.method_with_body(http_method, path, params) - request = http_method.send(:new, path, default_headers) - request.body = params.to_json - request["Content-Type"] = "application/json" - request + private def append_query_string_to_url(url, params) + return url if params.empty? + query_string = params.map { |k, v| "#{k.to_s}=#{CGI::escape(v.to_s)}" }.join('&') + url + "?#{query_string}" + end end - def self.default_headers - {'Accept-Encoding' => 'gzip, deflate', 'Accept' => 'application/vnd.intercom.3+json', 'User-Agent' => "Intercom-Ruby/#{Intercom::VERSION}"} + def initialize(path, net_http_method) + self.path = path + self.net_http_method = net_http_method + self.handle_rate_limit = false end - def client(uri, read_timeout:, open_timeout:) - net = Net::HTTP.new(uri.host, uri.port) - if uri.is_a?(URI::HTTPS) - net.use_ssl = true - net.verify_mode = OpenSSL::SSL::VERIFY_PEER - net.ca_file = File.join(File.dirname(__FILE__), '../data/cacert.pem') - end - net.read_timeout = read_timeout - net.open_timeout = open_timeout - net - end + attr_accessor :handle_rate_limit def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_timeout: 30, api_version: nil) retries = 3 @@ -72,10 +56,16 @@ def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_ client(base_uri, read_timeout: read_timeout, open_timeout: open_timeout).start do |http| begin response = http.request(net_http_method) + set_rate_limit_details(response) - decoded_body = decode_body(response) - parsed_body = parse_body(decoded_body, response) raise_errors_on_failure(response) + + parsed_body = extract_response_body(response) + + return nil if parsed_body.nil? + + raise_application_errors_on_failure(parsed_body, response.code.to_i) if parsed_body['type'] == 'error.list' + parsed_body rescue Intercom::RateLimitExceeded => e if @handle_rate_limit @@ -98,24 +88,51 @@ def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_ end end - def decode_body(response) - decode(response['content-encoding'], response.body) - end + attr_accessor :path, + :net_http_method, + :rate_limit_details - def parse_body(decoded_body, response) - parsed_body = nil - return parsed_body if decoded_body.nil? || decoded_body.strip.empty? - begin - parsed_body = JSON.parse(decoded_body) - rescue JSON::ParserError => _ - raise_errors_on_failure(response) + private :path, + :net_http_method, + :rate_limit_details + + private def client(uri, read_timeout:, open_timeout:) + net = Net::HTTP.new(uri.host, uri.port) + if uri.is_a?(URI::HTTPS) + net.use_ssl = true + net.verify_mode = OpenSSL::SSL::VERIFY_PEER + net.ca_file = File.join(File.dirname(__FILE__), '../data/cacert.pem') end - raise_errors_on_failure(response) if parsed_body.nil? - raise_application_errors_on_failure(parsed_body, response.code.to_i) if parsed_body['type'] == 'error.list' - parsed_body + net.read_timeout = read_timeout + net.open_timeout = open_timeout + net end - def set_rate_limit_details(response) + private def extract_response_body(response) + decoded_body = decode(response['content-encoding'], response.body) + + json_parse_response(decoded_body, response.code) + end + + private def decode(content_encoding, body) + return body if (!body) || body.empty? || content_encoding != 'gzip' + Zlib::GzipReader.new(StringIO.new(body)).read.force_encoding("utf-8") + end + + private def json_parse_response(str, code) + return nil if str.to_s.empty? + + JSON.parse(str) + rescue JSON::ParserError + msg = <<~MSG.gsub(/[[:space:]]+/, " ").strip # #squish from ActiveSuppor + Expected a JSON response body. Instead got '#{str}' + with status code '#{code}'. + MSG + + raise UnexpectedResponseError, msg + end + + private def set_rate_limit_details(response) rate_limit_details = {} rate_limit_details[:limit] = response['X-RateLimit-Limit'].to_i if response['X-RateLimit-Limit'] rate_limit_details[:remaining] = response['X-RateLimit-Remaining'].to_i if response['X-RateLimit-Remaining'] @@ -123,30 +140,39 @@ def set_rate_limit_details(response) @rate_limit_details = rate_limit_details end - def decode(content_encoding, body) - return body if (!body) || body.empty? || content_encoding != 'gzip' - Zlib::GzipReader.new(StringIO.new(body)).read.force_encoding("utf-8") + private def set_common_headers(method, base_uri) + method.add_field('AcceptEncoding', 'gzip, deflate') end - def raise_errors_on_failure(res) - if res.code.to_i.eql?(404) + private def set_basic_auth(method, username, secret) + method.basic_auth(CGI.unescape(username), CGI.unescape(secret)) + end + + private def set_api_version(method, api_version) + method.add_field('Intercom-Version', api_version) + end + + private def raise_errors_on_failure(res) + code = res.code.to_i + + if code == 404 raise Intercom::ResourceNotFound.new('Resource Not Found') - elsif res.code.to_i.eql?(401) + elsif code == 401 raise Intercom::AuthenticationError.new('Unauthorized') - elsif res.code.to_i.eql?(403) + elsif code == 403 raise Intercom::AuthenticationError.new('Forbidden') - elsif res.code.to_i.eql?(429) + elsif code == 429 raise Intercom::RateLimitExceeded.new('Rate Limit Exceeded') - elsif res.code.to_i.eql?(500) + elsif code == 500 raise Intercom::ServerError.new('Server Error') - elsif res.code.to_i.eql?(502) + elsif code == 502 raise Intercom::BadGatewayError.new('Bad Gateway Error') - elsif res.code.to_i.eql?(503) + elsif code == 503 raise Intercom::ServiceUnavailableError.new('Service Unavailable') end end - def raise_application_errors_on_failure(error_list_details, http_code) + private def raise_application_errors_on_failure(error_list_details, http_code) # Currently, we don't support multiple errors error_details = error_list_details['errors'].first error_code = error_details['type'] || error_details['code'] @@ -198,18 +224,12 @@ def raise_application_errors_on_failure(error_list_details, http_code) end end - def message_for_unexpected_error_with_type(error_details, parsed_http_code) + private def message_for_unexpected_error_with_type(error_details, parsed_http_code) "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." end - def message_for_unexpected_error_without_type(error_details, parsed_http_code) + private def message_for_unexpected_error_without_type(error_details, parsed_http_code) "An unexpected error occured. It occurred with the message: #{error_details['message']} and http_code: '#{parsed_http_code}'. Please contact Intercom with these details." end - - def self.append_query_string_to_url(url, params) - return url if params.empty? - query_string = params.map { |k, v| "#{k.to_s}=#{CGI::escape(v.to_s)}" }.join('&') - url + "?#{query_string}" - end end end diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index f25ca676..c60c7c04 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.9.0" + VERSION = "3.9.1" end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0381244b..db916bec 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,6 +3,7 @@ require 'mocha/setup' require 'webmock' require 'time' +require 'pry' include WebMock::API def test_customer(email="bob@example.com") diff --git a/spec/unit/intercom/client_spec.rb b/spec/unit/intercom/client_spec.rb index ace7be05..fcf3e87c 100644 --- a/spec/unit/intercom/client_spec.rb +++ b/spec/unit/intercom/client_spec.rb @@ -4,12 +4,22 @@ module Intercom describe Client do let(:app_id) { 'myappid' } let(:api_key) { 'myapikey' } - let(:client) { Client.new(app_id: app_id, api_key: api_key) } + let(:client) do + Client.new( + app_id: app_id, + api_key: api_key, + handle_rate_limit: true + ) + end it 'should set the base url' do client.base_url.must_equal('https://api.intercom.io') end + it 'should have handle_rate_limit set' do + _(client.handle_rate_limit).must_equal(true) + end + it 'should be able to change the base url' do prev = client.options(Intercom::Client.set_base_url('https://mymockintercom.io')) client.base_url.must_equal('https://mymockintercom.io') diff --git a/spec/unit/intercom/request_spec.rb b/spec/unit/intercom/request_spec.rb index 7f317264..1c7438f4 100644 --- a/spec/unit/intercom/request_spec.rb +++ b/spec/unit/intercom/request_spec.rb @@ -3,107 +3,146 @@ WebMock.enable! -describe 'Intercom::Request' do - it 'raises an error when a html error page rendered' do - response = OpenStruct.new(:code => 500) - req = Intercom::Request.new('path/', 'GET') - proc {req.parse_body('something', response)}.must_raise(Intercom::ServerError) +describe 'Intercom::Request', '#execute' do + let(:uri) {"https://api.intercom.io/users"} + let(:req) { Intercom::Request.get(uri, {}) } + let(:default_body) { { data: "test" }.to_json } + + def execute! + req.execute(uri, username: 'ted', secret: '') end - it 'raises a RateLimitExceeded error when the response code is 429' do - response = OpenStruct.new(:code => 429) - req = Intercom::Request.new('path/', 'GET') - proc {req.parse_body('something', response)}.must_raise(Intercom::RateLimitExceeded) + it 'should call sleep for rate limit error three times and raise a rate limit error otherwise' do + stub_request(:any, uri).to_return( + status: [429, "Too Many Requests"], + headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }, + body: default_body + ) + + req.handle_rate_limit=true + + req.expects(:sleep).times(3).with(any_parameters) + + expect { execute! }.must_raise(Intercom::RateLimitExceeded) end - it 'parse_body raises an error if the decoded_body is "null"' do - response = OpenStruct.new(:code => 500) - req = Intercom::Request.new('path/', 'GET') - proc { req.parse_body('null', response)}.must_raise(Intercom::ServerError) + it 'should not call sleep for rate limit error' do + stub_request(:any, uri).to_return( + status: [200, "OK"], + headers: { 'X-RateLimit-Reset' => Time.now.utc + 10 }, + body: default_body + ) + + req.handle_rate_limit=true + req.expects(:sleep).never.with(any_parameters) + + execute! end - describe 'Intercom::Client' do - let(:client) { Intercom::Client.new(token: 'foo', handle_rate_limit: true) } - let(:uri) {"https://api.intercom.io/users"} + it 'should call sleep for rate limit error just once' do + stub_request(:any, uri).to_return( + status: [429, "Too Many Requests"], + headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }, + ).then.to_return(status: [200, "OK"], body: default_body) - it 'should have handle_rate_limit set' do - client.handle_rate_limit.must_equal(true) - end + req.handle_rate_limit=true + req.expects(:sleep).with(any_parameters) + + execute! + end + + it 'should not sleep if rate limit reset time has passed' do + stub_request(:any, uri).to_return( + status: [429, "Too Many Requests"], + headers: { 'X-RateLimit-Reset' => Time.parse("February 25 2010").utc.to_i.to_s }, + body: default_body + ).then.to_return(status: [200, "OK"], body: default_body) + + req.handle_rate_limit=true + req.expects(:sleep).never.with(any_parameters) + + execute! + end + + it 'handles an empty body gracefully' do + stub_request(:any, uri).to_return( + status: 200, + body: nil + ) - it 'should call sleep for rate limit error three times and raise a rate limit error otherwise' do - expect { - stub_request(:any, uri).\ - to_return(status: [429, "Too Many Requests"], headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }) - req = Intercom::Request.get(uri, "") - req.handle_rate_limit=true - req.expects(:sleep).times(3).with(any_parameters) - req.execute(uri, username: "ted", secret: "") - }.must_raise(Intercom::RateLimitExceeded) + assert_nil(execute!) + end + + describe 'HTTP error handling' do + it 'raises an error when the response is successful but the body is not JSON' do + stub_request(:any, uri).to_return( + status: 200, + body: 'something' + ) + + expect { execute! }.must_raise(Intercom::UnexpectedResponseError) end - it 'should not call sleep for rate limit error' do - # Use webmock to mock the HTTP request - stub_request(:any, uri).\ - to_return(status: [200, "OK"], headers: { 'X-RateLimit-Reset' => Time.now.utc + 10 }) - req = Intercom::Request.get(uri, "") - req.handle_rate_limit=true - req.expects(:sleep).never.with(any_parameters) - req.execute(uri, username: "ted", secret: "") + it 'raises an error when an html error page rendered' do + stub_request(:any, uri).to_return( + status: 500, + body: 'something' + ) + + expect { execute! }.must_raise(Intercom::ServerError) end - it 'should call sleep for rate limit error just once' do - # Use webmock to mock the HTTP request - stub_request(:any, uri).\ - to_return(status: [429, "Too Many Requests"], headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }).\ - then.to_return(status: [200, "OK"]) - req = Intercom::Request.get(uri, "") - req.handle_rate_limit=true - req.expects(:sleep).with(any_parameters) - req.execute(uri, username: "ted", secret: "") + it 'raises an error if the decoded_body is "null"' do + stub_request(:any, uri).to_return( + status: 500, + body: 'null' + ) + + expect { execute! }.must_raise(Intercom::ServerError) end - it 'should not sleep if rate limit reset time has passed' do - # Use webmock to mock the HTTP request - stub_request(:any, uri).\ - to_return(status: [429, "Too Many Requests"], headers: { 'X-RateLimit-Reset' => Time.parse("February 25 2010").utc.to_i.to_s }).\ - then.to_return(status: [200, "OK"]) - req = Intercom::Request.get(uri, "") - req.handle_rate_limit=true - req.expects(:sleep).never.with(any_parameters) - req.execute(uri, username: "ted", secret: "") + it 'raises a RateLimitExceeded error when the response code is 429' do + stub_request(:any, uri).to_return( + status: 429, + body: 'null' + ) + + expect { execute! }.must_raise(Intercom::RateLimitExceeded) end end - - describe "Application errors on failure" do + describe "application error handling" do let(:uri) {"https://api.intercom.io/conversations/reply"} + let(:req) { Intercom::Request.put(uri, {}) } + it 'should raise ResourceNotUniqueError error on resource_conflict code' do - # Use webmock to mock the HTTP request - stub_request(:put, uri).\ - to_return(status: [409, "Resource Already Exists"], headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }, body: {type: "error.list", errors: [ code: "resource_conflict" ]}.to_json) - req = Intercom::Request.put(uri, "") - expect { req.execute(uri, username: "ted", secret: "") }.must_raise(Intercom::ResourceNotUniqueError) + stub_request(:put, uri).to_return( + status: [409, "Resource Already Exists"], + headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }, + body: { type: "error.list", errors: [ code: "resource_conflict" ] }.to_json + ) + + expect { execute! }.must_raise(Intercom::ResourceNotUniqueError) end it 'should raise ApiVersionInvalid error on intercom_version_invalid code' do - # Use webmock to mock the HTTP request - stub_request(:put, uri).\ - to_return(status: [400, "Bad Request"], headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }, body: {type: "error.list", errors: [ code: "intercom_version_invalid" ]}.to_json) - req = Intercom::Request.put(uri, "") - expect { req.execute(uri, username: "ted", secret: "") }.must_raise(Intercom::ApiVersionInvalid) + stub_request(:put, uri).to_return( + status: [400, "Bad Request"], + headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }, + body: { type: "error.list", errors: [ code: "intercom_version_invalid" ] }.to_json + ) + + expect { execute! }.must_raise(Intercom::ApiVersionInvalid) end it 'should raise ResourceNotFound error on company_not_found code' do - stub_request(:put, uri).\ - to_return(status: [404, "Not Found"], headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }, body: {type: "error.list", errors: [ code: "company_not_found" ]}.to_json) - req = Intercom::Request.put(uri, "") - expect { req.execute(uri, username: "ted", secret: "") }.must_raise(Intercom::ResourceNotFound) - end - end + stub_request(:put, uri).to_return( + status: [404, "Not Found"], + headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }, + body: { type: "error.list", errors: [ code: "company_not_found" ] }.to_json + ) - it 'parse_body returns nil if decoded_body is nil' do - response = OpenStruct.new(:code => 500) - req = Intercom::Request.new('path/', 'GET') - assert_nil(req.parse_body(nil, response)) + expect { execute! }.must_raise(Intercom::ResourceNotFound) + end end end From 0a07bc95feeb6e02d61a3b90037fba338862693e Mon Sep 17 00:00:00 2001 From: Adam Steel Date: Mon, 4 Nov 2019 07:19:28 -0700 Subject: [PATCH 180/245] [chore] test against each major supported Ruby version This adds a nice guarantee that methods used will be reliable for any version of Ruby gem users may reasonably be using. --- .circleci/config.yml | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 695cc363..23fcf9e1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,12 +1,35 @@ version: 2 jobs: - build: + "Test against Ruby 2.4": docker: - - image: circleci/ruby:2.4.6 - + - image: circleci/ruby:2.4.9 + working_directory: ~/intercom-ruby + steps: + - checkout + - run: bundle install + - run: bundle exec rake + "Test against Ruby 2.5": + docker: + - image: circleci/ruby:2.5.7 working_directory: ~/intercom-ruby - steps: - checkout - run: bundle install - run: bundle exec rake + "Test against Ruby 2.6": + docker: + - image: circleci/ruby:2.6.5 + working_directory: ~/intercom-ruby + steps: + - checkout + - run: bundle install + - run: bundle exec rake + +workflows: + version: 2 + build_and_test: + jobs: + - "Test against Ruby 2.4" + - "Test against Ruby 2.5" + - "Test against Ruby 2.6" + From 92f20320d805e9ab9be42f57450b5f88733a6c58 Mon Sep 17 00:00:00 2001 From: Jonny Date: Tue, 5 Nov 2019 10:11:12 +0000 Subject: [PATCH 181/245] Bump intercom to 3.9.2 --- README.md | 2 +- changes.txt | 6 ++++++ lib/intercom/version.rb | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a3d8541f..db4ffec3 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.9.0' + gem 'intercom', '~> 3.9.2' ## Basic Usage diff --git a/changes.txt b/changes.txt index 4dfc4695..6955e828 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,9 @@ +3.9.2 +Added error handling for malformed responses + +3.9.1 +Version skipped in error + 3.9.0 Added Teams endpoint functionality diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index c60c7c04..36b29e46 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.9.1" + VERSION = "3.9.2" end From 8fec48f5719025ba30b2726ba3954c9db94d1973 Mon Sep 17 00:00:00 2001 From: Jonny Date: Tue, 5 Nov 2019 13:50:45 +0000 Subject: [PATCH 182/245] Fix private method error and add test --- lib/intercom/request.rb | 3 +-- spec/unit/intercom/client_spec.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 64f8114e..40defd5f 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -93,8 +93,7 @@ def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_ :rate_limit_details private :path, - :net_http_method, - :rate_limit_details + :net_http_method private def client(uri, read_timeout:, open_timeout:) net = Net::HTTP.new(uri.host, uri.port) diff --git a/spec/unit/intercom/client_spec.rb b/spec/unit/intercom/client_spec.rb index fcf3e87c..2eec321e 100644 --- a/spec/unit/intercom/client_spec.rb +++ b/spec/unit/intercom/client_spec.rb @@ -73,6 +73,16 @@ module Intercom it 'raises on empty api version' do proc { Client.new(app_id: app_id, api_key: api_key, api_version: '') }.must_raise MisconfiguredClientError end + + it "assigns works" do + stub_request(:any, "https://api.intercom.io/users?id=123").to_return( + status: [200, "OK"], + headers: { 'X-RateLimit-Reset' => Time.now.utc + 10 }, + body: { "test": "testing" }.to_json + ) + + client.get("/users", { id: "123" }) + end end describe 'OAuth clients' do From 65a82a28f6d92e04197aaf2368e594960a6c8196 Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Tue, 5 Nov 2019 15:01:37 +0000 Subject: [PATCH 183/245] Jonny/bump version once again (#500) --- README.md | 2 +- changes.txt | 3 +++ lib/intercom/version.rb | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index db4ffec3..fbc1fcdc 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.9.2' + gem 'intercom', '~> 3.9.3' ## Basic Usage diff --git a/changes.txt b/changes.txt index 6955e828..b7260e18 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.9.3 +Fix regression added in 3.9.2 + 3.9.2 Added error handling for malformed responses diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 36b29e46..88b85911 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.9.2" + VERSION = "3.9.3" end From e450f85dc7c4de7e8798054f1668e263dfe58ac7 Mon Sep 17 00:00:00 2001 From: Adam Steel Date: Mon, 11 Nov 2019 08:43:25 -0700 Subject: [PATCH 184/245] [feature] handle 504 response Default handling for unexpected response bodies ([here](https://github.com/intercom/intercom-ruby/pull/492)) has revealed an unhandled 504 response from intercom. Example message from UnexpectedResponseError: ``` Intercom::UnexpectedResponseError, Expected a JSON response body. Instead got ' 504 Gateway Time-out

504 Gateway Time-out

' with status code '504'. ``` --- lib/intercom/errors.rb | 3 +++ lib/intercom/request.rb | 2 ++ spec/unit/intercom/request_spec.rb | 9 +++++++++ 3 files changed, 14 insertions(+) diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index 6e968eb6..7af77b54 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -42,6 +42,9 @@ class ServerError < IntercomError; end # Raised when we have bad gateway errors. class BadGatewayError < IntercomError; end + # Raised when we have gateway timeout errors. + class GatewayTimeoutError < IntercomError; end + # Raised when we experience a socket read timeout class ServiceUnavailableError < IntercomError; end diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 40defd5f..fb49ebf9 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -168,6 +168,8 @@ def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_ raise Intercom::BadGatewayError.new('Bad Gateway Error') elsif code == 503 raise Intercom::ServiceUnavailableError.new('Service Unavailable') + elsif code == 504 + raise Intercom::GatewayTimeoutError.new('Gateway Timeout') end end diff --git a/spec/unit/intercom/request_spec.rb b/spec/unit/intercom/request_spec.rb index 1c7438f4..48556927 100644 --- a/spec/unit/intercom/request_spec.rb +++ b/spec/unit/intercom/request_spec.rb @@ -109,6 +109,15 @@ def execute! expect { execute! }.must_raise(Intercom::RateLimitExceeded) end + + it 'raises a GatewayTimeoutError error when the response code is 504' do + stub_request(:any, uri).to_return( + status: 504, + body: ' 504 Gateway Time-out

504 Gateway Time-out

' + ) + + expect { execute! }.must_raise(Intercom::GatewayTimeoutError) + end end describe "application error handling" do From b193d555093eb700d2c52dbe97f884b583349088 Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Tue, 12 Nov 2019 10:30:24 +0000 Subject: [PATCH 185/245] Bump intercom to 3.9.4 (#504) --- README.md | 2 +- changes.txt | 3 +++ lib/intercom/version.rb | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fbc1fcdc..dcf1e560 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.9.3' + gem 'intercom', '~> 3.9.4' ## Basic Usage diff --git a/changes.txt b/changes.txt index b7260e18..7a9147c3 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.9.4 +Add handling for Gateway Timeouts + 3.9.3 Fix regression added in 3.9.2 diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 88b85911..8aaf92fc 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.9.3" + VERSION = "3.9.4" end From 51ad11679583f39fe0ebbf65317e625a70660aee Mon Sep 17 00:00:00 2001 From: Gabriel Anca Date: Wed, 13 Nov 2019 14:36:43 +0000 Subject: [PATCH 186/245] Accept 'Unstable' API Version --- lib/intercom/client.rb | 2 +- spec/unit/intercom/client_spec.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/intercom/client.rb b/lib/intercom/client.rb index a7586798..01a576f2 100644 --- a/lib/intercom/client.rb +++ b/lib/intercom/client.rb @@ -136,7 +136,7 @@ def validate_credentials! def validate_api_version! error = MisconfiguredClientError.new("api_version must be either nil or a valid API version") - fail error if (@api_version && Gem::Version.new(@api_version) < Gem::Version.new('1.0')) + fail error if (@api_version && @api_version != 'Unstable' && Gem::Version.new(@api_version) < Gem::Version.new('1.0')) end def execute_request(request) diff --git a/spec/unit/intercom/client_spec.rb b/spec/unit/intercom/client_spec.rb index 2eec321e..47fe6416 100644 --- a/spec/unit/intercom/client_spec.rb +++ b/spec/unit/intercom/client_spec.rb @@ -66,6 +66,10 @@ module Intercom assert_nil(Client.new(app_id: app_id, api_key: api_key, api_version: nil).api_version) end + it 'allows api version to be Unstable' do + Client.new(app_id: app_id, api_key: api_key, api_version: 'Unstable').api_version.must_equal('Unstable') + end + it 'raises on invalid api version' do proc { Client.new(app_id: app_id, api_key: api_key, api_version: '0.2') }.must_raise MisconfiguredClientError end From d827e13ab5ad9831e68a35cb2d01b0f76477777d Mon Sep 17 00:00:00 2001 From: Jonathan O'Mahony Date: Thu, 14 Nov 2019 10:19:58 +0000 Subject: [PATCH 187/245] Bump version to support the unstable API version (#506) --- README.md | 2 +- changes.txt | 3 +++ lib/intercom/version.rb | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dcf1e560..f4b46699 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.9.4' + gem 'intercom', '~> 3.9.5' ## Basic Usage diff --git a/changes.txt b/changes.txt index 7a9147c3..bec3ec6d 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +3.9.5 +Add Unstable version support + 3.9.4 Add handling for Gateway Timeouts diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 8aaf92fc..99eb61c7 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.9.4" + VERSION = "3.9.5" end From 06d1aec6b4ca14960f694aa7c888139a2c5c7728 Mon Sep 17 00:00:00 2001 From: Matthew Murray Date: Wed, 29 Jan 2020 14:08:24 +0000 Subject: [PATCH 188/245] Release version 4.0 --- README.md | 453 +++--- Rakefile | 2 +- lib/intercom.rb | 49 +- lib/intercom/api_operations/archive.rb | 3 +- lib/intercom/api_operations/delete.rb | 16 + lib/intercom/api_operations/find.rb | 7 +- lib/intercom/api_operations/find_all.rb | 7 +- lib/intercom/api_operations/list.rb | 5 +- lib/intercom/api_operations/load.rb | 6 +- .../api_operations/nested_resource.rb | 70 + lib/intercom/api_operations/save.rb | 9 +- lib/intercom/api_operations/scroll.rb | 9 +- lib/intercom/api_operations/search.rb | 5 +- lib/intercom/base_collection_proxy.rb | 72 + lib/intercom/client.rb | 45 +- lib/intercom/client_collection_proxy.rb | 56 +- lib/intercom/company.rb | 8 + lib/intercom/contact.rb | 24 +- lib/intercom/conversation.rb | 4 + lib/intercom/customer.rb | 10 - lib/intercom/data_attribute.rb | 7 + .../deprecated_leads_collection_proxy.rb | 22 + lib/intercom/deprecated_resources.rb | 13 + lib/intercom/errors.rb | 3 + .../extended_api_operations/segments.rb | 4 +- lib/intercom/extended_api_operations/tags.rb | 4 +- lib/intercom/lead.rb | 21 + lib/intercom/lib/typed_json_deserializer.rb | 79 +- lib/intercom/note.rb | 4 + lib/intercom/request.rb | 70 +- lib/intercom/scroll_collection_proxy.rb | 71 +- lib/intercom/search_collection_proxy.rb | 95 +- lib/intercom/service/base_service.rb | 7 + lib/intercom/service/company.rb | 12 - lib/intercom/service/contact.rb | 31 +- lib/intercom/service/conversation.rb | 15 +- lib/intercom/service/customer.rb | 14 - lib/intercom/service/data_attribute.rb | 20 + lib/intercom/service/lead.rb | 41 + lib/intercom/service/note.rb | 12 +- lib/intercom/service/subscription.rb | 4 +- lib/intercom/service/tag.rb | 18 +- lib/intercom/service/visitor.rb | 25 +- lib/intercom/tag.rb | 4 + lib/intercom/traits/api_resource.rb | 45 +- lib/intercom/user.rb | 15 +- lib/intercom/utils.rb | 15 +- lib/intercom/visitor.rb | 2 - spec/spec_helper.rb | 1251 ++++++++++------- spec/unit/intercom/admin_spec.rb | 4 +- .../intercom/base_collection_proxy_spec.rb | 30 + .../intercom/client_collection_proxy_spec.rb | 82 +- spec/unit/intercom/client_spec.rb | 51 +- spec/unit/intercom/company_spec.rb | 28 +- spec/unit/intercom/contact_spec.rb | 322 ++++- spec/unit/intercom/conversation_spec.rb | 36 +- spec/unit/intercom/count_spec.rb | 8 +- spec/unit/intercom/data_attribute_spec.rb | 40 + .../deprecated_leads_collection_proxy_spec.rb | 17 + spec/unit/intercom/event_spec.rb | 20 +- spec/unit/intercom/job_spec.rb | 48 +- spec/unit/intercom/lead_spec.rb | 57 + spec/unit/intercom/lib/flat_store_spec.rb | 42 +- spec/unit/intercom/message_spec.rb | 2 +- spec/unit/intercom/note_spec.rb | 14 +- spec/unit/intercom/request_spec.rb | 2 +- .../intercom/scroll_collection_proxy_spec.rb | 79 +- .../intercom/search_collection_proxy_spec.rb | 60 +- spec/unit/intercom/segment_spec.rb | 4 +- spec/unit/intercom/subscription_spec.rb | 11 +- spec/unit/intercom/tag_spec.rb | 36 +- spec/unit/intercom/team_spec.rb | 4 +- .../unit/intercom/traits/api_resource_spec.rb | 104 +- spec/unit/intercom/user_spec.rb | 450 +++--- spec/unit/intercom/visitor_spec.rb | 49 + spec/unit/intercom/visitors_spec.rb | 61 - spec/unit/intercom_spec.rb | 8 +- 77 files changed, 2705 insertions(+), 1748 deletions(-) create mode 100644 lib/intercom/api_operations/delete.rb create mode 100644 lib/intercom/api_operations/nested_resource.rb create mode 100644 lib/intercom/base_collection_proxy.rb delete mode 100644 lib/intercom/customer.rb create mode 100644 lib/intercom/data_attribute.rb create mode 100644 lib/intercom/deprecated_leads_collection_proxy.rb create mode 100644 lib/intercom/deprecated_resources.rb create mode 100644 lib/intercom/lead.rb delete mode 100644 lib/intercom/service/customer.rb create mode 100644 lib/intercom/service/data_attribute.rb create mode 100644 lib/intercom/service/lead.rb create mode 100644 spec/unit/intercom/base_collection_proxy_spec.rb create mode 100644 spec/unit/intercom/data_attribute_spec.rb create mode 100644 spec/unit/intercom/deprecated_leads_collection_proxy_spec.rb create mode 100644 spec/unit/intercom/lead_spec.rb create mode 100644 spec/unit/intercom/visitor_spec.rb delete mode 100644 spec/unit/intercom/visitors_spec.rb diff --git a/README.md b/README.md index f4b46699..e7d5e630 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,11 @@ Ruby bindings for the Intercom API (https://developers.intercom.io/reference). [Gem Documentation](http://rubydoc.info/github/intercom/intercom-ruby/master/frames) -For generating Intercom javascript script tags for Rails, please see https://github.com/intercom/intercom-rails. +For generating Intercom JavaScript script tags for Rails, please see https://github.com/intercom/intercom-rails. ## Upgrading information -Version 3 of intercom-ruby is not backwards compatible with previous versions. - -Version 3 moves away from a global setup approach to the use of an Intercom Client. +Version 4 of intercom-ruby is not backwards compatible with previous versions. Please see our [migration guide](https://github.com/intercom/ruby_v4/wiki/Migration-guide-for-v4) for full details of breaking changes. This version of the gem is compatible with `Ruby 2.1` and above. @@ -23,7 +21,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 3.9.5' + gem 'intercom', '~> 4.0' ## Basic Usage @@ -38,7 +36,7 @@ intercom = Intercom::Client.new(token: 'my_token') ```ruby # With a versioned app: -intercom = Intercom::Client.new(token: 'my_token', api_version: '1.0') +intercom = Intercom::Client.new(token: 'my_token', api_version: '2.0') ``` If you are building a third party application you can get your access_tokens by [setting-up-oauth](https://developers.intercom.io/page/setting-up-oauth) for Intercom. @@ -48,159 +46,233 @@ You can also use the [omniauth-intercom lib](https://github.com/intercom/omniaut Resources this API supports: - https://api.intercom.io/users - https://api.intercom.io/teams https://api.intercom.io/contacts + https://api.intercom.io/visitors https://api.intercom.io/companies - https://api.intercom.io/counts + https://api.intercom.io/data_attributes + https://api.intercom.io/events https://api.intercom.io/tags https://api.intercom.io/notes https://api.intercom.io/segments - https://api.intercom.io/events https://api.intercom.io/conversations https://api.intercom.io/messages + https://api.intercom.io/admins + https://api.intercom.io/teams + https://api.intercom.io/counts https://api.intercom.io/subscriptions https://api.intercom.io/jobs + ### Examples -#### Users - +#### Contacts ```ruby -# Find user by email -user = intercom.users.find(email: "bob@example.com") -# Find user by user_id -user = intercom.users.find(user_id: "1") -# Find user by id -user = intercom.users.find(id: "1") -# Create a user -user = intercom.users.create(email: "bob@example.com", name: "Bob Smith", signed_up_at: Time.now.to_i) -# archive a user -user = intercom.users.find(id: "1") -archived_user = intercom.users.archive(user) -# request a hard delete for a user -(https://developers.intercom.com/intercom-api-reference/reference#delete-users) -user = intercom.users.find(id: "1") -deleted_user = intercom.users.request_hard_delete(user) -# Update custom_attributes for a user -user.custom_attributes["average_monthly_spend"] = 1234.56 -intercom.users.save(user) -# Perform incrementing -user.increment('karma') -intercom.users.save(user) -# Perform decrementing -user.decrement('karma', 5) -intercom.users.save(user) -# Iterate over all users -intercom.users.all.each {|user| puts %Q(#{user.email} - #{user.custom_attributes["average_monthly_spend"]}) } -intercom.users.all.map {|user| user.email } -# List your users create in the last two days -intercom.users.find_all(type: 'users', page: 1, per_page: 10, created_since: 2, order: :asc).to_a.each_with_index {|usr, i| puts "#{i+1}: #{usr.name}"}; -# Paginate through your list of users choosing how many to return per page (default and max is 50 per page) -intercom.users.find_all(type: 'users', page: 1, per_page: 10, order: :asc).to_a.each_with_index {|usr, i| puts "#{i+1}: #{usr.name}"} - -# Duplicate users? If you have duplicate users you can search for them via their email address. -# Note this feature is only available from version 1.1 of the API so you will need to switch to that version -# This will return multiple users if they have the same email address -usrs = intercom.users.find_all(type: 'users', email: 'myemail@example.com', page: 1, per_page: 10, order: :asc) -# This returns a user.list so you can access it via -usrs.to_a.each_with_index {|usr, i| puts "#{i+1}: #{usr.id}"}; - -# If you have over 10,000 users then you will need to use the scroll function to list your users -# otherwise you will encounter a page limit with list all your users -# You can use the scroll method to list all your users -intercom.users.scroll.each { |user| puts user.name} -# Alternatively you can use the scroll.next method to get 100 users with each request -result = intercom.users.scroll.next -# The result object then contains a records attributes that is an array of your user objects and it also contains a scroll_param which -# you can then use to request the next 100 users. Note that the scroll parameter will time out after one minute and you will need to -# make a new request -result.scroll_param -=> "0730e341-63ef-44da-ab9c-9113f886326d" -result = intercom.users.scroll.next("0730e341-63ef-44da-ab9c-9113f886326d"); -``` +# Create a contact with "lead" role +contact = intercom.contacts.create(email: "some_contact2@example.com", role: "lead") -#### Admins -```ruby -# Find access token owner (only with Personal Access Token and OAuth) -intercom.admins.me -# Find an admin by id -intercom.admins.find(id: admin_id) -# Iterate over all admins -intercom.admins.all.each {|admin| puts admin.email } +# Get a single contact using their intercom_id +intercom.contacts.find(id: contact.id) + +# Update a contact +contact.name = "New name" +intercom.contacts.save(contact) + +# Update a contact's role from "lead" to "user" +contact.role = "user" +intercom.contacts.save(contact) + +# Delete a contact permanently +intercom.contacts.delete(contact) + +# List all contacts +contacts = intercom.contacts.all +contacts.each { |contact| p contact.name } + +# Search for contacts by email +contacts = intercom.contacts.search( + "query": { + "field": 'email', + "operator": '=', + "value": 'some_contact@example.com' + } +) +contacts.each {|c| p c.email} +# For full detail on possible queries, please refer to the API documentation: +# https://developers.intercom.com/intercom-api-reference/reference + +# Merge a lead into an existing user +lead = intercom.contacts.create(email: "some_contact2@example.com", role: "lead") +intercom.contacts.merge(lead, intercom.contacts.find(id: "5db2e80ab1b92243d2188cfe")) + +# Add a tag to a contact +tag = intercom.tags.find(id: "123") +contact.add_tag(id: tag.id) + +# Remove a tag +contact.remove_tag(id: tag.id) + +# List tags for a contact +contact.tags.each {|t| p t.name} + +# Create a note on a contact +contact.create_note(body: "

Text for the note

") + +# List notes for a contact +contact.notes.each {|n| p n.body} + +# Add a contact to a company +company = intercom.companies.find(id: "123") +contact.add_company(id: company.id}) + +# Remove a contact from a company +contact.remove_company(id: company.id}) + +# List companies for a contact +contact.companies.each {|c| p c.name} ``` -#### Teams +#### Visitors ```ruby -# Find a team by id -intercom.teams.find(id: team_id) -# Iterate over all teams -intercom.teams.all.each {|team| puts team.name } +# Get and update a visitor +visitor = intercom.visitors.find(id: "5dd570e7b1b922452676af23") +visitor.name = "New name" +intercom.visitors.save(visitor) + +# Convert a visitor into a lead +intercom.visitors.convert(visitor) + +# Convert a visitor into a user +user = intercom.contacts.find(id: "5db2e7f5b1b92243d2188cb3") +intercom.visitors.convert(visitor, user) ``` #### Companies ```ruby -# Add a user to one or more companies -user = intercom.users.find(email: "bob@example.com") -user.companies = [{company_id: 6, name: "Intercom"}, {company_id: 9, name: "Test Company"}] -intercom.users.save(user) -# You can also pass custom attributes within a company as you do this -user.companies = [{id: 6, name: "Intercom", custom_attributes: {referral_source: "Google"} } ] -intercom.users.save(user) # Find a company by company_id company = intercom.companies.find(company_id: "44") + # Find a company by name company = intercom.companies.find(name: "Some company") + # Find a company by id company = intercom.companies.find(id: "41e66f0313708347cb0000d0") + # Update a company company.name = 'Updated company name' intercom.companies.save(company) + # Iterate over all companies intercom.companies.all.each {|company| puts %Q(#{company.name} - #{company.custom_attributes["referral_source"]}) } intercom.companies.all.map {|company| company.name } -# Get a list of users in a company by Intercom Company ID -intercom.companies.users_by_intercom_company_id(company.id) -# Get a list of users in a company by external company_id -intercom.companies.users_by_company_id(company.company_id) + # Get a large list of companies using scroll intercom.companies.scroll.each { |comp| puts comp.name} # Please see users scroll for more details of how to use scroll ``` +#### Data Attributes +Data Attributes are a type of metadata used to describe your customer and company models. These include standard and custom attributes. +```ruby +# Create a new custom data attribute +intercom.data_attributes.create({ name: "test_attribute", model: "contact", data_type: "string" }) + +# List all data attributes +attributes = intercom.data_attributes.all +attributes.each { |attribute| p attribute.name } + +# Update an attribute +attribute = intercom.data_attributes.all.first +attribute.label = "New label" +intercom.data_attributes.save(attribute) + +# Archive an attribute +attribute.archived = true +intercom.data_attributes.save(attribute) + +# Find all customer attributes including archived +customer_attributes_incl_archived = intercom.data_attributes.find_all({"model": "contact", "include_archived": true}) +customer_attributes_incl_archived.each { |attr| p attribute.name } +``` + +#### Events +```ruby +intercom.events.create( + event_name: "invited-friend", + created_at: Time.now.to_i, + email: user.email, + metadata: { + "invitee_email" => "pi@example.org", + invite_code: "ADDAFRIEND", + "found_date" => 12909364407 + } +) + +# Alternatively, use "user_id" in case your app allows multiple accounts having the same email +intercom.events.create( + event_name: "invited-friend", + created_at: Time.now.to_i, + user_id: user.uuid, +) + +# Retrieve event list for user with id:'123abc' + intercom.events.find_all("type" => "user", "intercom_user_id" => "123abc") +``` + +Metadata Objects support a few simple types that Intercom can present on your behalf + +```ruby +intercom.events.create( + event_name: "placed-order", + email: current_user.email, + created_at: 1403001013, + metadata: { + order_date: Time.now.to_i, + stripe_invoice: 'inv_3434343434', + order_number: { + value: '3434-3434', + url: 'https://example.org/orders/3434-3434' + }, + price: { + currency: 'usd', + amount: 2999 + } + } +) +``` + +The metadata key values in the example are treated as follows- +- order_date: a Date (key ends with '_date') +- stripe_invoice: The identifier of the Stripe invoice (has a 'stripe_invoice' key) +- order_number: a Rich Link (value contains 'url' and 'value' keys) +- price: An Amount in US Dollars (value contains 'amount' and 'currency' keys) + +*NB:* This version of the gem reserves the field name `type` in Event data. + #### Tags ```ruby -# Tag users -tag = intercom.tags.tag(name: 'blue', users: [{email: "test1@example.com"}]) -# Untag users -intercom.tags.untag(name: 'blue', users: [{user_id: "42ea2f1b93891f6a99000427"}]) # Iterate over all tags intercom.tags.all.each {|tag| "#{tag.id} - #{tag.name}" } intercom.tags.all.map {|tag| tag.name } + # Tag companies tag = intercom.tags.tag(name: 'blue', companies: [{company_id: "42ea2f1b93891f6a99000427"}]) ``` +#### Notes +```ruby +# Find a note by id +note = intercom.notes.find(id: "123") +``` + #### Segments ```ruby # Find a segment segment = intercom.segments.find(id: segment_id) + # Iterate over all segments intercom.segments.all.each {|segment| puts "id: #{segment.id} name: #{segment.name}"} ``` -#### Notes -```ruby -# Find a note by id -note = intercom.notes.find(id: note) -# Create a note for a user -note = intercom.notes.create(body: "

Text for the note

", email: 'joe@example.com') -# Iterate over all notes for a user via their email address -intercom.notes.find_all(email: 'joe@example.com').each {|note| puts note.body} -# Iterate over all notes for a user via their user_id -intercom.notes.find_all(user_id: '123').each {|note| puts note.body} -``` - #### Conversations ```ruby # Iterate over all conversations for your app @@ -234,11 +306,15 @@ conversation = intercom.conversations.find(id: '1') # INTERACTING WITH THE PARTS OF A CONVERSATION # Getting the subject of a part (only applies to email-based conversations) -conversation.rendered_message.subject +conversation.source.subject # Get the part_type of the first part -conversation.conversation_parts[0].part_type +conversation.conversation_parts.first.part_type # Get the body of the second part conversation.conversation_parts[1].body +# Get statistics related to the conversation +conversation.statistics["first_contact_reply_at"] +# Get information about the rating of a conversation +conversation.conversation_rating.rating # REPLYING TO CONVERSATIONS # User (identified by email) replies with a comment @@ -282,13 +358,47 @@ intercom.conversations.reply(id: conversation.id, type: 'admin', assignee_id: as # MARKING A CONVERSATION AS READ intercom.conversations.mark_read(conversation.id) + +# RUN ASSIGNMENT RULES +intercom.conversations.run_assignment_rules(conversation.id) + +# Search for conversations +# For full detail on possible queries, please refer to the API documentation: +# https://developers.intercom.com/intercom-api-reference/reference + +# Search for open conversations, getting 10 per page and sorting by the created_at date +conversations = intercom.conversations.search( + query: { + field: "open", + operator: "=", + value: true + }, + per_page: 10, + sort_field: "created_at", + sort_order: "descending" +) +conversations.each {|c| p c.id} + +# Tagging for conversations +tag = intercom.tags.find(id: "2") +conversation = intercom.conversations.find(id: "1") + +# An Admin ID is required to add or remove tag on a conversation +admin = intercom.admins.find(id: "1") + +# Add a tag to a conversation +conversation.add_tag(id: tag.id, admin_id: admin.id}) + +# Remove a tag from a conversation +conversation.remove_tag(id: tag.id, admin_id: admin.id}) + ``` #### Full loading of an embedded entity ```ruby -# Given a conversation with a partial user, load the full user. This can be +# Given a conversation with a partial contact, load the full contact. This can be # done for any entity -intercom.users.load(conversation.user) +intercom.contacts.load(conversation.contacts.first) ``` #### Sending messages @@ -357,125 +467,22 @@ intercom.messages.create({ }) ``` -#### Events -```ruby -intercom.events.create( - event_name: "invited-friend", - created_at: Time.now.to_i, - email: user.email, - metadata: { - "invitee_email" => "pi@example.org", - invite_code: "ADDAFRIEND", - "found_date" => 12909364407 - } -) - -# Alternatively, use "user_id" in case your app allows multiple accounts having the same email -intercom.events.create( - event_name: "invited-friend", - created_at: Time.now.to_i, - user_id: user.uuid, -) - -# Retrieve event list for user with id:'123abc' - intercom.events.find_all("type" => "user", "intercom_user_id" => "123abc") - -``` - -Metadata Objects support a few simple types that Intercom can present on your behalf - -```ruby -intercom.events.create( - event_name: "placed-order", - email: current_user.email, - created_at: 1403001013, - metadata: { - order_date: Time.now.to_i, - stripe_invoice: 'inv_3434343434', - order_number: { - value: '3434-3434', - url: 'https://example.org/orders/3434-3434' - }, - price: { - currency: 'usd', - amount: 2999 - } - } -) -``` - -The metadata key values in the example are treated as follows- -- order_date: a Date (key ends with '_date') -- stripe_invoice: The identifier of the Stripe invoice (has a 'stripe_invoice' key) -- order_number: a Rich Link (value contains 'url' and 'value' keys) -- price: An Amount in US Dollars (value contains 'amount' and 'currency' keys) - -*NB:* This version of the gem reserves the field name `type` in Event data. - -#### Contacts - -`Contacts` represent logged out users of your application. -Note that `contacts` are referred to as `leads` in the [Intercom](https://developers.intercom.com/intercom-api-reference/reference#leads) - +#### Admins ```ruby -# Create a contact -contact = intercom.contacts.create(email: "some_contact@example.com") - -# Update a contact (via create method) -# You can update a contact by calling the create method but you MUST provide an id or user_id -# If you just provide an email, for example, it will create a new contact -# See https://developers.intercom.com/intercom-api-reference/reference#update-lead for more detail -contact = intercom.contacts.create(email: "some_contact@example.com", id: "3be0398668071a6bc6850413", name:"update_contact") - -# Update a contact (via contact object) -contact.custom_attributes['foo'] = 'bar' -intercom.contacts.save(contact) - -# Find contacts by email -contacts = intercom.contacts.find_all(email: "some_contact@example.com") - -# Using find to search for contacts by email -contact_list = intercom.contacts.find(email: "some_contact@example.com") -# This returns a Contact object with type contact.list -# Note: Multiple contacts can be returned in this list if there are multiple matching contacts found -# #, -# @contacts= -# [{"type"=>"contact", -# "id"=>"5b7fd9b683681ac52274b9c7", -# "user_id"=>"05bc4d17-72cc-433e-88ae-0bf88db5d0e6", -# "anonymous"=>true, -# "email"=>"some_contact@example.com", -# ...}], -# @custom_attributes={}, -# @limited=false, -# @pages=#, @next=nil, @page=1, @per_page=50, @total_pages=1, @type="pages">, -# @total_count=1, -# @type="contact.list"> -# Access the contact's data -contact_list.contacts.first - -# Convert a contact into a user -contact = intercom.contacts.find(id: "536e564f316c83104c000020") -intercom.contacts.convert(contact, Intercom::User.new(email: email)) -# Using find with email will not work here. See https://github.com/intercom/intercom-ruby/issues/419 for more information - -# archive a contact -intercom.contacts.archive(contact) - -# Get a large list of contacts using scroll -intercom.contacts.scroll.each { |lead| puts lead.id} -# Please see users scroll for more details of how to use scroll +# Find access token owner (only with Personal Access Token and OAuth) +intercom.admins.me +# Find an admin by id +intercom.admins.find(id: admin_id) +# Iterate over all admins +intercom.admins.all.each {|admin| puts admin.email } ``` -#### Customers - -Our Customer API is a central place for all the information on your customers, whether they're users or leads. More detail in our API documentation on [Customers](https://developers.intercom.com/intercom-api-reference/v0/reference#customers). - +#### Teams ```ruby -# Search for customers -customers = intercom.customers.search(query: { "field": "name", "operator": "=", "value": "Alice"}, per_page: 50, sort_field: "name", sort_order: "ascending") -customers.each { |customer| p customer.name } +# Find a team by id +intercom.teams.find(id: team_id) +# Iterate over all teams +intercom.teams.all.each {|team| puts team.name } ``` #### Counts diff --git a/Rakefile b/Rakefile index 1f14f39b..c1b11b09 100644 --- a/Rakefile +++ b/Rakefile @@ -18,4 +18,4 @@ Rake::TestTask.new("spec:integration") do |spec| end task :spec => "spec:unit" -task :default => :spec \ No newline at end of file +task :default => :spec diff --git a/lib/intercom.rb b/lib/intercom.rb index 524cb706..f9587801 100644 --- a/lib/intercom.rb +++ b/lib/intercom.rb @@ -1,10 +1,11 @@ -require "intercom/version" +# frozen_string_literal: true + +require 'intercom/version' require 'intercom/service/admin' require 'intercom/service/company' require 'intercom/service/contact' require 'intercom/service/conversation' require 'intercom/service/count' -require 'intercom/service/customer' require 'intercom/service/event' require 'intercom/service/message' require 'intercom/service/note' @@ -13,29 +14,33 @@ require 'intercom/service/segment' require 'intercom/service/tag' require 'intercom/service/team' -require 'intercom/service/user' require 'intercom/service/visitor' +require 'intercom/service/user' +require 'intercom/service/lead' +require 'intercom/deprecated_resources.rb' require 'intercom/options' require 'intercom/client' -require "intercom/contact" -require "intercom/count" -require "intercom/customer" -require "intercom/user" -require "intercom/company" -require "intercom/note" -require "intercom/job" -require "intercom/tag" -require "intercom/segment" -require "intercom/event" -require "intercom/conversation" -require "intercom/message" -require "intercom/admin" -require "intercom/request" -require "intercom/subscription" -require "intercom/team" -require "intercom/errors" -require "intercom/visitor" -require "json" +require 'intercom/contact' +require 'intercom/user' +require 'intercom/lead' +require 'intercom/count' +require 'intercom/company' +require 'intercom/service/data_attribute' +require 'intercom/note' +require 'intercom/job' +require 'intercom/tag' +require 'intercom/segment' +require 'intercom/event' +require 'intercom/conversation' +require 'intercom/message' +require 'intercom/admin' +require 'intercom/request' +require 'intercom/subscription' +require 'intercom/team' +require 'intercom/errors' +require 'intercom/visitor' +require 'intercom/data_attribute' +require 'json' ## # Intercom is a customer relationship management and messaging tool for web app owners diff --git a/lib/intercom/api_operations/archive.rb b/lib/intercom/api_operations/archive.rb index 0e8db101..8e6de554 100644 --- a/lib/intercom/api_operations/archive.rb +++ b/lib/intercom/api_operations/archive.rb @@ -1,10 +1,11 @@ +# frozen_string_literal: true + require 'intercom/utils' module Intercom module ApiOperations module Archive def archive(object) - collection_name = Utils.resource_class_to_collection_name(collection_class) @client.delete("/#{collection_name}/#{object.id}", {}) object end diff --git a/lib/intercom/api_operations/delete.rb b/lib/intercom/api_operations/delete.rb new file mode 100644 index 00000000..bdbe6fab --- /dev/null +++ b/lib/intercom/api_operations/delete.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'intercom/utils' + +module Intercom + module ApiOperations + module Delete + def delete(object) + @client.delete("/#{collection_name}/#{object.id}", {}) + object + end + + alias_method 'archive', 'delete' + end + end +end diff --git a/lib/intercom/api_operations/find.rb b/lib/intercom/api_operations/find.rb index 9744062b..460adaeb 100644 --- a/lib/intercom/api_operations/find.rb +++ b/lib/intercom/api_operations/find.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'intercom/utils' module Intercom @@ -5,14 +7,15 @@ module ApiOperations module Find def find(params) raise BadRequestError, "#{self}#find takes a hash as its parameter but you supplied #{params.inspect}" unless params.is_a? Hash - collection_name = Utils.resource_class_to_collection_name(collection_class) + if params[:id] id = params.delete(:id) response = @client.get("/#{collection_name}/#{id}", params) else response = @client.get("/#{collection_name}", params) end - raise Intercom::HttpError.new('Http Error - No response entity returned') unless response + raise Intercom::HttpError, 'Http Error - No response entity returned' unless response + from_api(response) end end diff --git a/lib/intercom/api_operations/find_all.rb b/lib/intercom/api_operations/find_all.rb index fff8423f..bdecd23b 100644 --- a/lib/intercom/api_operations/find_all.rb +++ b/lib/intercom/api_operations/find_all.rb @@ -1,13 +1,14 @@ +# frozen_string_literal: true + require 'intercom/client_collection_proxy' require 'intercom/utils' module Intercom module ApiOperations module FindAll - def find_all(params) raise BadRequestError, "#find takes a hash as its parameter but you supplied #{params.inspect}" unless params.is_a? Hash - collection_name = Utils.resource_class_to_collection_name(collection_class) + finder_details = {} if params[:id] && !type_switched_finder?(params) finder_details[:url] = "/#{collection_name}/#{params[:id]}" @@ -16,7 +17,7 @@ def find_all(params) finder_details[:url] = "/#{collection_name}" finder_details[:params] = params end - collection_proxy_class.new(collection_name, finder_details: finder_details, client: @client) + collection_proxy_class.new(collection_name, collection_class, details: finder_details, client: @client) end private diff --git a/lib/intercom/api_operations/list.rb b/lib/intercom/api_operations/list.rb index c11fb19b..34c5927c 100644 --- a/lib/intercom/api_operations/list.rb +++ b/lib/intercom/api_operations/list.rb @@ -1,11 +1,14 @@ +# frozen_string_literal: true + require 'intercom/client_collection_proxy' +require 'intercom/base_collection_proxy' require 'intercom/utils' module Intercom module ApiOperations module List def all - ClientCollectionProxy.new(Utils.resource_class_to_collection_name(collection_class), client: @client) + collection_proxy_class.new(collection_name, collection_class, client: @client) end end end diff --git a/lib/intercom/api_operations/load.rb b/lib/intercom/api_operations/load.rb index 6d25a152..8f91dc99 100644 --- a/lib/intercom/api_operations/load.rb +++ b/lib/intercom/api_operations/load.rb @@ -1,16 +1,18 @@ +# frozen_string_literal: true + require 'intercom/utils' module Intercom module ApiOperations module Load def load(object) - collection_name = Utils.resource_class_to_collection_name(collection_class) if object.id response = @client.get("/#{collection_name}/#{object.id}", {}) else raise "Cannot load #{collection_class} as it does not have a valid id." end - raise Intercom::HttpError.new('Http Error - No response entity returned') unless response + raise Intercom::HttpError, 'Http Error - No response entity returned' unless response + object.from_response(response) end end diff --git a/lib/intercom/api_operations/nested_resource.rb b/lib/intercom/api_operations/nested_resource.rb new file mode 100644 index 00000000..3dedef7f --- /dev/null +++ b/lib/intercom/api_operations/nested_resource.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Intercom + module ApiOperations + module NestedResource + module ClassMethods + def nested_resource_methods(resource, + path: nil, + operations: nil, + resource_plural: nil) + resource_plural ||= Utils.pluralize(resource.to_s) + path ||= resource_plural + raise ArgumentError, 'operations array required' if operations.nil? + + resource_url_method = :"#{resource_plural}_url" + + resource_name = Utils.resource_class_to_collection_name(self) + + define_method(resource_url_method.to_sym) do |id, nested_id = nil| + url = "/#{resource_name}/#{id}/#{path}" + url += "/#{nested_id}" unless nested_id.nil? + url + end + + operations.each do |operation| + case operation + when :create + define_method(:"create_#{resource}") do |params| + url = send(resource_url_method, self.id) + response = client.post(url, params) + raise_no_response_error unless response + self.class.from_api(response) + end + when :add + define_method(:"add_#{resource}") do |params| + url = send(resource_url_method, self.id) + response = client.post(url, params) + raise_no_response_error unless response + self.class.from_api(response) + end + when :delete + define_method(:"remove_#{resource}") do |params| + url = send(resource_url_method, self.id, params[:id]) + response = client.delete(url, params) + raise_no_response_error unless response + self.class.from_api(response) + end + when :list + define_method(resource_plural.to_sym) do + url = send(resource_url_method, self.id) + resource_class = Utils.constantize_resource_name(resource.to_s) + resource_class.collection_proxy_class.new(resource_plural, resource_class, details: { url: url }, client: client) + end + else + raise ArgumentError, "Unknown operation: #{operation.inspect}" + end + end + end + end + + def self.included(base) + base.extend(ClassMethods) + end + + private def raise_no_response_error + raise Intercom::HttpError, 'Http Error - No response entity returned' + end + end + end +end diff --git a/lib/intercom/api_operations/save.rb b/lib/intercom/api_operations/save.rb index f90a9a7c..caef1c94 100644 --- a/lib/intercom/api_operations/save.rb +++ b/lib/intercom/api_operations/save.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'intercom/utils' require 'ext/sliceable_hash' @@ -8,8 +10,8 @@ module Save private_constant :PARAMS_NOT_PROVIDED def create(params = PARAMS_NOT_PROVIDED) - if collection_class.ancestors.include?(Intercom::Contact) && params == PARAMS_NOT_PROVIDED - params = Hash.new + if collection_class.ancestors.include?(Intercom::Lead) && params == PARAMS_NOT_PROVIDED + params = {} elsif params == PARAMS_NOT_PROVIDED raise ArgumentError, '.create requires 1 parameter' end @@ -20,7 +22,6 @@ def create(params = PARAMS_NOT_PROVIDED) end def save(object) - collection_name = Utils.resource_class_to_collection_name(collection_class) if id_present?(object) && !posted_updates?(object) response = @client.put("/#{collection_name}/#{object.id}", object.to_submittable_hash) else @@ -30,7 +31,7 @@ def save(object) end def identity_hash(object) - object.respond_to?(:identity_vars) ? SliceableHash.new(object.to_hash).slice(*(object.identity_vars.map(&:to_s))) : {} + object.respond_to?(:identity_vars) ? SliceableHash.new(object.to_hash).slice(*object.identity_vars.map(&:to_s)) : {} end private diff --git a/lib/intercom/api_operations/scroll.rb b/lib/intercom/api_operations/scroll.rb index c3204d17..4d372d4a 100644 --- a/lib/intercom/api_operations/scroll.rb +++ b/lib/intercom/api_operations/scroll.rb @@ -1,17 +1,16 @@ +# frozen_string_literal: true + require 'intercom/scroll_collection_proxy' require 'intercom/utils' module Intercom module ApiOperations module Scroll - - def scroll() - collection_name = Utils.resource_class_to_collection_name(collection_class) + def scroll finder_details = {} finder_details[:url] = "/#{collection_name}" - ScrollCollectionProxy.new(collection_name, finder_details: finder_details, client: @client) + ScrollCollectionProxy.new(collection_name, collection_class, details: finder_details, client: @client) end - end end end diff --git a/lib/intercom/api_operations/search.rb b/lib/intercom/api_operations/search.rb index 40406f64..1eafc456 100644 --- a/lib/intercom/api_operations/search.rb +++ b/lib/intercom/api_operations/search.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'intercom/search_collection_proxy' require 'intercom/utils' @@ -5,12 +7,11 @@ module Intercom module ApiOperations module Search def search(params) - collection_name = Utils.resource_class_to_collection_name(collection_class) search_details = { url: "/#{collection_name}/search", params: params } - SearchCollectionProxy.new(collection_name, search_details: search_details, client: @client) + SearchCollectionProxy.new(collection_name, collection_class, details: search_details, client: @client) end end end diff --git a/lib/intercom/base_collection_proxy.rb b/lib/intercom/base_collection_proxy.rb new file mode 100644 index 00000000..806e0125 --- /dev/null +++ b/lib/intercom/base_collection_proxy.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'intercom/utils' + +module Intercom + class BaseCollectionProxy + attr_reader :resource_name, :url, :resource_class + + def initialize(resource_name, resource_class, details: {}, client:, method: 'get') + @resource_name = resource_name + @resource_class = resource_class + @url = (details[:url] || "/#{@resource_name}") + @params = (details[:params] || {}) + @client = client + @method = method + end + + def each(&block) + loop do + response_hash = @client.public_send(@method, @url, payload) + raise Intercom::HttpError, 'Http Error - No response entity returned' unless response_hash + + deserialize_response_hash(response_hash, block) + break unless has_next_link?(response_hash) + end + self + end + + def [](target_index) + each_with_index do |item, index| + return item if index == target_index + end + nil + end + + include Enumerable + + private + + def deserialize_response_hash(response_hash, block) + top_level_type = response_hash.delete('type') + top_level_entity_key = if resource_name == 'subscriptions' + 'items' + else + Utils.entity_key_from_type(top_level_type) + end + response_hash[top_level_entity_key].each do |object_json| + block.call Lib::TypedJsonDeserializer.new(object_json, @client).deserialize + end + end + + def has_next_link?(response_hash) + paging_info = response_hash.delete('pages') + return false unless paging_info + + paging_next = paging_info['next'] + if paging_next + @params[:starting_after] = paging_next['starting_after'] + return true + else + return false + end + end + + def payload + payload = {} + payload[:per_page] = @params[:per_page] if @params[:per_page] + payload[:starting_after] = @params[:starting_after] if @params[:starting_after] + payload + end + end +end diff --git a/lib/intercom/client.rb b/lib/intercom/client.rb index 01a576f2..433c4097 100644 --- a/lib/intercom/client.rb +++ b/lib/intercom/client.rb @@ -1,38 +1,35 @@ +# frozen_string_literal: true + module Intercom class MisconfiguredClientError < StandardError; end class Client include Options - attr_reader :base_url, :rate_limit_details, :username_part, :password_part, :handle_rate_limit, :timeouts, :api_version + include DeprecatedResources + attr_reader :base_url, :rate_limit_details, :token, :handle_rate_limit, :timeouts, :api_version class << self def set_base_url(base_url) - return Proc.new do |o| + proc do |o| old_url = o.base_url o.send(:base_url=, base_url) - Proc.new { |obj| set_base_url(old_url).call(o) } + proc { |_obj| set_base_url(old_url).call(o) } end end def set_timeouts(open_timeout: nil, read_timeout: nil) - return Proc.new do |o| + proc do |o| old_timeouts = o.timeouts timeouts = {} timeouts[:open_timeout] = open_timeout if open_timeout timeouts[:read_timeout] = read_timeout if read_timeout o.send(:timeouts=, timeouts) - Proc.new { |obj| set_timeouts(old_timeouts).call(o) } + proc { |_obj| set_timeouts(old_timeouts).call(o) } end end end - 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) - if token - @username_part = token - @password_part = "" - else - @username_part = app_id - @password_part = api_key - end + def initialize(token: nil, base_url: 'https://api.intercom.io', handle_rate_limit: false, api_version: nil) + @token = token validate_credentials! @api_version = api_version @@ -67,10 +64,6 @@ def counts Intercom::Service::Counts.new(self) end - def customers - Intercom::Service::Customer.new(self) - end - def events Intercom::Service::Event.new(self) end @@ -111,6 +104,10 @@ def jobs Intercom::Service::Job.new(self) end + def data_attributes + Intercom::Service::DataAttribute.new(self) + end + def get(path, params) execute_request Intercom::Request.get(path, params) end @@ -130,25 +127,23 @@ def delete(path, payload_hash) private def validate_credentials! - error = MisconfiguredClientError.new("app_id and api_key must not be nil") - fail error if @username_part.nil? + error = MisconfiguredClientError.new('an access token must be provided') + raise error if @token.nil? end def validate_api_version! - error = MisconfiguredClientError.new("api_version must be either nil or a valid API version") - fail error if (@api_version && @api_version != 'Unstable' && Gem::Version.new(@api_version) < Gem::Version.new('1.0')) + error = MisconfiguredClientError.new('api_version must be either nil or a valid API version') + raise error if @api_version && @api_version != 'Unstable' && Gem::Version.new(@api_version) < Gem::Version.new('1.0') end def execute_request(request) request.handle_rate_limit = handle_rate_limit - request.execute(@base_url, username: @username_part, secret: @password_part, api_version: @api_version, **timeouts) + request.execute(@base_url, token: @token, api_version: @api_version, **timeouts) ensure @rate_limit_details = request.rate_limit_details end - def base_url=(new_url) - @base_url = new_url - end + attr_writer :base_url def timeouts=(timeouts) @timeouts = @timeouts.merge(timeouts) diff --git a/lib/intercom/client_collection_proxy.rb b/lib/intercom/client_collection_proxy.rb index dc2bcc44..f08b9a3e 100644 --- a/lib/intercom/client_collection_proxy.rb +++ b/lib/intercom/client_collection_proxy.rb @@ -1,71 +1,49 @@ -require "intercom/utils" +# frozen_string_literal: true -module Intercom - class ClientCollectionProxy - - attr_reader :resource_name, :finder_url, :resource_class - - def initialize(resource_name, finder_details: {}, client:) - @resource_name = resource_name - @resource_class = Utils.constantize_resource_name(resource_name) - @finder_url = (finder_details[:url] || "/#{@resource_name}") - @finder_params = (finder_details[:params] || {}) - @client = client - end +require 'intercom/utils' +require 'intercom/base_collection_proxy' +module Intercom + class ClientCollectionProxy < BaseCollectionProxy def each(&block) next_page = nil current_page = nil loop do - if next_page - response_hash = @client.get(next_page, {}) - else - response_hash = @client.get(@finder_url, @finder_params) - end - raise Intercom::HttpError.new('Http Error - No response entity returned') unless response_hash + response_hash = fetch(next_page) + raise Intercom::HttpError, 'Http Error - No response entity returned' unless response_hash + current_page = extract_current_page(response_hash) deserialize_response_hash(response_hash, block) next_page = extract_next_link(response_hash) - break if next_page.nil? or (@finder_params[:page] and (current_page >= @finder_params[:page])) + break if next_page.nil? || (@params[:page] && (current_page >= @params[:page])) end self end - def [](target_index) - self.each_with_index do |item, index| - return item if index == target_index + def fetch(next_page) + if next_page + @client.get(next_page, {}) + else + @client.get(@url, @params) end - nil end - include Enumerable - private - def deserialize_response_hash(response_hash, block) - top_level_type = response_hash.delete('type') - if resource_name == 'subscriptions' - top_level_entity_key = 'items' - else - top_level_entity_key = Utils.entity_key_from_type(top_level_type) - end - response_hash[top_level_entity_key].each do |object_json| - block.call Lib::TypedJsonDeserializer.new(object_json).deserialize - end - end - def paging_info_present?(response_hash) !!(response_hash['pages'] && response_hash['pages']['type']) end def extract_next_link(response_hash) return nil unless paging_info_present?(response_hash) + paging_info = response_hash.delete('pages') - paging_info["next"] + paging_info['next'] end def extract_current_page(response_hash) return nil unless paging_info_present?(response_hash) + response_hash['pages']['page'] end end diff --git a/lib/intercom/company.rb b/lib/intercom/company.rb index ddf3d549..61e8ca5d 100644 --- a/lib/intercom/company.rb +++ b/lib/intercom/company.rb @@ -1,10 +1,18 @@ require 'intercom/traits/incrementable_attributes' require 'intercom/traits/api_resource' +require 'intercom/api_operations/nested_resource' module Intercom class Company include Traits::IncrementableAttributes include Traits::ApiResource + include ApiOperations::NestedResource + + nested_resource_methods :contact, operations: %i[list] + + def self.collection_proxy_class + Intercom::ClientCollectionProxy + end def identity_vars ; [:id, :company_id] ; end def flat_store_attributes ; [:custom_attributes] ; end diff --git a/lib/intercom/contact.rb b/lib/intercom/contact.rb index 97ee2ed9..487b5b0f 100644 --- a/lib/intercom/contact.rb +++ b/lib/intercom/contact.rb @@ -1,11 +1,29 @@ +# frozen_string_literal: true + +require 'intercom/traits/incrementable_attributes' require 'intercom/traits/api_resource' +require 'intercom/api_operations/nested_resource' module Intercom class Contact + include Traits::IncrementableAttributes include Traits::ApiResource + include ApiOperations::NestedResource + + nested_resource_methods :tag, operations: %i[add delete list] + nested_resource_methods :note, operations: %i[create list] + nested_resource_methods :company, operations: %i[add delete list] + + def self.collection_proxy_class + Intercom::BaseCollectionProxy + end + + def identity_vars + [:id] + end - def identity_vars ; [:email, :user_id] ; end - def flat_store_attributes ; [:custom_attributes] ; end - def update_verb; 'put' ; end + def flat_store_attributes + [:custom_attributes] + end end end diff --git a/lib/intercom/conversation.rb b/lib/intercom/conversation.rb index 356d4f6a..e9edec39 100644 --- a/lib/intercom/conversation.rb +++ b/lib/intercom/conversation.rb @@ -1,7 +1,11 @@ require 'intercom/traits/api_resource' +require 'intercom/api_operations/nested_resource' module Intercom class Conversation include Traits::ApiResource + include ApiOperations::NestedResource + + nested_resource_methods :tag, operations: %i[add delete] end end diff --git a/lib/intercom/customer.rb b/lib/intercom/customer.rb deleted file mode 100644 index e996a9d9..00000000 --- a/lib/intercom/customer.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'intercom/traits/api_resource' - -module Intercom - class Customer - include Traits::ApiResource - - def identity_vars ; [:id, :email, :user_id] ; end - def flat_store_attributes ; [:custom_attributes] ; end - end -end diff --git a/lib/intercom/data_attribute.rb b/lib/intercom/data_attribute.rb new file mode 100644 index 00000000..cda919b3 --- /dev/null +++ b/lib/intercom/data_attribute.rb @@ -0,0 +1,7 @@ +require 'intercom/traits/api_resource' + +module Intercom + class DataAttribute + include Traits::ApiResource + end +end diff --git a/lib/intercom/deprecated_leads_collection_proxy.rb b/lib/intercom/deprecated_leads_collection_proxy.rb new file mode 100644 index 00000000..8a96165c --- /dev/null +++ b/lib/intercom/deprecated_leads_collection_proxy.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Intercom + class DeprecatedLeadsCollectionProxy < ClientCollectionProxy + def fetch(next_page) + response_hash = if next_page + @client.get(next_page, {}) + else + @client.get(@url, @params) + end + transform(response_hash) + end + + def transform(response_hash) + response_hash['type'] = 'lead.list' + leads_list = response_hash.delete('contacts') + leads_list.each { |lead| lead['type'] = 'lead' } + response_hash['leads'] = leads_list + response_hash + end + end +end diff --git a/lib/intercom/deprecated_resources.rb b/lib/intercom/deprecated_resources.rb new file mode 100644 index 00000000..bbc178fa --- /dev/null +++ b/lib/intercom/deprecated_resources.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Intercom + module DeprecatedResources + def deprecated__leads + Intercom::Service::Lead.new(self) + end + + def deprecated__users + Intercom::Service::User.new(self) + end + end +end diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index 7af77b54..5d7e594a 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -96,6 +96,9 @@ class ScrollAlreadyExistsError < IntercomError; end # Raised when a CDA is invalid class InvalidDocumentError < IntercomError; end + # Raised when a merge is invalid + class InvalidMergeError < IntercomError; end + # # Non-public errors (internal to the gem) # diff --git a/lib/intercom/extended_api_operations/segments.rb b/lib/intercom/extended_api_operations/segments.rb index a1bee733..d3ee8fe1 100644 --- a/lib/intercom/extended_api_operations/segments.rb +++ b/lib/intercom/extended_api_operations/segments.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'intercom/client_collection_proxy' require 'intercom/utils' @@ -6,7 +8,7 @@ module ExtendedApiOperations module Segments def by_segment(id) collection_name = Utils.resource_class_to_collection_name(collection_class) - ClientCollectionProxy.new(collection_name, finder_details: {url: "/#{collection_name}?segment_id=#{id}"}, client: @client) + ClientCollectionProxy.new(collection_name, collection_class, details: { url: "/#{collection_name}?segment_id=#{id}" }, client: @client) end end end diff --git a/lib/intercom/extended_api_operations/tags.rb b/lib/intercom/extended_api_operations/tags.rb index 2e9b9e41..d252e254 100644 --- a/lib/intercom/extended_api_operations/tags.rb +++ b/lib/intercom/extended_api_operations/tags.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'intercom/client_collection_proxy' require 'intercom/utils' @@ -6,7 +8,7 @@ module ExtendedApiOperations module Tags def by_tag(id) collection_name = Utils.resource_class_to_collection_name(collection_class) - ClientCollectionProxy.new(collection_name, finder_details: {url: "/#{collection_name}?tag_id=#{id}"}, client: @client) + ClientCollectionProxy.new(collection_name, collection_class, details: { url: "/#{collection_name}?tag_id=#{id}" }, client: @client) end end end diff --git a/lib/intercom/lead.rb b/lib/intercom/lead.rb new file mode 100644 index 00000000..022b3534 --- /dev/null +++ b/lib/intercom/lead.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'intercom/traits/api_resource' + +module Intercom + class Lead + include Traits::ApiResource + + def identity_vars + %i[email user_id] + end + + def flat_store_attributes + [:custom_attributes] + end + + def update_verb + 'put' + end + end +end diff --git a/lib/intercom/lib/typed_json_deserializer.rb b/lib/intercom/lib/typed_json_deserializer.rb index f805a5e2..17ed5ea3 100644 --- a/lib/intercom/lib/typed_json_deserializer.rb +++ b/lib/intercom/lib/typed_json_deserializer.rb @@ -1,54 +1,59 @@ +# frozen_string_literal: true + require 'intercom/utils' module Intercom module Lib - # Responsibility: To decide whether we are deserializing a collection or an - # entity of a particular type and to dispatch deserialization - class TypedJsonDeserializer - attr_reader :json - - def initialize(json) - @json = json - end + # Responsibility: To decide whether we are deserializing a collection or an + # entity of a particular type and to dispatch deserialization + class TypedJsonDeserializer + attr_reader :json + + def initialize(json, client) + @json = json + @client = client + end - def deserialize - if blank_object_type?(object_type) - raise DeserializationError, "No type field was found to facilitate deserialization" - elsif list_object_type?(object_type) - deserialize_collection(json[object_entity_key]) - else # singular object type - deserialize_object(json) - end + def deserialize + if blank_object_type?(object_type) + raise DeserializationError, 'No type field was found to facilitate deserialization' + elsif list_object_type?(object_type) + deserialize_collection(json[object_entity_key]) + else # singular object type + deserialize_object(json) end + end - private + private - def blank_object_type?(object_type) - object_type.nil? || object_type == '' - end + def blank_object_type?(object_type) + object_type.nil? || object_type == '' + end - def list_object_type?(object_type) - object_type.end_with?('.list') - end + def list_object_type?(object_type) + object_type.end_with?('.list') + end - def deserialize_collection(collection_json) - return [] if collection_json == nil - collection_json.map { |item_json| TypedJsonDeserializer.new(item_json).deserialize } - end + def deserialize_collection(collection_json) + return [] if collection_json.nil? - def deserialize_object(object_json) - entity_class = Utils.constantize_singular_resource_name(object_entity_key) - entity_class.from_api(object_json) - end + collection_json.map { |item_json| TypedJsonDeserializer.new(item_json, @client).deserialize } + end - def object_type - @object_type ||= json['type'] - end + def deserialize_object(object_json) + entity_class = Utils.constantize_singular_resource_name(object_entity_key) + deserialized = entity_class.from_api(object_json) + deserialized.client = @client + deserialized + end - def object_entity_key - @object_entity_key ||= Utils.entity_key_from_type(object_type) - end + def object_type + @object_type ||= json['type'] + end + def object_entity_key + @object_entity_key ||= Utils.entity_key_from_type(object_type) end + end end end diff --git a/lib/intercom/note.rb b/lib/intercom/note.rb index 0a724b3c..ec8a0e01 100644 --- a/lib/intercom/note.rb +++ b/lib/intercom/note.rb @@ -4,5 +4,9 @@ module Intercom class Note include Traits::ApiResource + + def self.collection_proxy_class + Intercom::BaseCollectionProxy + end end end diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index fb49ebf9..0be75505 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'cgi' require 'net/https' @@ -23,17 +25,18 @@ def put(path, form_data) private def method_with_body(http_method, path, params) request = http_method.send(:new, path, default_headers) request.body = params.to_json - request["Content-Type"] = "application/json" + request['Content-Type'] = 'application/json' request end private def default_headers - {'Accept-Encoding' => 'gzip, deflate', 'Accept' => 'application/vnd.intercom.3+json', 'User-Agent' => "Intercom-Ruby/#{Intercom::VERSION}"} + { 'Accept-Encoding' => 'gzip, deflate', 'Accept' => 'application/vnd.intercom.3+json', 'User-Agent' => "Intercom-Ruby/#{Intercom::VERSION}" } end private def append_query_string_to_url(url, params) return url if params.empty? - query_string = params.map { |k, v| "#{k.to_s}=#{CGI::escape(v.to_s)}" }.join('&') + + query_string = params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&') url + "?#{query_string}" end end @@ -46,11 +49,11 @@ def initialize(path, net_http_method) attr_accessor :handle_rate_limit - def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_timeout: 30, api_version: nil) + def execute(target_base_url = nil, token:, read_timeout: 90, open_timeout: 30, api_version: nil) retries = 3 base_uri = URI.parse(target_base_url) set_common_headers(net_http_method, base_uri) - set_basic_auth(net_http_method, username, secret) + set_auth_header(net_http_method, token) set_api_version(net_http_method, api_version) if api_version begin client(base_uri, read_timeout: read_timeout, open_timeout: open_timeout).start do |http| @@ -71,7 +74,7 @@ def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_ if @handle_rate_limit seconds_to_retry = (@rate_limit_details[:reset_at] - Time.now.utc).ceil if (retries -= 1) < 0 - raise Intercom::RateLimitExceeded.new('Rate limit retries exceeded. Please examine current API Usage.') + raise Intercom::RateLimitExceeded, 'Rate limit retries exceeded. Please examine current API Usage.' else sleep seconds_to_retry unless seconds_to_retry < 0 retry @@ -80,11 +83,11 @@ def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_ raise e end rescue Timeout::Error - raise Intercom::ServiceUnavailableError.new('Service Unavailable [request timed out]') + raise Intercom::ServiceUnavailableError, 'Service Unavailable [request timed out]' end end rescue Timeout::Error - raise Intercom::ServiceConnectionError.new('Failed to connect to service [connection attempt timed out]') + raise Intercom::ServiceConnectionError, 'Failed to connect to service [connection attempt timed out]' end end @@ -114,8 +117,9 @@ def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_ end private def decode(content_encoding, body) - return body if (!body) || body.empty? || content_encoding != 'gzip' - Zlib::GzipReader.new(StringIO.new(body)).read.force_encoding("utf-8") + return body if !body || body.empty? || content_encoding != 'gzip' + + Zlib::GzipReader.new(StringIO.new(body)).read.force_encoding('utf-8') end private def json_parse_response(str, code) @@ -123,7 +127,7 @@ def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_ JSON.parse(str) rescue JSON::ParserError - msg = <<~MSG.gsub(/[[:space:]]+/, " ").strip # #squish from ActiveSuppor + msg = <<~MSG.gsub(/[[:space:]]+/, ' ').strip # #squish from ActiveSuppor Expected a JSON response body. Instead got '#{str}' with status code '#{code}'. MSG @@ -139,12 +143,12 @@ def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_ @rate_limit_details = rate_limit_details end - private def set_common_headers(method, base_uri) + private def set_common_headers(method, _base_uri) method.add_field('AcceptEncoding', 'gzip, deflate') end - private def set_basic_auth(method, username, secret) - method.basic_auth(CGI.unescape(username), CGI.unescape(secret)) + private def set_auth_header(method, token) + method.add_field('Authorization', "Bearer #{token}") end private def set_api_version(method, api_version) @@ -155,21 +159,21 @@ def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_ code = res.code.to_i if code == 404 - raise Intercom::ResourceNotFound.new('Resource Not Found') + raise Intercom::ResourceNotFound, 'Resource Not Found' elsif code == 401 - raise Intercom::AuthenticationError.new('Unauthorized') + raise Intercom::AuthenticationError, 'Unauthorized' elsif code == 403 - raise Intercom::AuthenticationError.new('Forbidden') + raise Intercom::AuthenticationError, 'Forbidden' elsif code == 429 - raise Intercom::RateLimitExceeded.new('Rate Limit Exceeded') + raise Intercom::RateLimitExceeded, 'Rate Limit Exceeded' elsif code == 500 - raise Intercom::ServerError.new('Server Error') + raise Intercom::ServerError, 'Server Error' elsif code == 502 - raise Intercom::BadGatewayError.new('Bad Gateway Error') + raise Intercom::BadGatewayError, 'Bad Gateway Error' elsif code == 503 - raise Intercom::ServiceUnavailableError.new('Service Unavailable') + raise Intercom::ServiceUnavailableError, 'Service Unavailable' elsif code == 504 - raise Intercom::GatewayTimeoutError.new('Gateway Timeout') + raise Intercom::GatewayTimeoutError, 'Gateway Timeout' end end @@ -180,10 +184,10 @@ def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_ error_field = error_details['field'] parsed_http_code = (http_code > 0 ? http_code : nil) error_context = { - :http_code => parsed_http_code, - :application_error_code => error_code, - :field => error_field, - :request_id => error_list_details['request_id'] + http_code: parsed_http_code, + application_error_code: error_code, + field: error_field, + request_id: error_list_details['request_id'] } case error_code when 'unauthorized', 'forbidden', 'token_not_found' @@ -194,19 +198,19 @@ def execute(target_base_url=nil, username:, secret: nil, read_timeout: 90, open_ raise Intercom::TokenRevokedError.new(error_details['message'], error_context) when 'token_unauthorized' raise Intercom::TokenUnauthorizedError.new(error_details['message'], error_context) - when "bad_request", "missing_parameter", 'parameter_invalid', 'parameter_not_found' + when 'bad_request', 'missing_parameter', 'parameter_invalid', 'parameter_not_found' raise Intercom::BadRequestError.new(error_details['message'], error_context) - when "not_restorable" + when 'not_restorable' raise Intercom::BlockedUserError.new(error_details['message'], error_context) - when "not_found", "company_not_found" + when 'not_found', 'company_not_found' raise Intercom::ResourceNotFound.new(error_details['message'], error_context) - when "admin_not_found" + when 'admin_not_found' raise Intercom::AdminNotFound.new(error_details['message'], error_context) - when "rate_limit_exceeded" + when 'rate_limit_exceeded' raise Intercom::RateLimitExceeded.new(error_details['message'], error_context) - when "custom_data_limit_reached" + when 'custom_data_limit_reached' raise Intercom::CDALimitReachedError.new(error_details['message'], error_context) - when "invalid_document" + when 'invalid_document' raise Intercom::InvalidDocumentError.new(error_details['message'], error_context) when 'service_unavailable' raise Intercom::ServiceUnavailableError.new(error_details['message'], error_context) diff --git a/lib/intercom/scroll_collection_proxy.rb b/lib/intercom/scroll_collection_proxy.rb index 0727fe36..929c1425 100644 --- a/lib/intercom/scroll_collection_proxy.rb +++ b/lib/intercom/scroll_collection_proxy.rb @@ -1,32 +1,34 @@ -require "intercom/utils" +# frozen_string_literal: true -module Intercom - class ScrollCollectionProxy +require 'intercom/utils' +require 'intercom/base_collection_proxy' - attr_reader :resource_name, :scroll_url, :resource_class, :scroll_param, :records +module Intercom + class ScrollCollectionProxy < BaseCollectionProxy + attr_reader :scroll_url, :scroll_param, :records - def initialize(resource_name, finder_details: {}, client:) + def initialize(resource_name, resource_class, details: {}, client:) @resource_name = resource_name - @resource_class = Utils.constantize_resource_name(resource_name) - @scroll_url = (finder_details[:url] || "/#{@resource_name}") + '/scroll' + @resource_class = resource_class + @scroll_url = (details[:url] || "/#{@resource_name}") + '/scroll' @client = client - end - def next(scroll_parameter=nil) + def next(scroll_parameter = nil) @records = [] - if not scroll_parameter - #First time so do initial get without scroll_param - response_hash = @client.get(@scroll_url, '') - else - #Not first call so use get next page - response_hash = @client.get(@scroll_url, scroll_param: scroll_parameter) - end - raise Intercom::HttpError.new('Http Error - No response entity returned') unless response_hash + response_hash = if !scroll_parameter + # First time so do initial get without scroll_param + @client.get(@scroll_url, '') + else + # Not first call so use get next page + @client.get(@scroll_url, scroll_param: scroll_parameter) + end + raise Intercom::HttpError, 'Http Error - No response entity returned' unless response_hash + @scroll_param = extract_scroll_param(response_hash) top_level_entity_key = deserialize_response_hash(response_hash) response_hash[top_level_entity_key] = response_hash[top_level_entity_key].map do |object_json| - Lib::TypedJsonDeserializer.new(object_json).deserialize + Lib::TypedJsonDeserializer.new(object_json, @client).deserialize end @records = response_hash[@resource_name] self @@ -35,47 +37,40 @@ def next(scroll_parameter=nil) def each(&block) scroll_param = nil loop do - if not scroll_param - response_hash = @client.get(@scroll_url, '') - else - response_hash = @client.get(@scroll_url, scroll_param: scroll_param) - end - raise Intercom::HttpError.new('Http Error - No response entity returned') unless response_hash + response_hash = if !scroll_param + @client.get(@scroll_url, '') + else + @client.get(@scroll_url, scroll_param: scroll_param) + end + raise Intercom::HttpError, 'Http Error - No response entity returned' unless response_hash + response_hash[deserialize_response_hash(response_hash)].each do |object_json| - block.call Lib::TypedJsonDeserializer.new(object_json).deserialize + block.call Lib::TypedJsonDeserializer.new(object_json, @client).deserialize end scroll_param = extract_scroll_param(response_hash) - break if not records_present?(response_hash) + break unless records_present?(response_hash) end self end - def [](target_index) - self.each_with_index do |item, index| - return item if index == target_index - end - nil - end - - include Enumerable - private def deserialize_response_hash(response_hash) top_level_type = response_hash.delete('type') if resource_name == 'subscriptions' - top_level_entity_key = 'items' + 'items' else - top_level_entity_key = Utils.entity_key_from_type(top_level_type) + Utils.entity_key_from_type(top_level_type) end end def records_present?(response_hash) - (response_hash[@resource_name].length > 0) + !response_hash[@resource_name].empty? end def extract_scroll_param(response_hash) return nil unless records_present?(response_hash) + response_hash['scroll_param'] end end diff --git a/lib/intercom/search_collection_proxy.rb b/lib/intercom/search_collection_proxy.rb index 78c064c5..01e5643a 100644 --- a/lib/intercom/search_collection_proxy.rb +++ b/lib/intercom/search_collection_proxy.rb @@ -1,82 +1,47 @@ -require "intercom/utils" +# frozen_string_literal: true -module Intercom - class SearchCollectionProxy - - attr_reader :resource_name, :resource_class - - def initialize(resource_name, search_details: {}, client:) - @resource_name = resource_name - @resource_class = Utils.constantize_resource_name(resource_name) - @search_url = search_details[:url] - @search_params = search_details[:params] - @client = client - end - - def each(&block) - loop do - response_hash = @client.post(@search_url, payload) - raise Intercom::HttpError.new('Http Error - No response entity returned') unless response_hash - deserialize_response_hash(response_hash, block) - break unless has_next_link?(response_hash) - end - self - end +require 'intercom/utils' +require 'intercom/base_collection_proxy' - def [](target_index) - self.each_with_index do |item, index| - return item if index == target_index - end - nil +module Intercom + class SearchCollectionProxy < BaseCollectionProxy + def initialize(resource_name, resource_class, details: {}, client:) + super(resource_name, resource_class, details: details, client: client, method: 'post') end - include Enumerable - private - def deserialize_response_hash(response_hash, block) - top_level_type = response_hash.delete('type') - top_level_entity_key = Utils.entity_key_from_type(top_level_type) - response_hash[top_level_entity_key].each do |object_json| - block.call Lib::TypedJsonDeserializer.new(object_json).deserialize - end - end - - def has_next_link?(response_hash) - paging_info = response_hash.delete('pages') - paging_next = paging_info["next"] - if paging_next - @search_params[:starting_after] = paging_next["starting_after"] - return true - else - return false - end - end - def payload payload = { - query: @search_params[:query] + query: @params[:query] } - if @search_params[:sort_field] || @search_params[:sort_order] + if sort_field || sort_order payload[:sort] = {} - if @search_params[:sort_field] - payload[:sort][:field] = @search_params[:sort_field] - end - if @search_params[:sort_order] - payload[:sort][:order] = @search_params[:sort_order] - end + payload[:sort][:field] = sort_field if sort_field + payload[:sort][:order] = sort_order if sort_order end - if @search_params[:per_page] || @search_params[:starting_after] + if per_page || starting_after payload[:pagination] = {} - if @search_params[:per_page] - payload[:pagination][:per_page] = @search_params[:per_page] - end - if @search_params[:starting_after] - payload[:pagination][:starting_after] = @search_params[:starting_after] - end + payload[:pagination][:per_page] = per_page if per_page + payload[:pagination][:starting_after] = starting_after if starting_after end - return payload + payload + end + + def sort_field + @params[:sort_field] end + def sort_order + @params[:sort_order] + end + + def per_page + @params[:per_page] + end + + def starting_after + @params[:starting_after] + end end end diff --git a/lib/intercom/service/base_service.rb b/lib/intercom/service/base_service.rb index 6fbc2c8c..caadafa3 100644 --- a/lib/intercom/service/base_service.rb +++ b/lib/intercom/service/base_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'intercom/client_collection_proxy' module Intercom @@ -17,8 +19,13 @@ def collection_proxy_class Intercom::ClientCollectionProxy end + def collection_name + @collection_name ||= Utils.resource_class_to_collection_name(collection_class) + end + def from_api(api_response) object = collection_class.new + object.client = @client object.from_response(api_response) object end diff --git a/lib/intercom/service/company.rb b/lib/intercom/service/company.rb index 38367a0a..f23a97c6 100644 --- a/lib/intercom/service/company.rb +++ b/lib/intercom/service/company.rb @@ -23,18 +23,6 @@ class Company < BaseService def collection_class Intercom::Company end - - def users_by_intercom_company_id(id) - get_users(url: "/companies/#{id}/users") - end - - def users_by_company_id(id) - get_users(url: "/companies", params: { company_id: id, type: "user" }) - end - - private def get_users(url:, params: {}) - ClientCollectionProxy.new("users", finder_details: { url: url, params: params }, client: @client) - end end end end diff --git a/lib/intercom/service/contact.rb b/lib/intercom/service/contact.rb index 3a8ce974..737dc96e 100644 --- a/lib/intercom/service/contact.rb +++ b/lib/intercom/service/contact.rb @@ -2,12 +2,9 @@ require 'intercom/api_operations/load' require 'intercom/api_operations/list' require 'intercom/api_operations/find' -require 'intercom/api_operations/find_all' require 'intercom/api_operations/save' -require 'intercom/api_operations/scroll' -require 'intercom/api_operations/convert' -require 'intercom/api_operations/archive' -require 'intercom/api_operations/request_hard_delete' +require 'intercom/api_operations/delete' +require 'intercom/api_operations/search' module Intercom module Service @@ -15,16 +12,30 @@ class Contact < BaseService include ApiOperations::Load include ApiOperations::List include ApiOperations::Find - include ApiOperations::FindAll include ApiOperations::Save - include ApiOperations::Scroll - include ApiOperations::Convert - include ApiOperations::Archive - include ApiOperations::RequestHardDelete + include ApiOperations::Delete + include ApiOperations::Search def collection_class Intercom::Contact end + + def collection_proxy_class + Intercom::BaseCollectionProxy + end + + def merge(lead, user) + raise_invalid_merge_error unless lead.role == 'lead' && user.role == 'user' + + response = @client.post('/contacts/merge', from: lead.id, into: user.id) + raise Intercom::HttpError, 'Http Error - No response entity returned' unless response + + user.from_response(response) + end + + private def raise_invalid_merge_error + raise Intercom::InvalidMergeError, 'Merging can only be performed on a lead into a user' + end end end end diff --git a/lib/intercom/service/conversation.rb b/lib/intercom/service/conversation.rb index 95c4bd2c..bed5e93b 100644 --- a/lib/intercom/service/conversation.rb +++ b/lib/intercom/service/conversation.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'intercom/service/base_service' require 'intercom/api_operations/find_all' require 'intercom/api_operations/find' @@ -13,6 +15,7 @@ class Conversation < BaseService include ApiOperations::Find include ApiOperations::Load include ApiOperations::Save + include ApiOperations::Search def collection_class Intercom::Conversation @@ -25,7 +28,7 @@ def mark_read(id) def reply(reply_data) id = reply_data.delete(:id) collection_name = Utils.resource_class_to_collection_name(collection_class) - response = @client.post("/#{collection_name}/#{id}/reply", reply_data.merge(:conversation_id => id)) + response = @client.post("/#{collection_name}/#{id}/reply", reply_data.merge(conversation_id: id)) collection_class.new.from_response(response) end @@ -44,14 +47,20 @@ def close(reply_data) end def snooze(reply_data) - snoozed_until = reply_data.fetch(:snoozed_until) { fail 'snoozed_until field is required' } + reply_data.fetch(:snoozed_until) { raise 'snoozed_until field is required' } reply reply_data.merge(message_type: 'snoozed', type: 'admin') end def assign(reply_data) - assignee_id = reply_data.fetch(:assignee_id) { fail 'assignee_id is required' } + assignee_id = reply_data.fetch(:assignee_id) { raise 'assignee_id is required' } reply reply_data.merge(message_type: 'assignment', assignee_id: assignee_id, type: 'admin') end + + def run_assignment_rules(id) + collection_name = Utils.resource_class_to_collection_name(collection_class) + response = @client.post("/#{collection_name}/#{id}/run_assignment_rules") + collection_class.new.from_response(response) + end end end end diff --git a/lib/intercom/service/customer.rb b/lib/intercom/service/customer.rb deleted file mode 100644 index 48fdad95..00000000 --- a/lib/intercom/service/customer.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'intercom/service/base_service' -require 'intercom/api_operations/search' - -module Intercom - module Service - class Customer < BaseService - include ApiOperations::Search - - def collection_class - Intercom::Customer - end - end - end -end diff --git a/lib/intercom/service/data_attribute.rb b/lib/intercom/service/data_attribute.rb new file mode 100644 index 00000000..40f20a61 --- /dev/null +++ b/lib/intercom/service/data_attribute.rb @@ -0,0 +1,20 @@ +require 'intercom/service/base_service' +require 'intercom/api_operations/load' +require 'intercom/api_operations/list' +require 'intercom/api_operations/find_all' +require 'intercom/api_operations/save' + +module Intercom + module Service + class DataAttribute < BaseService + include ApiOperations::Load + include ApiOperations::List + include ApiOperations::FindAll + include ApiOperations::Save + + def collection_class + Intercom::DataAttribute + end + end + end +end diff --git a/lib/intercom/service/lead.rb b/lib/intercom/service/lead.rb new file mode 100644 index 00000000..7c243e9f --- /dev/null +++ b/lib/intercom/service/lead.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'intercom/service/base_service' +require 'intercom/api_operations/load' +require 'intercom/api_operations/list' +require 'intercom/api_operations/find' +require 'intercom/api_operations/find_all' +require 'intercom/api_operations/save' +require 'intercom/api_operations/scroll' +require 'intercom/api_operations/convert' +require 'intercom/api_operations/archive' +require 'intercom/api_operations/request_hard_delete' +require 'intercom/deprecated_leads_collection_proxy' + +module Intercom + module Service + class Lead < BaseService + include ApiOperations::Load + include ApiOperations::List + include ApiOperations::Find + include ApiOperations::FindAll + include ApiOperations::Save + include ApiOperations::Scroll + include ApiOperations::Convert + include ApiOperations::Archive + include ApiOperations::RequestHardDelete + + def collection_proxy_class + Intercom::DeprecatedLeadsCollectionProxy + end + + def collection_class + Intercom::Lead + end + + def collection_name + 'contacts' + end + end + end +end diff --git a/lib/intercom/service/note.rb b/lib/intercom/service/note.rb index 875c62a7..1f220c25 100644 --- a/lib/intercom/service/note.rb +++ b/lib/intercom/service/note.rb @@ -1,22 +1,18 @@ require 'intercom/service/base_service' -require 'intercom/api_operations/list' -require 'intercom/api_operations/find_all' require 'intercom/api_operations/find' -require 'intercom/api_operations/load' -require 'intercom/api_operations/save' module Intercom module Service class Note < BaseService - include ApiOperations::Save - include ApiOperations::List - include ApiOperations::FindAll include ApiOperations::Find - include ApiOperations::Load def collection_class Intercom::Note end + + def collection_proxy_class + Intercom::BaseCollectionProxy + end end end end diff --git a/lib/intercom/service/subscription.rb b/lib/intercom/service/subscription.rb index 59840213..eb5ea139 100644 --- a/lib/intercom/service/subscription.rb +++ b/lib/intercom/service/subscription.rb @@ -2,7 +2,7 @@ require 'intercom/api_operations/find_all' require 'intercom/api_operations/find' require 'intercom/api_operations/save' -require 'intercom/api_operations/archive' +require 'intercom/api_operations/delete' module Intercom module Service @@ -11,7 +11,7 @@ class Subscription < BaseService include ApiOperations::Find include ApiOperations::FindAll include ApiOperations::Save - include ApiOperations::Archive + include ApiOperations::Delete def collection_class Intercom::Subscription diff --git a/lib/intercom/service/tag.rb b/lib/intercom/service/tag.rb index 5044eada..42b8f00e 100644 --- a/lib/intercom/service/tag.rb +++ b/lib/intercom/service/tag.rb @@ -2,6 +2,7 @@ require 'intercom/api_operations/save' require 'intercom/api_operations/list' require 'intercom/api_operations/find_all' +require 'intercom/api_operations/find' module Intercom module Service @@ -9,12 +10,17 @@ class Tag < BaseService include ApiOperations::Save include ApiOperations::List include ApiOperations::FindAll - include ApiOperations::Archive + include ApiOperations::Delete + include ApiOperations::Find def collection_class Intercom::Tag end + def collection_proxy_class + Intercom::BaseCollectionProxy + end + def tag(params) params['tag_or_untag'] = 'tag' create(params) @@ -22,17 +28,11 @@ def tag(params) def untag(params) params['tag_or_untag'] = 'untag' - users_or_companies(params).each do |user_or_company| - user_or_company[:untag] = true + params[:companies].each do |company| + company[:untag] = true end create(params) end - - private - - def users_or_companies(params) - params[:users] || params[:companies] - end end end end diff --git a/lib/intercom/service/visitor.rb b/lib/intercom/service/visitor.rb index 0deac9cd..8b8578a2 100644 --- a/lib/intercom/service/visitor.rb +++ b/lib/intercom/service/visitor.rb @@ -1,26 +1,35 @@ require 'intercom/service/base_service' require 'intercom/api_operations/load' -require 'intercom/api_operations/list' require 'intercom/api_operations/find' -require 'intercom/api_operations/find_all' require 'intercom/api_operations/save' -require 'intercom/api_operations/convert' -require 'intercom/api_operations/archive' +require 'intercom/api_operations/delete' module Intercom module Service class Visitor < BaseService include ApiOperations::Load - include ApiOperations::List include ApiOperations::Find - include ApiOperations::FindAll include ApiOperations::Save - include ApiOperations::Convert - include ApiOperations::Archive + include ApiOperations::Delete def collection_class Intercom::Visitor end + + def convert(visitor, contact = false) + req = { visitor: { user_id: visitor.user_id } } + if contact + req[:user] = identity_hash(contact) + req[:type] = 'user' + else + req[:type] = 'lead' + end + Intercom::Contact.new.from_response( + @client.post( + "/visitors/convert", req + ) + ) + end end end end diff --git a/lib/intercom/tag.rb b/lib/intercom/tag.rb index e1c0b99c..eaeaee59 100644 --- a/lib/intercom/tag.rb +++ b/lib/intercom/tag.rb @@ -3,5 +3,9 @@ module Intercom class Tag include Traits::ApiResource + + def self.collection_proxy_class + Intercom::BaseCollectionProxy + end end end diff --git a/lib/intercom/traits/api_resource.rb b/lib/intercom/traits/api_resource.rb index e13f9574..52707794 100644 --- a/lib/intercom/traits/api_resource.rb +++ b/lib/intercom/traits/api_resource.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'intercom/lib/flat_store' require 'intercom/lib/dynamic_accessors' require 'intercom/lib/dynamic_accessors_on_method_missing' @@ -6,11 +8,10 @@ module Intercom module Traits - module ApiResource include DirtyTracking - attr_accessor :id + attr_accessor :id, :client def initialize(attributes = {}) from_hash(attributes) @@ -31,9 +32,8 @@ def from_hash(hash) end def to_hash - instance_variables_excluding_dirty_tracking_field.inject({}) do |hash, variable| - hash[variable.to_s.delete("@")] = instance_variable_get(variable) - hash + instance_variables_excluding_dirty_tracking_field.each_with_object({}) do |variable, hash| + hash[variable.to_s.delete('@')] = instance_variable_get(variable) end end @@ -46,34 +46,46 @@ def to_submittable_hash end def method_missing(method_sym, *arguments, &block) - Lib::DynamicAccessorsOnMethodMissing.new(method_sym, *arguments, self). - define_accessors_or_call { super } + Lib::DynamicAccessorsOnMethodMissing.new(method_sym, *arguments, self) + .define_accessors_or_call { super } end def flat_store_attribute?(attribute) - (respond_to?(:flat_store_attributes)) && (flat_store_attributes.map(&:to_s).include?(attribute.to_s)) + respond_to?(:flat_store_attributes) && flat_store_attributes.map(&:to_s).include?(attribute.to_s) end private def initialize_property(attribute, value) + return if addressable_list?(attribute, value) + Lib::DynamicAccessors.define_accessors(attribute, value, self) unless accessors_already_defined?(attribute) set_property(attribute, value) end + def addressable_list?(attribute, value) + return false unless typed_property?(attribute, value) + + value['type'] == 'list' && value['url'] + end + def accessors_already_defined?(attribute) respond_to?(attribute) && respond_to?("#{attribute}=") end def set_property(attribute, value) + value_to_set = parsed_value_for_attribute(attribute, value) + call_setter_for_attribute(attribute, value_to_set) + end + + def parsed_value_for_attribute(attribute, value) if typed_property?(attribute, value) - value_to_set = Intercom::Lib::TypedJsonDeserializer.new(value).deserialize + Intercom::Lib::TypedJsonDeserializer.new(value, client).deserialize elsif flat_store_attribute?(attribute) - value_to_set = Intercom::Lib::FlatStore.new(value) + Intercom::Lib::FlatStore.new(value) else - value_to_set = value + value end - call_setter_for_attribute(attribute, value_to_set) end def custom_attribute_field?(attribute) @@ -97,12 +109,12 @@ def typed_property?(attribute, value) end def typed_value?(value) - value.is_a? Hash and !!value['type'] + value.is_a?(Hash) && !!value['type'] end def call_setter_for_attribute(attribute, value) - setter_method = "#{attribute.to_s}=" - self.send(setter_method, value) + setter_method = "#{attribute}=" + send(setter_method, value) end def initialize_missing_flat_store_attributes @@ -120,7 +132,7 @@ def submittable_attribute?(attribute, value) module ClassMethods def from_api(api_response) - object = self.new + object = new object.from_response(api_response) object end @@ -129,7 +141,6 @@ def from_api(api_response) def self.included(base) base.extend(ClassMethods) end - end end end diff --git a/lib/intercom/user.rb b/lib/intercom/user.rb index a559d9d5..2cdc9de3 100644 --- a/lib/intercom/user.rb +++ b/lib/intercom/user.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'intercom/traits/incrementable_attributes' require 'intercom/traits/api_resource' @@ -6,9 +8,16 @@ class User include Traits::IncrementableAttributes include Traits::ApiResource - def identity_vars ; [:id, :email, :user_id] ; end - def flat_store_attributes ; [:custom_attributes] ; end - def update_verb ; 'post' ; end + def identity_vars + %i[id email user_id] + end + + def flat_store_attributes + [:custom_attributes] + end + def update_verb + 'post' + end end end diff --git a/lib/intercom/utils.rb b/lib/intercom/utils.rb index 900fd84f..ce0bcd74 100644 --- a/lib/intercom/utils.rb +++ b/lib/intercom/utils.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Intercom module Utils class << self @@ -7,6 +9,7 @@ def singularize(str) def pluralize(str) return str.gsub(/y$/, 'ies') if str =~ /y$/ + "#{str}s" end @@ -23,7 +26,13 @@ def constantize(camel_cased_word) end def resource_class_to_singular_name(resource_class) - resource_class.to_s.split('::')[-1].downcase + resource_name = resource_class.to_s.split('::')[-1] + resource_name = maybe_underscore_name(resource_name) + resource_name.downcase + end + + def maybe_underscore_name(resource_name) + resource_name.gsub!(/(.)([A-Z])/, '\1_\2') || resource_name end def resource_class_to_collection_name(resource_class) @@ -52,9 +61,11 @@ def define_lightweight_class(class_name) end def entity_key_from_type(type) + return 'data' if type == 'list' + is_list = type.split('.')[1] == 'list' entity_name = type.split('.')[0] - is_list ? Utils.pluralize(entity_name) : entity_name + is_list ? Utils.pluralize(entity_name) : entity_name end end end diff --git a/lib/intercom/visitor.rb b/lib/intercom/visitor.rb index 1b6ab6eb..099c2021 100644 --- a/lib/intercom/visitor.rb +++ b/lib/intercom/visitor.rb @@ -8,7 +8,5 @@ class Visitor def identity_vars ; [:id, :email, :user_id] ; end def flat_store_attributes ; [:custom_attributes] ; end - def update_verb ; 'put' ; end - end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index db916bec..ea084a15 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'intercom' require 'minitest/autorun' require 'mocha/setup' @@ -6,132 +8,175 @@ require 'pry' include WebMock::API -def test_customer(email="bob@example.com") - customer = test_user(email) - customer["type"] = "customer" - customer["role"] = "user" - customer +def test_user(email = 'bob@example.com') + { + 'type' => 'user', + 'id' => 'aaaaaaaaaaaaaaaaaaaaaaaa', + 'user_id' => 'id-from-customers-app', + 'email' => email, + 'name' => 'Joe Schmoe', + 'avatar' => { 'type' => 'avatar', 'image_url' => 'https://graph.facebook.com/1/picture?width=24&height=24' }, + 'app_id' => 'the-app-id', + 'custom_attributes' => { 'a' => 'b', 'b' => 2 }, + 'companies' => + { 'type' => 'company.list', + 'companies' => + [{ 'type' => 'company', + 'company_id' => '123', + 'id' => 'bbbbbbbbbbbbbbbbbbbbbbbb', + 'app_id' => 'the-app-id', + 'name' => 'Company 1', + 'remote_created_at' => 1_390_936_440, + 'created_at' => 1_401_970_114, + 'updated_at' => 1_401_970_114, + 'last_request_at' => 1_401_970_113, + 'monthly_spend' => 0, + 'session_count' => 0, + 'user_count' => 1, + 'tag_ids' => [], + 'custom_attributes' => { 'category' => 'Tech' } }] }, + 'session_count' => 123, + 'unsubscribed_from_emails' => true, + 'last_request_at' => 1_401_970_113, + 'created_at' => 1_401_970_114, + 'remote_created_at' => 1_393_613_864, + 'updated_at' => 1_401_970_114, + 'user_agent_data' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11', + 'social_profiles' => { 'type' => 'social_profile.list', + 'social_profiles' => [ + { 'type' => 'social_profile', 'name' => 'twitter', 'url' => 'http://twitter.com/abc', 'username' => 'abc', 'id' => nil }, + { 'type' => 'social_profile', 'name' => 'twitter', 'username' => 'abc2', 'url' => 'http://twitter.com/abc2', 'id' => nil }, + { 'type' => 'social_profile', 'name' => 'facebook', 'url' => 'http://facebook.com/abc', 'username' => 'abc', 'id' => '1234242' }, + { 'type' => 'social_profile', 'name' => 'quora', 'url' => 'http://facebook.com/abc', 'username' => 'abc', 'id' => '1234242' } + ] }, + 'location_data' => + { 'type' => 'location_data', + 'city_name' => 'Dublin', + 'continent_code' => 'EU', + 'country_name' => 'Ireland', + 'latitude' => '90', + 'longitude' => '10', + 'postal_code' => 'IE', + 'region_name' => 'Europe', + 'timezone' => '+1000', + 'country_code' => 'IRL' } + } end -def test_user(email="bob@example.com") +def test_contact(email = 'bob@example.com', role = 'user') { - "type" =>"user", - "id" =>"aaaaaaaaaaaaaaaaaaaaaaaa", - "user_id" => 'id-from-customers-app', - "email" => email, - "name" => "Joe Schmoe", - "avatar" => {"type"=>"avatar", "image_url"=>"https://graph.facebook.com/1/picture?width=24&height=24"}, - "app_id" => "the-app-id", - "custom_attributes" => {"a" => "b", "b" => 2}, - "companies" => - {"type"=>"company.list", - "companies"=> - [{"type"=>"company", - "company_id"=>"123", - "id"=>"bbbbbbbbbbbbbbbbbbbbbbbb", - "app_id"=>"the-app-id", - "name"=>"Company 1", - "remote_created_at"=>1390936440, - "created_at"=>1401970114, - "updated_at"=>1401970114, - "last_request_at"=>1401970113, - "monthly_spend"=>0, - "session_count"=>0, - "user_count"=>1, - "tag_ids"=>[], - "custom_attributes"=>{"category"=>"Tech"}}]}, - "session_count" => 123, - "unsubscribed_from_emails" => true, - "last_request_at" =>1401970113, - "created_at" =>1401970114, - "remote_created_at" =>1393613864, - "updated_at" =>1401970114, - "user_agent_data" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", - "social_profiles" =>{"type"=>"social_profile.list", - "social_profiles" => [ - {"type" => "social_profile", "name" => "twitter", "url" => "http://twitter.com/abc", "username" => "abc", "id" => nil}, - {"type" => "social_profile", "name" => "twitter", "username" => "abc2", "url" => "http://twitter.com/abc2", "id" => nil}, - {"type" => "social_profile", "name" => "facebook", "url" => "http://facebook.com/abc", "username" => "abc", "id" => "1234242"}, - {"type" => "social_profile", "name" => "quora", "url" => "http://facebook.com/abc", "username" => "abc", "id" => "1234242"} - ]}, - "location_data"=> - {"type"=>"location_data", - "city_name"=> 'Dublin', - "continent_code"=> 'EU', - "country_name"=> 'Ireland', - "latitude"=> '90', - "longitude"=> '10', - "postal_code"=> 'IE', - "region_name"=> 'Europe', - "timezone"=> '+1000', - "country_code" => "IRL"} + 'type' => 'contact', + 'id' => 'aaaaaaaaaaaaaaaaaaaaaaaa', + 'external_id' => 'id-from-customers-app', + 'role' => role, + 'email' => email, + 'name' => 'Joe Schmoe', + 'avatar' => { 'type' => 'avatar', 'image_url' => 'https://graph.facebook.com/1/picture?width=24&height=24' }, + 'workspace_id' => 'the-workspace-id', + 'custom_attributes' => { 'a' => 'b', 'b' => 2 }, + 'companies' => { + 'type' => 'list', + 'data' => + [{ 'type' => 'company', + 'company_id' => '123', + 'id' => 'bbbbbbbbbbbbbbbbbbbbbbbb', + 'workspace_id' => 'the-workspace-id', + 'name' => 'Company 1', + 'remote_created_at' => 1_390_936_440, + 'created_at' => 1_401_970_114, + 'updated_at' => 1_401_970_114, + 'last_request_at' => 1_401_970_113, + 'monthly_spend' => 0, + 'session_count' => 0, + 'contact_count' => 1, + 'tag_ids' => [], + 'custom_attributes' => { 'category' => 'Tech' } }], + 'url' => '/contacts/12345/companies' + }, + 'tags' => { + 'type' => 'list', + 'data' => [], + 'url' => '/contacts/12345/tags' + }, + 'notes' => { + 'type' => 'list', + 'data' => [], + 'url' => '/contacts/12345/notes' + }, + 'session_count' => 123, + 'unsubscribed_from_emails' => true, + 'last_request_at' => 1_401_970_113, + 'created_at' => 1_401_970_114, + 'remote_created_at' => 1_393_613_864, + 'updated_at' => 1_401_970_114, + 'user_agent_data' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11', + 'social_profiles' => { 'type' => 'social_profile.list', + 'social_profiles' => [ + { 'type' => 'social_profile', 'name' => 'twitter', 'url' => 'http://twitter.com/abc', 'username' => 'abc', 'id' => nil }, + { 'type' => 'social_profile', 'name' => 'twitter', 'username' => 'abc2', 'url' => 'http://twitter.com/abc2', 'id' => nil }, + { 'type' => 'social_profile', 'name' => 'facebook', 'url' => 'http://facebook.com/abc', 'username' => 'abc', 'id' => '1234242' }, + { 'type' => 'social_profile', 'name' => 'quora', 'url' => 'http://facebook.com/abc', 'username' => 'abc', 'id' => '1234242' } + ] }, + 'location_data' => + { 'type' => 'location_data', + 'city_name' => 'Dublin', + 'continent_code' => 'EU', + 'country_name' => 'Ireland', + 'latitude' => '90', + 'longitude' => '10', + 'postal_code' => 'IE', + 'region_name' => 'Europe', + 'timezone' => '+1000', + 'country_code' => 'IRL' } } end -def test_user_dates(email="bob@example.com", created_at=1401970114, last_request_at=1401970113) +def test_visitor { - "type" =>"user", - "id" =>"aaaaaaaaaaaaaaaaaaaaaaaa", - "user_id" => 'id-from-customers-app', - "email" => email, - "name" => "Joe Schmoe", - "avatar" => {"type"=>"avatar", "image_url"=>"https://graph.facebook.com/1/picture?width=24&height=24"}, - "app_id" => "the-app-id", - "custom_attributes" => {"a" => "b", "b" => 2}, - "companies" => - {"type"=>"company.list", - "companies"=> - [{"type"=>"company", - "company_id"=>"123", - "id"=>"bbbbbbbbbbbbbbbbbbbbbbbb", - "app_id"=>"the-app-id", - "name"=>"Company 1", - "remote_created_at"=>1390936440, - "created_at"=>1401970114, - "updated_at"=>1401970114, - "last_request_at"=>1401970113, - "monthly_spend"=>0, - "session_count"=>0, - "user_count"=>1, - "tag_ids"=>[], - "custom_attributes"=>{"category"=>"Tech"}}]}, - "session_count" => 123, - "unsubscribed_from_emails" => true, - "last_request_at" =>last_request_at, - "created_at" =>created_at, - "remote_created_at" =>1393613864, - "updated_at" =>1401970114, - "user_agent_data" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11", - "social_profiles" =>{"type"=>"social_profile.list", - "social_profiles" => [ - {"type" => "social_profile", "name" => "twitter", "url" => "http://twitter.com/abc", "username" => "abc", "id" => nil}, - {"type" => "social_profile", "name" => "twitter", "username" => "abc2", "url" => "http://twitter.com/abc2", "id" => nil}, - {"type" => "social_profile", "name" => "facebook", "url" => "http://facebook.com/abc", "username" => "abc", "id" => "1234242"}, - {"type" => "social_profile", "name" => "quora", "url" => "http://facebook.com/abc", "username" => "abc", "id" => "1234242"} - ]}, - "location_data"=> - {"type"=>"location_data", - "city_name"=> 'Dublin', - "continent_code"=> 'EU', - "country_name"=> 'Ireland', - "latitude"=> '90', - "longitude"=> '10', - "postal_code"=> 'IE', - "region_name"=> 'Europe', - "timezone"=> '+1000', - "country_code" => "IRL"} + 'type' => 'visitor', + 'id' => '123', + 'user_id' => '12334', + 'anonymous' => true, + 'email' => nil, + 'phone' => nil, + 'name' => nil, + 'pseudonym' => nil, + 'app_id' => 'abcd1234', + 'companies' => { 'type' => 'company.list', 'companies' => [] }, + 'location_data' => {}, + 'last_request_at' => nil, + 'created_at' => 1_401_970_114, + 'remote_created_at' => 1_401_970_114, + 'signed_up_at' => 1_401_970_114, + 'updated_at' => 1_401_970_114, + 'session_count' => 0, + 'social_profiles' => { 'type' => 'social_profile.list', 'social_profiles' => [] }, + 'owner_id' => nil, + 'unsubscribed_from_emails' => false, + 'marked_email_as_spam' => false, + 'has_hard_bounced' => false, + 'tags' => { 'type' => 'tag.list', 'tags' => [] }, + 'segments' => { 'type' => 'segment.list', 'segments' => [] }, + 'custom_attributes' => {}, + 'referrer' => nil, + 'utm_campaign' => nil, + 'utm_content' => nil, + 'utm_medium' => nil, + 'utm_source' => nil, + 'utm_term' => nil, + 'do_not_track' => nil } end def test_admin_list { - "type" => "admin.list", - "admins" => [ + 'type' => 'admin.list', + 'admins' => [ { - "type" => "admin", - "id" => "1234", - "name" => "Hoban Washburne", - "email" => "wash@serenity.io" + 'type' => 'admin', + 'id' => '1234', + 'name' => 'Hoban Washburne', + 'email' => 'wash@serenity.io' } ] } @@ -139,63 +184,88 @@ def test_admin_list def test_admin { - "type" => "admin", - "id" => "1234", - "name" => "Hoban Washburne", - "email" => "wash@serenity.io" + 'type' => 'admin', + 'id' => '1234', + 'name' => 'Hoban Washburne', + 'email' => 'wash@serenity.io' } end def test_team_list { - "type"=> "team.list", - "teams" => [ + 'type' => 'team.list', + 'teams' => [ { - "type"=> "team", - "id"=> "2744328", - "name"=> "the_a_team", - "admin_ids"=> [646303, 814860], + 'type' => 'team', + 'id' => '2744328', + 'name' => 'the_a_team', + 'admin_ids' => [646_303, 814_860] }, { - "type"=> "team", - "id"=> "814865", - "name"=> "BA_App", - "admin_ids" => [492881, 1195856] - }, + 'type' => 'team', + 'id' => '814865', + 'name' => 'BA_App', + 'admin_ids' => [492_881, 1_195_856] + } ] } end def test_team { - "type" => "team", - "id" => "2744328", - "name" => "the_a_team", - "admin_ids" => [646303, 814860] + 'type' => 'team', + 'id' => '2744328', + 'name' => 'the_a_team', + 'admin_ids' => [646_303, 814_860] + } +end + +def test_company(name = 'Blue Sun') + { + 'type' => 'company', + 'id' => '531ee472cce572a6ec000006', + 'name' => name, + 'plan' => { + 'type' => 'plan', + 'id' => '1', + 'name' => 'Paid' + }, + 'company_id' => '6', + 'remote_created_at' => 1_394_531_169, + 'created_at' => 1_394_533_506, + 'updated_at' => 1_396_874_658, + 'last_request_at' => 1_396_874_658, + 'monthly_spend' => 49, + 'session_count' => 26, + 'user_count' => 10, + 'custom_attributes' => { + 'paid_subscriber' => true, + 'team_mates' => 0 + } } end -def test_company +def test_company_dates(name = 'Blue Sun', created_at = 1_401_970_114, last_request_at = 1_401_970_113) { - "type" => "company", - "id" => "531ee472cce572a6ec000006", - "name" => "Blue Sun", - "plan" => { - "type" =>"plan", - "id" =>"1", - "name" =>"Paid" + 'type' => 'company', + 'id' => '531ee472cce572a6ec000006', + 'name' => name, + 'plan' => { + 'type' => 'plan', + 'id' => '1', + 'name' => 'Paid' }, - "company_id" => "6", - "remote_created_at" => 1394531169, - "created_at" => 1394533506, - "updated_at" => 1396874658, - "last_request_at" => 1396874658, - "monthly_spend" => 49, - "session_count" => 26, - "user_count" => 10, - "custom_attributes" => { - "paid_subscriber" => true, - "team_mates" => 0 + 'company_id' => '6', + 'remote_created_at' => 1_394_531_169, + 'created_at' => created_at, + 'updated_at' => 1_396_874_658, + 'last_request_at' => last_request_at, + 'monthly_spend' => 49, + 'session_count' => 26, + 'user_count' => 10, + 'custom_attributes' => { + 'paid_subscriber' => true, + 'team_mates' => 0 } } end @@ -206,140 +276,238 @@ def test_messages def test_message { - "created_at" => 1329837506, - "updated_at" => 1329664706, - "read" => true, - "created_by_user" => true, - "thread_id" => 5591, - "messages" => [ - { - "created_at" => 1329837506, - "html" => "

Hey Intercom, What is up?

\n", - "from" => { - "email" => "bob@example.com", - "name" => "Bob", - "user_id" => "123", - "is_admin" => false - } - }, - { - "created_at" => 1329664706, - "rendered_body" => "

Not much, you?

\n", - "from" => { - "name" => "Super Duper Admin", - "avatar" => { - "square_25" => "https://static.intercomcdn.com/avatars/13347/square_25/Ruairi_Profile.png?1375368166", - "square_50" => "https://static.intercomcdn.com/avatars/13347/square_50/Ruairi_Profile.png?1375368166", - "square_128" => "https://static.intercomcdn.com/avatars/13347/square_128/Ruairi_Profile.png?1375368166" - }, - "is_admin" => true - } + 'created_at' => 1_329_837_506, + 'updated_at' => 1_329_664_706, + 'read' => true, + 'created_by_user' => true, + 'thread_id' => 5591, + 'messages' => [ + { + 'created_at' => 1_329_837_506, + 'html' => "

Hey Intercom, What is up?

\n", + 'from' => { + 'email' => 'bob@example.com', + 'name' => 'Bob', + 'user_id' => '123', + 'is_admin' => false + } + }, + { + 'created_at' => 1_329_664_706, + 'rendered_body' => "

Not much, you?

\n", + 'from' => { + 'name' => 'Super Duper Admin', + 'avatar' => { + 'square_25' => 'https://static.intercomcdn.com/avatars/13347/square_25/Ruairi_Profile.png?1375368166', + 'square_50' => 'https://static.intercomcdn.com/avatars/13347/square_50/Ruairi_Profile.png?1375368166', + 'square_128' => 'https://static.intercomcdn.com/avatars/13347/square_128/Ruairi_Profile.png?1375368166' }, - { - "created_at" => 1329664806, - "rendered_body" => "

Not much either :(

\n", - "from" => { - "email" => "bob@example.com", - "name" => "Bob", - "user_id" => "123", - "is_admin" => false - } - } - ] + 'is_admin' => true + } + }, + { + 'created_at' => 1_329_664_806, + 'rendered_body' => "

Not much either :(

\n", + 'from' => { + 'email' => 'bob@example.com', + 'name' => 'Bob', + 'user_id' => '123', + 'is_admin' => false + } + } + ] } end -def page_of_users(include_next_link= false) +def page_of_users(include_next_link = false) { - "type"=>"user.list", - "pages"=> + 'type' => 'user.list', + 'pages' => { - "type"=>"pages", - "next"=> (include_next_link ? "https://api.intercom.io/users?per_page=50&page=2" : nil), - "page"=>1, - "per_page"=>50, - "total_pages"=>7 + 'type' => 'pages', + 'next' => (include_next_link ? 'https://api.intercom.io/users?per_page=50&page=2' : nil), + 'page' => 1, + 'per_page' => 50, + 'total_pages' => 7 }, - "users"=> [test_user("user1@example.com"), test_user("user2@example.com"), test_user("user3@example.com")], - "total_count"=>314 + 'users' => [test_user('user1@example.com'), test_user('user2@example.com'), test_user('user3@example.com')], + 'total_count' => 314 } end -def page_of_customers(include_starting_after= false) +def page_of_contacts(include_starting_after = false) + { 'type' => 'list', + 'data' => [ + { + 'type' => 'contact', + 'id' => '123', + 'workspace_id' => 'abc', + 'external_id' => '12345', + 'role' => 'lead', + 'email' => 'test1@example.com', + 'name' => 'Test', + 'unsubscribed_from_emails' => false, + 'created_at' => 1_573_035_771, + 'updated_at' => 1_573_035_771, + 'custom_attributes' => {}, + 'tags' => { + 'type' => 'list', + 'data' => [], + 'url' => '/contacts/12345/tags' + }, + 'notes' => { + 'type' => 'list', + 'data' => [], + 'url' => '/contacts/12345/notes' + }, + 'companies' => { + 'type' => 'list', + 'data' => [], + 'url' => '/contacts/12345/companies' + } + }, + { + 'type' => 'contact', + 'id' => '321', + 'workspace_id' => 'abc', + 'external_id' => '54321', + 'role' => 'user', + 'email' => 'test2@example.com', + 'name' => 'Test', + 'unsubscribed_from_emails' => false, + 'created_at' => 1_573_035_771, + 'updated_at' => 1_573_035_771, + 'custom_attributes' => {}, + 'tags' => { + 'type' => 'list', + 'data' => [], + 'url' => '/contacts/54321/tags' + }, + 'notes' => { + 'type' => 'list', + 'data' => [], + 'url' => '/contacts/54321/notes' + }, + 'companies' => { + 'type' => 'list', + 'data' => [], + 'url' => '/contacts/54321/companies' + } + }, + { + 'type' => 'contact', + 'id' => '111', + 'workspace_id' => 'abc', + 'external_id' => '111', + 'role' => 'lead', + 'email' => 'test3@example.com', + 'name' => 'Test', + 'unsubscribed_from_emails' => false, + 'created_at' => 1_573_035_771, + 'updated_at' => 1_573_035_771, + 'custom_attributes' => {}, + 'tags' => { + 'type' => 'list', + 'data' => [], + 'url' => '/contacts/111/tags' + }, + 'notes' => { + 'type' => 'list', + 'data' => [], + 'url' => '/contacts/111/notes' + }, + 'companies' => { + 'type' => 'list', + 'data' => [], + 'url' => '/contacts/111/companies' + } + } + ], + 'total_count' => 3, + 'pages' => { + 'type' => 'pages', + 'next' => (include_starting_after ? { 'page' => 2, 'starting_after' => 'EnCrYpTeDsTrInG' } : nil), + 'page' => 1, + 'per_page' => 50, + 'total_pages' => 1 + } } +end + +def page_of_companies(include_next_link = false) { - "type"=>"customer.list", - "pages"=> - { - "type"=>"pages", - "next"=> (include_starting_after ? { "page" => 2, "starting_after" => "EnCrYpTeDsTrInG" } : nil), - "page"=>1, - "per_page"=>50, - "total_pages"=>7 - }, - "customers"=> [test_customer("user1@example.com"), test_customer("user2@example.com"), test_customer("user3@example.com")], - "total_count"=>314 + 'type' => 'company.list', + 'pages' => + { + 'type' => 'pages', + 'next' => (include_next_link ? 'https://api.intercom.io/companies?per_page=50&page=2' : nil), + 'page' => 1, + 'per_page' => 50, + 'total_pages' => 7 + }, + 'companies' => [test_company('company1'), test_company('company2'), test_company('company3')], + 'total_count' => 314 } end -def users_scroll(include_users= false) +def companies_scroll(include_companies = false) { - "type"=>"user.list", - "scroll_param"=> ("da6bbbac-25f6-4f07-866b-b911082d7"), - "users"=> (include_users ? [test_user("user1@example.com"), test_user("user2@example.com"), test_user("user3@example.com")] : []), + 'type' => 'company.list', + 'scroll_param' => 'da6bbbac-25f6-4f07-866b-b911082d7', + 'companies' => (include_companies ? [test_company('company1'), test_company('company2'), test_company('company3')] : []) } end -def users_pagination(include_next_link:, per_page:, page:, total_pages:, total_count:, user_list:) +def companies_pagination(include_next_link:, per_page:, page:, total_pages:, total_count:, company_list:) { - "type"=>"user.list", - "pages"=> + 'type' => 'company.list', + 'pages' => { - "type"=>"pages", - "next"=> (include_next_link ? "https://api.intercom.io/users?per_page=" \ - + per_page.to_s + "&page=" + (page+1).to_s : nil), - "page"=>page, - "per_page"=>per_page, - "total_pages"=>total_pages + 'type' => 'pages', + 'next' => (include_next_link ? 'https://api.intercom.io/companies?per_page=' \ + + per_page.to_s + '&page=' + (page + 1).to_s : nil), + 'page' => page, + 'per_page' => per_page, + 'total_pages' => total_pages }, - "users"=> user_list, - "total_count"=>total_count + 'companies' => company_list, + 'total_count' => total_count } end def test_conversation { - "type" => "conversation", - "id" => "147", - "created_at" => 1400850973, - "updated_at" => 1400857494, - "conversation_message" => { - "type" => "conversation_message", - "subject" => "", - "body" => "

Hi Alice,

\n\n

We noticed you using our Product, do you have any questions?

\n

- Jane

", - "author" => { - "type" => "admin", - "id" => "25" + 'type' => 'conversation', + 'id' => '147', + 'created_at' => 1_400_850_973, + 'updated_at' => 1_400_857_494, + 'conversation_message' => { + 'type' => 'conversation_message', + 'subject' => '', + 'body' => "

Hi Alice,

\n\n

We noticed you using our Product, do you have any questions?

\n

- Jane

", + 'author' => { + 'type' => 'admin', + 'id' => '25' }, - "attachments" => [ + 'attachments' => [ { - "name" => "signature", - "url" => "http =>//someurl.com/signature.jpg" + 'name' => 'signature', + 'url' => 'http =>//someurl.com/signature.jpg' } ] }, - "user" => { - "type" => "user", - "id" => "536e564f316c83104c000020" + 'user' => { + 'type' => 'user', + 'id' => '536e564f316c83104c000020' }, - "assignee" => { - "type" => "admin", - "id" => "25" + 'assignee' => { + 'type' => 'admin', + 'id' => '25' }, - "open" => true, - "read" => true, - "conversation_parts" => { - "type" => "conversation_part.list", - "conversation_parts" => [ + 'open' => true, + 'read' => true, + 'conversation_parts' => { + 'type' => 'conversation_part.list', + 'conversation_parts' => [ ] } } @@ -347,88 +515,88 @@ def test_conversation def test_conversation_list { - "type" => "conversation.list", - "pages" => { - "type" => "pages", - "page" => 1, - "per_page" => 20, - "total_pages" => 1 - }, - "conversations" => [ - { - "type" => "conversation", - "id" => "147", - "created_at" => 1400850973, - "updated_at" => 1400857494, - "conversation_message" => { - "type" => "conversation_message", - "subject" => "", - "body" => "

Hi Alice,

\n\n

We noticed you using our Product, do you have any questions?

\n

- Jane

", - "author" => { - "type" => "admin", - "id" => "25" - }, - "attachments" => [ - { - "name" => "signature", - "url" => "http =>//someurl.com/signature.jpg" - } - ] - }, - "user" => { - "type" => "user", - "id" => "536e564f316c83104c000020" - }, - "assignee" => { - "type" => "admin", - "id" => "25" - }, - "open" => true, - "read" => true, - "conversation_parts" => { - "type" => "conversation_part.list", - "conversation_parts" => [ - ] - } - } - ] + 'type' => 'conversation.list', + 'pages' => { + 'type' => 'pages', + 'page' => 1, + 'per_page' => 20, + 'total_pages' => 1 + }, + 'conversations' => [ + { + 'type' => 'conversation', + 'id' => '147', + 'created_at' => 1_400_850_973, + 'updated_at' => 1_400_857_494, + 'conversation_message' => { + 'type' => 'conversation_message', + 'subject' => '', + 'body' => "

Hi Alice,

\n\n

We noticed you using our Product, do you have any questions?

\n

- Jane

", + 'author' => { + 'type' => 'admin', + 'id' => '25' + }, + 'attachments' => [ + { + 'name' => 'signature', + 'url' => 'http =>//someurl.com/signature.jpg' + } + ] + }, + 'user' => { + 'type' => 'user', + 'id' => '536e564f316c83104c000020' + }, + 'assignee' => { + 'type' => 'admin', + 'id' => '25' + }, + 'open' => true, + 'read' => true, + 'conversation_parts' => { + 'type' => 'conversation_part.list', + 'conversation_parts' => [ + ] + } + } + ] } end def segment { - "type" => "segment", - "id" => "5310d8e7598c9a0b24000002", - "name" => "Active", - "created_at" => 1393613031, - "updated_at" => 1393613031 + 'type' => 'segment', + 'id' => '5310d8e7598c9a0b24000002', + 'name' => 'Active', + 'created_at' => 1_393_613_031, + 'updated_at' => 1_393_613_031 } end def segment_list { - "type" => "segment.list", - "segments" => [ + 'type' => 'segment.list', + 'segments' => [ { - "created_at" => 1393613031, - "id" => "5310d8e7598c9a0b24000002", - "name" => "Active", - "type" => "segment", - "updated_at" => 1393613031 + 'created_at' => 1_393_613_031, + 'id' => '5310d8e7598c9a0b24000002', + 'name' => 'Active', + 'type' => 'segment', + 'updated_at' => 1_393_613_031 }, { - "created_at" => 1393613030, - "id" => "5310d8e6598c9a0b24000001", - "name" => "New", - "type" => "segment", - "updated_at" => 1393613030 + 'created_at' => 1_393_613_030, + 'id' => '5310d8e6598c9a0b24000001', + 'name' => 'New', + 'type' => 'segment', + 'updated_at' => 1_393_613_030 }, { - "created_at" => 1393613031, - "id" => "5310d8e7598c9a0b24000003", - "name" => "Slipping Away", - "type" => "segment", - "updated_at" => 1393613031 + 'created_at' => 1_393_613_031, + 'id' => '5310d8e7598c9a0b24000003', + 'name' => 'Slipping Away', + 'type' => 'segment', + 'updated_at' => 1_393_613_031 } ] } @@ -436,207 +604,210 @@ def segment_list def test_tag { - "id" => "4f73428b5e4dfc000b000112", - "name" => "Test Tag", - "segment" => false, - "tagged_user_count" => 2 + 'id' => '4f73428b5e4dfc000b000112', + 'name' => 'Test Tag', + 'segment' => false, + 'tagged_company_count' => 2 } end def test_user_notification { - "type" => "notification_event", - "id" => "notif_123456-56465-546546", - "topic" => "user.created", - "app_id" => "aaaaaa", - "data" => - { - "type" => "notification_event_data", - "item" => - { - "type" => "user", - "id" => "aaaaaaaaaaaaaaaaaaaaaaaa", - "user_id" => nil, - "email" => "joe@example.com", - "name" => "Joe Schmoe", - "avatar" => - { - "type" => "avatar", - "image_url" => nil - }, - "app_id" => "aaaaa", - "companies" => - { - "type" => "company.list", - "companies" => [ ] - }, - "location_data" => - { - }, - "last_request_at" => nil, - "created_at" => "1401970114", - "remote_created_at" => nil, - "updated_at" => "1401970114", - "session_count" => 0, - "social_profiles" => - { - "type" => "social_profile.list", - "social_profiles" => [ ] - }, - "unsubscribed_from_emails" => false, - "user_agent_data" => nil, - "tags" => - { - "type" => "tag.list", - "tags" => [ ] - }, - "segments" => - { - "type" => "segment.list", - "segments" => [ ] - }, - "custom_attributes" => - { - } - } - }, - "delivery_status" => nil, - "delivery_attempts" => 1, - "delivered_at" => 0, - "first_sent_at" => 1410188629, - "created_at" => 1410188628, - "links" => { }, - "self" => nil + 'type' => 'notification_event', + 'id' => 'notif_123456-56465-546546', + 'topic' => 'user.created', + 'app_id' => 'aaaaaa', + 'data' => + { + 'type' => 'notification_event_data', + 'item' => + { + 'type' => 'user', + 'id' => 'aaaaaaaaaaaaaaaaaaaaaaaa', + 'user_id' => nil, + 'email' => 'joe@example.com', + 'name' => 'Joe Schmoe', + 'avatar' => + { + 'type' => 'avatar', + 'image_url' => nil + }, + 'app_id' => 'aaaaa', + 'companies' => + { + 'type' => 'company.list', + 'companies' => [] + }, + 'location_data' => + { + }, + 'last_request_at' => nil, + 'created_at' => '1401970114', + 'remote_created_at' => nil, + 'updated_at' => '1401970114', + 'session_count' => 0, + 'social_profiles' => + { + 'type' => 'social_profile.list', + 'social_profiles' => [] + }, + 'unsubscribed_from_emails' => false, + 'user_agent_data' => nil, + 'tags' => + { + 'type' => 'tag.list', + 'tags' => [] + }, + 'segments' => + { + 'type' => 'segment.list', + 'segments' => [] + }, + 'custom_attributes' => + { + } + } + }, + 'delivery_status' => nil, + 'delivery_attempts' => 1, + 'delivered_at' => 0, + 'first_sent_at' => 1_410_188_629, + 'created_at' => 1_410_188_628, + 'links' => {}, + 'self' => nil } end def test_conversation_notification { - "type"=>"notification_event", - "id"=>"notif_123456-56465-546546", - "topic"=>"conversation.user.created", - "app_id"=>"aaaaa", - "data"=> - { - "type"=>"notification_event_data", - "item"=> + 'type' => 'notification_event', + 'id' => 'notif_123456-56465-546546', + 'topic' => 'conversation.user.created', + 'app_id' => 'aaaaa', + 'data' => { - "type"=>"conversation", - "id"=>"123456789", - "created_at"=>"1410335293", - "updated_at"=>"1410335293", - "user"=> - { - "type"=>"user", - "id"=>"540f1de7112d3d1d51001637", - "name"=>"Kill Bill", - "email"=>"bill@bill.bill"}, - "assignee"=> - { - "type"=>"nobody_admin", - "id"=>nil - }, - "conversation_message"=> - { - "type"=>"conversation_message", - "id"=>"321546", - "subject"=>"", - "body"=>"

An important message

", - "author"=> - { - "type"=>"user", - "id"=>"aaaaaaaaaaaaaaaaaaaaaa", - "name"=>"Kill Bill", - "email"=>"bill@bill.bill"}, - "attachments"=>[] - }, - "conversation_parts"=> - { - "type"=>"conversation_part.list", - "conversation_parts"=>[] - }, - "open"=>nil, - "read"=>true, - "links"=> - { - "conversation_web"=> - "https://app.intercom.io/a/apps/aaaaaa/inbox/all/conversations/123456789"} - } - }, - "delivery_status"=>nil, - "delivery_attempts"=>1, - "delivered_at"=>0, - "first_sent_at"=>1410335293, - "created_at"=>1410335293, - "links"=>{}, - "self"=>nil + 'type' => 'notification_event_data', + 'item' => + { + 'type' => 'conversation', + 'id' => '123456789', + 'created_at' => '1410335293', + 'updated_at' => '1410335293', + 'user' => + { + 'type' => 'user', + 'id' => '540f1de7112d3d1d51001637', + 'name' => 'Kill Bill', + 'email' => 'bill@bill.bill' + }, + 'assignee' => + { + 'type' => 'nobody_admin', + 'id' => nil + }, + 'conversation_message' => + { + 'type' => 'conversation_message', + 'id' => '321546', + 'subject' => '', + 'body' => '

An important message

', + 'author' => + { + 'type' => 'user', + 'id' => 'aaaaaaaaaaaaaaaaaaaaaa', + 'name' => 'Kill Bill', + 'email' => 'bill@bill.bill' + }, + 'attachments' => [] + }, + 'conversation_parts' => + { + 'type' => 'conversation_part.list', + 'conversation_parts' => [] + }, + 'open' => nil, + 'read' => true, + 'links' => + { + 'conversation_web' => + 'https://app.intercom.io/a/apps/aaaaaa/inbox/all/conversations/123456789' + } + } + }, + 'delivery_status' => nil, + 'delivery_attempts' => 1, + 'delivered_at' => 0, + 'first_sent_at' => 1_410_335_293, + 'created_at' => 1_410_335_293, + 'links' => {}, + 'self' => nil } end def test_subscription - {"request"=> - {"type"=>"notification_subscription", - "id"=>"nsub_123456789", - "created_at"=>1410368642, - "updated_at"=>1410368642, - "service_type"=>"web", - "app_id"=>"3qmk5gyg", - "url"=> - "http://example.com", - "self"=> - "https://api.intercom.io/subscriptions/nsub_123456789", - "topics"=>["user.created", "conversation.user.replied", "conversation.admin.replied"], - "active"=>true, - "metadata"=>{}, - "hub_secret"=>nil, - "mode"=>"point", - "links"=> - {"sent"=> - "https://api.intercom.io/subscriptions/nsub_123456789/sent", - "retry"=> - "https://api.intercom.io/subscriptions/nsub_123456789/retry", - "errors"=> - "https://api.intercom.io/subscriptions/nsub_123456789/errors"}, - "notes"=>[]}} + { 'request' => + { 'type' => 'notification_subscription', + 'id' => 'nsub_123456789', + 'created_at' => 1_410_368_642, + 'updated_at' => 1_410_368_642, + 'service_type' => 'web', + 'app_id' => '3qmk5gyg', + 'url' => + 'http://example.com', + 'self' => + 'https://api.intercom.io/subscriptions/nsub_123456789', + 'topics' => ['user.created', 'conversation.user.replied', 'conversation.admin.replied'], + 'active' => true, + 'metadata' => {}, + 'hub_secret' => nil, + 'mode' => 'point', + 'links' => + { 'sent' => + 'https://api.intercom.io/subscriptions/nsub_123456789/sent', + 'retry' => + 'https://api.intercom.io/subscriptions/nsub_123456789/retry', + 'errors' => + 'https://api.intercom.io/subscriptions/nsub_123456789/errors' }, + 'notes' => [] } } end def test_app_count { - "type" => "count.hash", - "company" => { - "count" => 8 + 'type' => 'count.hash', + 'company' => { + 'count' => 8 }, - "segment" => { - "count" => 47 + 'segment' => { + 'count' => 47 }, - "tag" => { - "count" => 341 + 'tag' => { + 'count' => 341 }, - "user" => { - "count" => 12239 + 'user' => { + 'count' => 12_239 } } end def test_segment_count { - "type" => "count", - "user" => { - "segment" => [ + 'type' => 'count', + 'user' => { + 'segment' => [ { - "Active" => 1 + 'Active' => 1 }, { - "New" => 0 + 'New' => 0 }, { - "VIP" => 0 + 'VIP' => 0 }, { - "Slipping Away" => 0 + 'Slipping Away' => 0 }, { - "segment 1" => 1 + 'segment 1' => 1 } ] } @@ -645,36 +816,36 @@ def test_segment_count def test_conversation_count { - "type" => "count", - "conversation" => { - "assigned" => 1, - "closed" => 15, - "open" => 1, - "unassigned" => 0 + 'type' => 'count', + 'conversation' => { + 'assigned' => 1, + 'closed' => 15, + 'open' => 1, + 'unassigned' => 0 } } end def test_event - { - "type" => "event", - "event_name" => "invited-friend", - "created_at" => 1389913941, - "user_id" => "314159", - "metadata" => { - "type" => "user", - "invitee_email" => "pi@example.org", - "invite_code" => "ADDAFRIEND" - } + { + 'type' => 'event', + 'event_name' => 'invited-friend', + 'created_at' => 1_389_913_941, + 'user_id' => '314159', + 'metadata' => { + 'type' => 'user', + 'invitee_email' => 'pi@example.org', + 'invite_code' => 'ADDAFRIEND' } + } end def test_event_list { - "type" => "event.list", - "events" => [ test_event ], - "pages" => { - "next" => "https://api.intercom.io/events?type=user&intercom_user_id=55a3b&before=144474756550" + 'type' => 'event.list', + 'events' => [test_event], + 'pages' => { + 'next' => 'https://api.intercom.io/events?type=user&intercom_user_id=55a3b&before=144474756550' } } end @@ -683,28 +854,82 @@ def tomorrow (DateTime.now.to_time + 1).to_i end -def page_of_events(include_next_link=false) +def page_of_events(include_next_link = false) { - "type" => "event.list", - "events" => [ test_event ], - "pages" => + 'type' => 'event.list', + 'events' => [test_event], + 'pages' => { - "next" => (include_next_link ? "https://api.intercom.io/events?type=user&intercom_user_id=55a3b&before=144474756550" : nil), + 'next' => (include_next_link ? 'https://api.intercom.io/events?type=user&intercom_user_id=55a3b&before=144474756550' : nil) } } end +def test_data_attribute + { + 'type' => 'data_attribute', + 'model' => 'contact', + 'name' => 'region_name', + 'full_name' => 'location_data.region_name', + 'label' => 'Region', + 'description' => '', + 'data_type' => 'string', + 'api_writable' => false, + 'ui_writable' => true, + 'custom' => false, + 'archived' => false + } +end + +def test_data_attribute_list + { + 'type' => 'data_attribute.list', + 'data_attributes' => [ + { + 'type' => 'data_attribute', + 'model' => 'customer', + 'name' => 'paid_subscriber', + 'full_name' => 'custom_attributes.paid_subscriber', + 'label' => 'paid_subscriber', + 'description' => '', + 'data_type' => 'string', + 'options' => %w[ + pick_value_1 + pick_value_2 + ], + 'api_writable' => true, + 'ui_writable' => true, + 'custom' => true, + 'archived' => false, + 'admin_id' => '1', + 'created_at' => 1_392_734_388, + 'updated_at' => 1_392_734_388 + }, + { + 'type' => 'data_attribute', + 'model' => 'customer', + 'name' => 'region_name', + 'full_name' => 'location_data.region_name', + 'label' => 'Region', + 'description' => '', + 'data_type' => 'string', + 'api_writable' => false, + 'ui_writable' => true, + 'custom' => false, + 'archived' => false + } + ] + } +end def error_on_modify_frozen RUBY_VERSION =~ /1.8/ ? TypeError : RuntimeError end def capture_exception(&block) - begin - block.call - rescue => e - return e - end + block.call +rescue StandardError => e + e end def unshuffleable_array(array) diff --git a/spec/unit/intercom/admin_spec.rb b/spec/unit/intercom/admin_spec.rb index 30424e03..4c633b21 100644 --- a/spec/unit/intercom/admin_spec.rb +++ b/spec/unit/intercom/admin_spec.rb @@ -1,12 +1,12 @@ require "spec_helper" describe "Intercom::Admin" do - let (:client) { Intercom::Client.new(token: 'token') } + let(:client) { Intercom::Client.new(token: 'token') } it "returns a CollectionProxy for all without making any requests" do client.expects(:execute_request).never all = client.admins.all - all.must_be_instance_of(Intercom::ClientCollectionProxy) + _(all).must_be_instance_of(Intercom::ClientCollectionProxy) end it "gets me (access token method only)" do diff --git a/spec/unit/intercom/base_collection_proxy_spec.rb b/spec/unit/intercom/base_collection_proxy_spec.rb new file mode 100644 index 00000000..ca37b720 --- /dev/null +++ b/spec/unit/intercom/base_collection_proxy_spec.rb @@ -0,0 +1,30 @@ +require "spec_helper" + +describe Intercom::BaseCollectionProxy do + let(:client) { Intercom::Client.new(token: 'token') } + + it "stops iterating if no starting after value" do + client.expects(:get).with("/contacts", {}). returns(page_of_contacts(false)) + emails = [] + client.contacts.all.each { |contact| emails << contact.email } + _(emails).must_equal %w[test1@example.com test2@example.com test3@example.com] + end + + it "keeps iterating if starting after value" do + client.expects(:get).with("/contacts", {}).returns(page_of_contacts(true)) + client.expects(:get).with('/contacts', { starting_after: "EnCrYpTeDsTrInG" }).returns(page_of_contacts(false)) + emails = [] + client.contacts.all.each { |contact| emails << contact.email } + end + + it "supports indexed array access" do + client.expects(:get).with("/contacts", {}).returns(page_of_contacts(false)) + _(client.contacts.all[0].email).must_equal "test1@example.com" + end + + it "supports map" do + client.expects(:get).with("/contacts", {}).returns(page_of_contacts(false)) + emails = client.contacts.all.map { |contact| contact.email } + _(emails).must_equal %w[test1@example.com test2@example.com test3@example.com] + end +end diff --git a/spec/unit/intercom/client_collection_proxy_spec.rb b/spec/unit/intercom/client_collection_proxy_spec.rb index 88d231b9..40431636 100644 --- a/spec/unit/intercom/client_collection_proxy_spec.rb +++ b/spec/unit/intercom/client_collection_proxy_spec.rb @@ -1,80 +1,80 @@ require "spec_helper" describe Intercom::ClientCollectionProxy do - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } + let(:client) { Intercom::Client.new(token: 'token') } it "stops iterating if no next link" do - client.expects(:get).with("/users", {}).returns(page_of_users(false)) - emails = [] - client.users.all.each { |user| emails << user.email } - emails.must_equal %W(user1@example.com user2@example.com user3@example.com) + client.expects(:get).with("/companies", {}).returns(page_of_companies(false)) + names = [] + client.companies.all.each { |company| names << company.name } + _(names).must_equal %W(company1 company2 company3) end it "keeps iterating if next link" do - client.expects(:get).with("/users", {}).returns(page_of_users(true)) - client.expects(:get).with('https://api.intercom.io/users?per_page=50&page=2', {}).returns(page_of_users(false)) - emails = [] - client.users.all.each { |user| emails << user.email } + client.expects(:get).with("/companies", {}).returns(page_of_companies(true)) + client.expects(:get).with('https://api.intercom.io/companies?per_page=50&page=2', {}).returns(page_of_companies(false)) + names = [] + client.companies.all.each { |company| names << company.name } end it "supports indexed array access" do - client.expects(:get).with("/users", {}).returns(page_of_users(false)) - client.users.all[0].email.must_equal 'user1@example.com' + client.expects(:get).with("/companies", {}).returns(page_of_companies(false)) + _(client.companies.all[0].name).must_equal 'company1' end it "supports map" do - client.expects(:get).with("/users", {}).returns(page_of_users(false)) - emails = client.users.all.map { |user| user.email } - emails.must_equal %W(user1@example.com user2@example.com user3@example.com) + client.expects(:get).with("/companies", {}).returns(page_of_companies(false)) + names = client.companies.all.map { |company| company.name } + _(names).must_equal %W(company1 company2 company3) end it "supports querying" do - client.expects(:get).with("/users", {:tag_name => 'Taggart J'}).returns(page_of_users(false)) - client.users.find_all(:tag_name => 'Taggart J').map(&:email).must_equal %W(user1@example.com user2@example.com user3@example.com) + client.expects(:get).with("/companies", {:tag_name => 'Taggart J'}).returns(page_of_companies(false)) + _(client.companies.find_all(:tag_name => 'Taggart J').map(&:name)).must_equal %W(company1 company2 company3) end it "supports single page pagination" do - users = [test_user("user1@example.com"), test_user("user2@example.com"), test_user("user3@example.com"), - test_user("user4@example.com"), test_user("user5@example.com"), test_user("user6@example.com"), - test_user("user7@example.com"), test_user("user8@example.com"), test_user("user9@example.com"), - test_user("user10@example.com")] - client.expects(:get).with("/users", {:type=>'users', :per_page => 10, :page => 1}).returns(users_pagination(include_next_link: false, per_page: 10, page: 1, total_pages: 1, total_count: 10, user_list: users)) - result = client.users.find_all(:type=>'users', :per_page => 10, :page => 1).map {|user| user.email } - result.must_equal %W(user1@example.com user2@example.com user3@example.com user4@example.com user5@example.com user6@example.com user7@example.com user8@example.com user9@example.com user10@example.com) + companies = [test_company("company1"), test_company("company2"), test_company("company3"), + test_company("company4"), test_company("company5"), test_company("company6"), + test_company("company7"), test_company("company8"), test_company("company9"), + test_company("company10")] + client.expects(:get).with("/companies", {:type=>'companies', :per_page => 10, :page => 1}).returns(companies_pagination(include_next_link: false, per_page: 10, page: 1, total_pages: 1, total_count: 10, company_list: companies)) + result = client.companies.find_all(:type=>'companies', :per_page => 10, :page => 1).map {|company| company.name } + _(result).must_equal %W(company1 company2 company3 company4 company5 company6 company7 company8 company9 company10) end it "supports multi page pagination" do - users = [test_user("user3@example.com"), test_user("user4@example.com")] - client.expects(:get).with("/users", {:type=>'users', :per_page => 2, :page => 3}).returns(users_pagination(include_next_link: true, per_page: 2, page: 3, total_pages: 5, total_count: 10, user_list: users)) - result = client.users.find_all(:type=>'users', :per_page => 2, :page => 3).map {|user| user.email } - result.must_equal %W(user3@example.com user4@example.com) + companies = [test_company("company3"), test_company("company4")] + client.expects(:get).with("/companies", {:type=>'companies', :per_page => 2, :page => 3}).returns(companies_pagination(include_next_link: true, per_page: 2, page: 3, total_pages: 5, total_count: 10, company_list: companies)) + result = client.companies.find_all(:type=>'companies', :per_page => 2, :page => 3).map {|company| company.name } + _(result).must_equal %W(company3 company4) end it "works with page out of range request" do - users = [] - client.expects(:get).with("/users", {:type=>'users', :per_page => 2, :page => 30}).returns(users_pagination(include_next_link: true, per_page: 2, page: 30, total_pages: 2, total_count: 3, user_list: users)) - result = client.users.find_all(:type=>'users', :per_page => 2, :page => 30).map {|user| user.email } - result.must_equal %W() + companies = [] + client.expects(:get).with("/companies", {:type=>'companies', :per_page => 2, :page => 30}).returns(companies_pagination(include_next_link: true, per_page: 2, page: 30, total_pages: 2, total_count: 3, company_list: companies)) + result = client.companies.find_all(:type=>'companies', :per_page => 2, :page => 30).map {|company| company.name } + _(result).must_equal %W() end it "works with asc order" do test_date=1457337600 time_increment=1000 - users = [test_user_dates(email="user1@example.com", created_at=test_date), test_user_dates(email="user2@example.com", created_at=test_date-time_increment), - test_user_dates(email="user3@example.com", created_at=test_date-2*time_increment), test_user_dates(email="user4@example.com", created_at=test_date-3*time_increment)] - client.expects(:get).with("/users", {:type=>'users', :per_page => 4, :page => 5, :order => "asc", :sort => "created_at"}).returns(users_pagination(include_next_link: true, per_page: 4, page: 5, total_pages: 6, total_count: 30, user_list: users)) - result = client.users.find_all(:type=>'users', :per_page => 4, :page => 5, :order => "asc", :sort => "created_at").map(&:email) - result.must_equal %W(user1@example.com user2@example.com user3@example.com user4@example.com) + companies = [test_company_dates(name="company1", created_at=test_date), test_company_dates(name="company2", created_at=test_date-time_increment), + test_company_dates(name="company3", created_at=test_date-2*time_increment), test_company_dates(name="company4", created_at=test_date-3*time_increment)] + client.expects(:get).with("/companies", {:type=>'companies', :per_page => 4, :page => 5, :order => "asc", :sort => "created_at"}).returns(companies_pagination(include_next_link: true, per_page: 4, page: 5, total_pages: 6, total_count: 30, company_list: companies)) + result = client.companies.find_all(:type=>'companies', :per_page => 4, :page => 5, :order => "asc", :sort => "created_at").map(&:name) + _(result).must_equal %W(company1 company2 company3 company4) end it "works with desc order" do test_date=1457337600 time_increment=1000 - users = [test_user_dates(email="user4@example.com", created_at=3*test_date), test_user_dates(email="user3@example.com", created_at=test_date-2*time_increment), - test_user_dates(email="user2@example.com", created_at=test_date-time_increment), test_user_dates(email="user1@example.com", created_at=test_date)] - client.expects(:get).with("/users", {:type=>'users', :per_page => 4, :page => 5, :order => "desc", :sort => "created_at"}).returns(users_pagination(include_next_link: true, per_page: 4, page: 5, total_pages: 6, total_count: 30, user_list: users)) - result = client.users.find_all(:type=>'users', :per_page => 4, :page => 5, :order => "desc", :sort => "created_at").map {|user| user.email } - result.must_equal %W(user4@example.com user3@example.com user2@example.com user1@example.com) + companies = [test_company_dates(name="company4", created_at=3*test_date), test_company_dates(name="company3", created_at=test_date-2*time_increment), + test_company_dates(name="company2", created_at=test_date-time_increment), test_company_dates(name="company1", created_at=test_date)] + client.expects(:get).with("/companies", {:type=>'companies', :per_page => 4, :page => 5, :order => "desc", :sort => "created_at"}).returns(companies_pagination(include_next_link: true, per_page: 4, page: 5, total_pages: 6, total_count: 30, company_list: companies)) + result = client.companies.find_all(:type=>'companies', :per_page => 4, :page => 5, :order => "desc", :sort => "created_at").map {|company| company.name } + _(result).must_equal %W(company4 company3 company2 company1) end end diff --git a/spec/unit/intercom/client_spec.rb b/spec/unit/intercom/client_spec.rb index 47fe6416..660c49d9 100644 --- a/spec/unit/intercom/client_spec.rb +++ b/spec/unit/intercom/client_spec.rb @@ -1,19 +1,19 @@ +# frozen_string_literal: true + require 'spec_helper' module Intercom describe Client do - let(:app_id) { 'myappid' } - let(:api_key) { 'myapikey' } + let(:token) { 'my_access_token' } let(:client) do Client.new( - app_id: app_id, - api_key: api_key, + token: token, handle_rate_limit: true ) end it 'should set the base url' do - client.base_url.must_equal('https://api.intercom.io') + _(client.base_url).must_equal('https://api.intercom.io') end it 'should have handle_rate_limit set' do @@ -22,34 +22,34 @@ module Intercom it 'should be able to change the base url' do prev = client.options(Intercom::Client.set_base_url('https://mymockintercom.io')) - client.base_url.must_equal('https://mymockintercom.io') + _(client.base_url).must_equal('https://mymockintercom.io') client.options(prev) - client.base_url.must_equal('https://api.intercom.io') + _(client.base_url).must_equal('https://api.intercom.io') end it 'should be able to change the timeouts' do prev = client.options(Intercom::Client.set_timeouts(open_timeout: 10, read_timeout: 15)) - client.timeouts.must_equal(open_timeout: 10, read_timeout: 15) + _(client.timeouts).must_equal(open_timeout: 10, read_timeout: 15) client.options(prev) - client.timeouts.must_equal(open_timeout: 30, read_timeout: 90) + _(client.timeouts).must_equal(open_timeout: 30, read_timeout: 90) end it 'should be able to change the open timeout individually' do prev = client.options(Intercom::Client.set_timeouts(open_timeout: 50)) - client.timeouts.must_equal(open_timeout: 50, read_timeout: 90) + _(client.timeouts).must_equal(open_timeout: 50, read_timeout: 90) client.options(prev) - client.timeouts.must_equal(open_timeout: 30, read_timeout: 90) + _(client.timeouts).must_equal(open_timeout: 30, read_timeout: 90) end it 'should be able to change the read timeout individually' do prev = client.options(Intercom::Client.set_timeouts(read_timeout: 50)) - client.timeouts.must_equal(open_timeout: 30, read_timeout: 50) + _(client.timeouts).must_equal(open_timeout: 30, read_timeout: 50) client.options(prev) - client.timeouts.must_equal(open_timeout: 30, read_timeout: 90) + _(client.timeouts).must_equal(open_timeout: 30, read_timeout: 90) end it 'should raise on nil credentials' do - proc { Client.new(app_id: nil, api_key: nil) }.must_raise MisconfiguredClientError + _(proc { Client.new(token: nil) }).must_raise MisconfiguredClientError end describe 'API version' do @@ -58,42 +58,41 @@ module Intercom end it 'allows api version to be provided' do - Client.new(app_id: app_id, api_key: api_key, api_version: '1.0').api_version.must_equal('1.0') + _(Client.new(token: token, api_version: '2.0').api_version).must_equal('2.0') end it 'allows api version to be nil' do # matches default behavior, and will honor version set in the Developer Hub - assert_nil(Client.new(app_id: app_id, api_key: api_key, api_version: nil).api_version) + assert_nil(Client.new(token: token, api_version: nil).api_version) end it 'allows api version to be Unstable' do - Client.new(app_id: app_id, api_key: api_key, api_version: 'Unstable').api_version.must_equal('Unstable') + _(Client.new(token: token, api_version: 'Unstable').api_version).must_equal('Unstable') end it 'raises on invalid api version' do - proc { Client.new(app_id: app_id, api_key: api_key, api_version: '0.2') }.must_raise MisconfiguredClientError + _(proc { Client.new(token: token, api_version: '0.2') }).must_raise MisconfiguredClientError end it 'raises on empty api version' do - proc { Client.new(app_id: app_id, api_key: api_key, api_version: '') }.must_raise MisconfiguredClientError + _(proc { Client.new(token: token, api_version: '') }).must_raise MisconfiguredClientError end - it "assigns works" do - stub_request(:any, "https://api.intercom.io/users?id=123").to_return( - status: [200, "OK"], + it 'assigns works' do + stub_request(:any, 'https://api.intercom.io/contacts?id=123').to_return( + status: [200, 'OK'], headers: { 'X-RateLimit-Reset' => Time.now.utc + 10 }, - body: { "test": "testing" }.to_json + body: { "test": 'testing' }.to_json ) - client.get("/users", { id: "123" }) + client.get('/contacts', id: '123') end end describe 'OAuth clients' do it 'supports "token"' do client = Client.new(token: 'foo') - client.username_part.must_equal('foo') - client.password_part.must_equal('') + _(client.token).must_equal('foo') end end end diff --git a/spec/unit/intercom/company_spec.rb b/spec/unit/intercom/company_spec.rb index 71fc41b2..c7e1c646 100644 --- a/spec/unit/intercom/company_spec.rb +++ b/spec/unit/intercom/company_spec.rb @@ -1,40 +1,38 @@ require "spec_helper" describe Intercom::Company do - let (:client) { Intercom::Client.new(app_id: "app_id", api_key: "api_key") } + let(:client) { Intercom::Client.new(token: 'token') } describe "when no response raises error" do it "on find" do client.expects(:get).with("/companies", {:company_id => "4"}).returns(nil) - proc {client.companies.find(:company_id => "4")}.must_raise Intercom::HttpError + _(proc { client.companies.find(:company_id => "4")}).must_raise Intercom::HttpError end it "on find_all" do client.expects(:get).with("/companies", {}).returns(nil) - proc {client.companies.all.each {|company| }}.must_raise Intercom::HttpError + _(proc { client.companies.all.each {|company| }}).must_raise Intercom::HttpError end it "on load" do client.expects(:get).with("/companies", {:company_id => "4"}).returns({"type" =>"user", "id" =>"aaaaaaaaaaaaaaaaaaaaaaaa", "company_id" => "4", "name" => "MyCo"}) company = client.companies.find(:company_id => "4") client.expects(:get).with("/companies/aaaaaaaaaaaaaaaaaaaaaaaa", {}).returns(nil) - proc {client.companies.load(company)}.must_raise Intercom::HttpError + _(proc { client.companies.load(company)}).must_raise Intercom::HttpError end end - it "gets users in a company by intercom ID" do - client.expects(:get).with("/companies/abc123/users", {}).returns(page_of_users(false)) - client.companies.users_by_intercom_company_id("abc123").each { |u| } - end - - it "gets users in a company by external company ID" do - client.expects(:get).with("/companies", { company_id: "abc123", type: "user" }).returns(page_of_users(false)) - client.companies.users_by_company_id("abc123").each { |u| } - end - it "finds a company" do client.expects(:get).with("/companies/531ee472cce572a6ec000006", {}).returns(test_company) company = client.companies.find(id: "531ee472cce572a6ec000006") - company.name.must_equal("Blue Sun") + _(company.name).must_equal("Blue Sun") + end + + it "returns a collection proxy for listing contacts" do + company = Intercom::Company.new("id" => "1") + proxy = company.contacts + _(proxy.resource_name).must_equal 'contacts' + _(proxy.url).must_equal '/companies/1/contacts' + _(proxy.resource_class).must_equal Intercom::Contact end end diff --git a/spec/unit/intercom/contact_spec.rb b/spec/unit/intercom/contact_spec.rb index c08615e6..96aab94b 100644 --- a/spec/unit/intercom/contact_spec.rb +++ b/spec/unit/intercom/contact_spec.rb @@ -1,59 +1,315 @@ -require "spec_helper" +# frozen_string_literal: true -describe "Intercom::Contact" do - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } +require 'spec_helper' + +describe Intercom::Contact do + let(:client) { Intercom::Client.new(token: 'token') } it 'should be listable' do proxy = client.contacts.all - proxy.resource_name.must_equal 'contacts' - proxy.finder_url.must_equal '/contacts' - proxy.resource_class.must_equal Intercom::Contact + _(proxy.resource_name).must_equal 'contacts' + _(proxy.url).must_equal '/contacts' + _(proxy.resource_class).must_equal Intercom::Contact end - it 'should not throw ArgumentErrors when there are no parameters' do - client.expects(:post) - client.contacts.create + it 'should throw an ArgumentError when there are no parameters' do + _(proc { client.contacts.create }).must_raise(ArgumentError) end - it 'can update a contact with an id' do - contact = Intercom::Contact.new(:id => "de45ae78gae1289cb") - client.expects(:put).with("/contacts/de45ae78gae1289cb", {'custom_attributes' => {}}) + it "to_hash'es itself" do + created_at = Time.now + contact = Intercom::Contact.new(email: 'jim@example.com', contact_id: '12345', created_at: created_at, name: 'Jim Bob') + as_hash = contact.to_hash + _(as_hash['email']).must_equal 'jim@example.com' + _(as_hash['contact_id']).must_equal '12345' + _(as_hash['created_at']).must_equal created_at.to_i + _(as_hash['name']).must_equal 'Jim Bob' + end + + it 'presents created_at and last_impression_at as Date' do + now = Time.now + contact = Intercom::Contact.new(created_at: now, last_impression_at: now) + _(contact.created_at).must_be_kind_of Time + _(contact.created_at.to_s).must_equal now.to_s + _(contact.last_impression_at).must_be_kind_of Time + _(contact.last_impression_at.to_s).must_equal now.to_s + end + + it 'is throws a Intercom::AttributeNotSetError on trying to access an attribute that has not been set' do + contact = Intercom::Contact.new + _(proc { contact.foo_property }).must_raise Intercom::AttributeNotSetError + end + + it 'presents a complete contact record correctly' do + contact = Intercom::Contact.new(test_contact) + _(contact.external_id).must_equal 'id-from-customers-app' + _(contact.email).must_equal 'bob@example.com' + _(contact.name).must_equal 'Joe Schmoe' + _(contact.workspace_id).must_equal 'the-workspace-id' + _(contact.session_count).must_equal 123 + _(contact.created_at.to_i).must_equal 1_401_970_114 + _(contact.remote_created_at.to_i).must_equal 1_393_613_864 + _(contact.updated_at.to_i).must_equal 1_401_970_114 + + _(contact.avatar).must_be_kind_of Intercom::Avatar + _(contact.avatar.image_url).must_equal 'https://graph.facebook.com/1/picture?width=24&height=24' + + _(contact.notes).must_be_kind_of Intercom::BaseCollectionProxy + _(contact.tags).must_be_kind_of Intercom::BaseCollectionProxy + _(contact.companies).must_be_kind_of Intercom::ClientCollectionProxy + + _(contact.custom_attributes).must_be_kind_of Intercom::Lib::FlatStore + _(contact.custom_attributes['a']).must_equal 'b' + _(contact.custom_attributes['b']).must_equal 2 + + _(contact.social_profiles.size).must_equal 4 + twitter_account = contact.social_profiles.first + _(twitter_account).must_be_kind_of Intercom::SocialProfile + _(twitter_account.name).must_equal 'twitter' + _(twitter_account.username).must_equal 'abc' + _(twitter_account.url).must_equal 'http://twitter.com/abc' + + _(contact.location_data).must_be_kind_of Intercom::LocationData + _(contact.location_data.city_name).must_equal 'Dublin' + _(contact.location_data.continent_code).must_equal 'EU' + _(contact.location_data.country_name).must_equal 'Ireland' + _(contact.location_data.latitude).must_equal '90' + _(contact.location_data.longitude).must_equal '10' + _(contact.location_data.country_code).must_equal 'IRL' + + _(contact.unsubscribed_from_emails).must_equal true + _(contact.user_agent_data).must_equal 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11' + end + + it 'allows easy setting of custom data' do + now = Time.now + contact = Intercom::Contact.new + contact.custom_attributes['mad'] = 123 + contact.custom_attributes['other'] = now.to_i + contact.custom_attributes['thing'] = 'yay' + _(contact.to_hash['custom_attributes']).must_equal 'mad' => 123, 'other' => now.to_i, 'thing' => 'yay' + end + + it 'rejects nested data structures in custom_attributes' do + contact = Intercom::Contact.new + + _(proc { contact.custom_attributes['thing'] = [1] }).must_raise(ArgumentError) + _(proc { contact.custom_attributes['thing'] = { 1 => 2 } }).must_raise(ArgumentError) + _(proc { contact.custom_attributes['thing'] = { 1 => { 2 => 3 } } }).must_raise(ArgumentError) + + contact = Intercom::Contact.new(test_contact) + _(proc { contact.custom_attributes['thing'] = [1] }).must_raise(ArgumentError) + end + + describe 'incrementing custom_attributes fields' do + before :each do + @now = Time.now + @contact = Intercom::Contact.new('email' => 'jo@example.com', :external_id => 'i-1224242', :custom_attributes => { 'mad' => 123, 'another' => 432, 'other' => @now.to_i, :thing => 'yay' }) + end + + it 'increments up by 1 with no args' do + @contact.increment('mad') + _(@contact.to_hash['custom_attributes']['mad']).must_equal 124 + end + + it 'increments up by given value' do + @contact.increment('mad', 4) + _(@contact.to_hash['custom_attributes']['mad']).must_equal 127 + end + + it 'increments down by given value' do + @contact.increment('mad', -1) + _(@contact.to_hash['custom_attributes']['mad']).must_equal 122 + end + + it 'can increment new custom data fields' do + @contact.increment('new_field', 3) + _(@contact.to_hash['custom_attributes']['new_field']).must_equal 3 + end + + it 'can call increment on the same key twice and increment by 2' do + @contact.increment('mad') + @contact.increment('mad') + _(@contact.to_hash['custom_attributes']['mad']).must_equal 125 + end + end + + describe 'decrementing custom_attributes fields' do + before :each do + @now = Time.now + @contact = Intercom::Contact.new('email' => 'jo@example.com', :external_id => 'i-1224242', :custom_attributes => { 'mad' => 123, 'another' => 432, 'other' => @now.to_i, :thing => 'yay' }) + end + + it 'decrements down by 1 with no args' do + @contact.decrement('mad') + _(@contact.to_hash['custom_attributes']['mad']).must_equal 122 + end + + it 'decrements down by given value' do + @contact.decrement('mad', 3) + _(@contact.to_hash['custom_attributes']['mad']).must_equal 120 + end + + it 'can decrement new custom data fields' do + @contact.decrement('new_field', 5) + _(@contact.to_hash['custom_attributes']['new_field']).must_equal(-5) + end + + it 'can call decrement on the same key twice and decrement by 2' do + @contact.decrement('mad') + @contact.decrement('mad') + _(@contact.to_hash['custom_attributes']['mad']).must_equal 121 + end + end + + it 'saves a contact (always sends custom_attributes)' do + contact = Intercom::Contact.new('email' => 'jo@example.com', :external_id => 'i-1224242') + client.expects(:post).with('/contacts', 'email' => 'jo@example.com', 'external_id' => 'i-1224242', 'custom_attributes' => {}).returns('email' => 'jo@example.com', 'external_id' => 'i-1224242') client.contacts.save(contact) end - describe 'converting' do - let(:contact) { Intercom::Contact.from_api(user_id: 'contact_id') } - let(:user) { Intercom::User.from_api(id: 'user_id') } + it 'can save a contact with a nil email' do + contact = Intercom::Contact.new('email' => nil, :external_id => 'i-1224242') + client.expects(:post).with('/contacts', 'custom_attributes' => {}, 'email' => nil, 'external_id' => 'i-1224242').returns('email' => nil, 'external_id' => 'i-1224242') + client.contacts.save(contact) + end - it do - client.expects(:post).with( - "/contacts/convert", - { - contact: { user_id: contact.user_id }, - user: { 'id' => user.id } - } - ).returns(test_user) + it 'can use client.contacts.create for convenience' do + client.expects(:post).with('/contacts', 'custom_attributes' => {}, 'email' => 'jo@example.com', 'external_id' => 'i-1224242').returns('email' => 'jo@example.com', 'external_id' => 'i-1224242') + contact = client.contacts.create('email' => 'jo@example.com', :external_id => 'i-1224242') + _(contact.email).must_equal 'jo@example.com' + end - client.contacts.convert(contact, user) + it 'updates the contact with attributes as set by the server' do + client.expects(:post).with('/contacts', 'email' => 'jo@example.com', 'external_id' => 'i-1224242', 'custom_attributes' => {}).returns('email' => 'jo@example.com', 'external_id' => 'i-1224242', 'session_count' => 4) + contact = client.contacts.create('email' => 'jo@example.com', :external_id => 'i-1224242') + _(contact.session_count).must_equal 4 + end + + it 'allows setting dates to nil without converting them to 0' do + client.expects(:post).with('/contacts', 'email' => 'jo@example.com', 'custom_attributes' => {}, 'remote_created_at' => nil).returns('email' => 'jo@example.com') + contact = client.contacts.create('email' => 'jo@example.com', 'remote_created_at' => nil) + assert_nil contact.remote_created_at + end + + it 'sets/gets rw keys' do + params = { 'email' => 'me@example.com', :external_id => 'abc123', 'name' => 'Bob Smith', 'last_seen_ip' => '1.2.3.4', 'last_seen_contact_agent' => 'ie6', 'created_at' => Time.now } + contact = Intercom::Contact.new(params) + custom_attributes = (params.keys + ['custom_attributes']).map(&:to_s).sort + _(contact.to_hash.keys.sort).must_equal custom_attributes + params.keys.each do |key| + _(contact.send(key).to_s).must_equal params[key].to_s end end - it "returns a ClientCollectionProxy for all without making any requests" do + it 'will allow extra attributes in response from api' do + contact = Intercom::Contact.send(:from_api, 'new_param' => 'some value') + _(contact.new_param).must_equal 'some value' + end + + it 'returns a BaseCollectionProxy for all without making any requests' do client.expects(:execute_request).never all = client.contacts.all - all.must_be_instance_of(Intercom::ClientCollectionProxy) + _(all).must_be_instance_of(Intercom::BaseCollectionProxy) + end + + it 'can print contacts without crashing' do + client.expects(:get).with('/contacts', 'email' => 'bo@example.com').returns(test_contact) + contact = client.contacts.find('email' => 'bo@example.com') + + begin + orignal_stdout = $stdout + $stdout = StringIO.new + + puts contact + p contact + ensure + $stdout = orignal_stdout + end end - it "deletes a contact" do - contact = Intercom::Contact.new("id" => "1") - client.expects(:delete).with("/contacts/1", {}).returns(contact) + it 'fetches a contact' do + client.expects(:get).with('/contacts', 'email' => 'bo@example.com').returns(test_contact) + contact = client.contacts.find('email' => 'bo@example.com') + _(contact.email).must_equal 'bob@example.com' + _(contact.name).must_equal 'Joe Schmoe' + _(contact.session_count).must_equal 123 + end + + it 'can update a contact with an id' do + contact = Intercom::Contact.new(id: 'de45ae78gae1289cb') + client.expects(:put).with('/contacts/de45ae78gae1289cb', 'custom_attributes' => {}) + client.contacts.save(contact) + end + + it 'deletes a contact' do + contact = Intercom::Contact.new('id' => '1') + client.expects(:delete).with('/contacts/1', {}).returns(contact) client.contacts.delete(contact) end - it "sends a request for a hard deletion" do - contact = Intercom::Contact.new("id" => "1") - client.expects(:post).with("/user_delete_requests", {intercom_user_id: "1"}).returns({id: contact.id}) - client.contacts.request_hard_delete(contact) + describe 'merging' do + let(:lead) { Intercom::Contact.from_api(external_id: 'contact_id', role: 'lead') } + let(:user) { Intercom::Contact.from_api(id: 'external_id', role: 'user') } + + it 'should be successful with a lead and user' do + client.expects(:post).with('/contacts/merge', + from: lead.id, into: user.id).returns(test_contact) + + client.contacts.merge(lead, user) + end end + describe 'nested resources' do + let(:contact) { Intercom::Contact.new(id: '1', client: client) } + let(:company) { Intercom::Company.new(id: '1') } + let(:tag) { Intercom::Tag.new(id: '1') } + let(:note) { Intercom::Note.new(body: "

Text for the note

") } + + it 'returns a collection proxy for listing notes' do + proxy = contact.notes + _(proxy.resource_name).must_equal 'notes' + _(proxy.url).must_equal '/contacts/1/notes' + _(proxy.resource_class).must_equal Intercom::Note + end + + it 'returns a collection proxy for listing tags' do + proxy = contact.tags + _(proxy.resource_name).must_equal 'tags' + _(proxy.url).must_equal '/contacts/1/tags' + _(proxy.resource_class).must_equal Intercom::Tag + end + + it 'returns a collection proxy for listing companies' do + proxy = contact.companies + _(proxy.resource_name).must_equal 'companies' + _(proxy.url).must_equal '/contacts/1/companies' + _(proxy.resource_class).must_equal Intercom::Company + end + + it 'adds a note to a contact' do + client.expects(:post).with('/contacts/1/notes', {body: note.body}).returns(note.to_hash) + contact.create_note({body: note.body}) + end + + it 'adds a tag to a contact' do + client.expects(:post).with('/contacts/1/tags', "id": tag.id).returns(tag.to_hash) + contact.add_tag({ "id": tag.id }) + end + + it 'removes a tag from a contact' do + client.expects(:delete).with("/contacts/1/tags/#{tag.id}", "id": tag.id ).returns(tag.to_hash) + contact.remove_tag({ "id": tag.id }) + end + + it 'adds a contact to a company' do + client.expects(:post).with('/contacts/1/companies', "id": company.id).returns(test_company) + contact.add_company({ "id": tag.id }) + end + + it 'removes a contact from a company' do + client.expects(:delete).with("/contacts/1/companies/#{company.id}", "id": tag.id ).returns(test_company) + contact.remove_company({ "id": tag.id }) + end + end end diff --git a/spec/unit/intercom/conversation_spec.rb b/spec/unit/intercom/conversation_spec.rb index 0f0c74cf..50a494da 100644 --- a/spec/unit/intercom/conversation_spec.rb +++ b/spec/unit/intercom/conversation_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe "Intercom::Conversation" do - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } + let(:client) { Intercom::Client.new(token: 'token') } it "gets a conversation" do client.expects(:get).with("/conversations/147", {}).returns(test_conversation) @@ -53,11 +53,33 @@ client.conversations.snooze(id: '147', admin_id: '123', snoozed_until: tomorrow) end - # it "creates a subscription" do - # client.expects(:post).with("/subscriptions", {'url' => "http://example.com", 'topics' => ["user.created"]}).returns(test_subscription) - # subscription = client.subscriptions.create(:url => "http://example.com", :topics => ["user.created"]) - # subscription.request.topics[0].must_equal "user.created" - # subscription.request.url.must_equal "http://example.com" - # end + it 'runs assignment rules on a conversation' do + client.expects(:post).with('/conversations/147/run_assignment_rules').returns(test_conversation) + client.conversations.run_assignment_rules('147') + end + + describe 'nested resources' do + let(:conversation) { Intercom::Conversation.new('id' => '1', 'client' => client) } + let(:tag) { Intercom::Tag.new('id' => '1') } + + it 'adds a tag to a conversation' do + client.expects(:post).with("/conversations/1/tags", { 'id': tag.id, 'admin_id': test_admin['id'] }).returns(tag.to_hash) + conversation.add_tag({ "id": tag.id, "admin_id": test_admin['id'] }) + end + it 'does not add a tag to a conversation if no admin_id is passed' do + client.expects(:post).with("/conversations/1/tags", { 'id': tag.id }).returns(nil) + _(proc { conversation.add_tag({ "id": tag.id }) }).must_raise Intercom::HttpError + end + + it 'removes a tag from a conversation' do + client.expects(:delete).with("/conversations/1/tags/1", { "id": tag.id, "admin_id": test_admin['id'] }).returns(tag.to_hash) + conversation.remove_tag({ "id": tag.id, "admin_id": test_admin['id'] }) + end + + it 'does not remove a tag from a conversation if no admin_id is passed' do + client.expects(:delete).with("/conversations/1/tags/1", { "id": tag.id }).returns(nil) + _(proc { conversation.remove_tag({ "id": tag.id }) }).must_raise Intercom::HttpError + end + end end diff --git a/spec/unit/intercom/count_spec.rb b/spec/unit/intercom/count_spec.rb index e0990018..208e0790 100644 --- a/spec/unit/intercom/count_spec.rb +++ b/spec/unit/intercom/count_spec.rb @@ -1,24 +1,24 @@ require "spec_helper" describe "Intercom::Count" do - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } + let(:client) { Intercom::Client.new(token: 'token') } it 'should get app wide counts' do client.expects(:get).with("/counts", {}).returns(test_app_count) counts = client.counts.for_app - counts.tag['count'].must_equal(341) + _(counts.tag['count']).must_equal(341) end it 'should get type counts' do client.expects(:get).with("/counts", {type: 'user', count: 'segment'}).returns(test_segment_count) counts = client.counts.for_type(type: 'user', count: 'segment') - counts.user['segment'][4]["segment 1"].must_equal(1) + _(counts.user['segment'][4]["segment 1"]).must_equal(1) end it 'should not include count param when nil' do client.expects(:get).with("/counts", {type: 'conversation'}).returns(test_conversation_count) counts = client.counts.for_type(type: 'conversation') - counts.conversation.must_equal({ + _(counts.conversation).must_equal({ "assigned" => 1, "closed" => 15, "open" => 1, diff --git a/spec/unit/intercom/data_attribute_spec.rb b/spec/unit/intercom/data_attribute_spec.rb new file mode 100644 index 00000000..c8d0b3e4 --- /dev/null +++ b/spec/unit/intercom/data_attribute_spec.rb @@ -0,0 +1,40 @@ +require "spec_helper" + +describe "Intercom::DataAttribute" do + let(:client) { Intercom::Client.new(token: 'token') } + + it "returns a CollectionProxy for all without making any requests" do + client.expects(:execute_request).never + all = client.data_attributes.all + _(all).must_be_instance_of(Intercom::ClientCollectionProxy) + end + + + it "creates a new data attribute" do + client.expects(:post).with("/data_attributes", { "name" => "blah", "model" => "contact", "data_type" => "string" }).returns(status: 200) + client.data_attributes.create("name": "blah", + "model": "contact", + "data_type": "string" ) + end + + it "updates an existing attribute" do + attribute = Intercom::DataAttribute.new("id": 123, + "name": "blah", + "model": "contact", + "data_type": "string") + client.expects(:put).with("/data_attributes/#{attribute.id}", { "name" => "New name", "model" => "contact", "data_type" => "string" }) + attribute.name = "New name" + client.data_attributes.save(attribute) + _(attribute.name).must_equal "New name" + end + + it 'gets a list of attributes' do + client.expects(:get).with("/data_attributes", {}).returns(test_data_attribute_list) + client.data_attributes.all.each { |d| } + end + + it 'finds all customer or company attributes' do + client.expects(:get).with("/data_attributes", { "model": "contact" }).returns(test_data_attribute_list) + client.data_attributes.find_all({"model": "contact"}).each { |d| } + end +end diff --git a/spec/unit/intercom/deprecated_leads_collection_proxy_spec.rb b/spec/unit/intercom/deprecated_leads_collection_proxy_spec.rb new file mode 100644 index 00000000..9c90a9e1 --- /dev/null +++ b/spec/unit/intercom/deprecated_leads_collection_proxy_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Intercom::ClientCollectionProxy do + let(:client) { Intercom::Client.new(token: 'token') } + let(:lead_json) do + { 'type' => 'contact.list', 'contacts' => [{ 'type' => 'contact', 'id' => 'id' }] } + end + + it 'stops iterating if no next link' do + client.expects(:get).with('/contacts', {}).returns(lead_json) + client.deprecated__leads.all.each do |company| + _(company.class).must_equal Intercom::Lead + end + end +end diff --git a/spec/unit/intercom/event_spec.rb b/spec/unit/intercom/event_spec.rb index a87ce70d..125d9996 100644 --- a/spec/unit/intercom/event_spec.rb +++ b/spec/unit/intercom/event_spec.rb @@ -4,7 +4,7 @@ let(:user) {Intercom::User.new("email" => "jim@example.com", :user_id => "12345", :created_at => Time.now, :name => "Jim Bob")} let(:created_time) {Time.now - 300} - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } + let(:client) { Intercom::Client.new(token: 'token') } it 'gets events for a user' do client.expects(:get).with('/events', type: 'user', email: 'joe@example.com').returns(test_event_list) @@ -12,14 +12,14 @@ end it "has the correct collection proxy class" do - client.events.collection_proxy_class.must_equal Intercom::EventCollectionProxy + _(client.events.collection_proxy_class).must_equal Intercom::EventCollectionProxy end it "stops iterating if no next link" do client.expects(:get).with("/events", type: 'user', email: 'joe@example.com').returns(page_of_events(false)) event_names = [] client.events.find_all(type: 'user', email: 'joe@example.com').each { |event| event_names << event.event_name } - event_names.must_equal %W(invited-friend) + _(event_names).must_equal %W(invited-friend) end it "keeps iterating if next link" do @@ -27,7 +27,7 @@ client.expects(:get).with("https://api.intercom.io/events?type=user&intercom_user_id=55a3b&before=144474756550", {}).returns(page_of_events(false)) event_names = [] client.events.find_all(type: 'user', email: 'joe@example.com').each { |event| event_names << event.event_name } - event_names.must_equal %W(invited-friend invited-friend) + _(event_names).must_equal %W(invited-friend invited-friend) end it "creates an event with metadata" do @@ -74,15 +74,15 @@ "state"=>"completed" } ] - } + } } let(:bulk_request) { - { + { items: [ { method: "post", data_type: "event", - data:{ + data: { event_name: "ordered-item", created_at: 1438944980, user_id: "314159", @@ -95,7 +95,7 @@ { method: "post", data_type: "event", - data:{ + data: { event_name: "invited-friend", created_at: 1438944979, user_id: "314159", @@ -143,9 +143,7 @@ end it "does not submit delete jobs" do - lambda { client.events.submit_bulk_job(delete_items: events) }.must_raise ArgumentError + _(lambda { client.events.submit_bulk_job(delete_items: events) }).must_raise ArgumentError end - end - end diff --git a/spec/unit/intercom/job_spec.rb b/spec/unit/intercom/job_spec.rb index 1b1dd75e..f21a49ee 100644 --- a/spec/unit/intercom/job_spec.rb +++ b/spec/unit/intercom/job_spec.rb @@ -1,41 +1,41 @@ require "spec_helper" describe "jobs" do - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } + let(:client) { Intercom::Client.new(token: 'token') } let (:job) { { - "app_id"=>"app_id", - "id"=>"super_awesome_job", - "created_at"=>1446033421, - "completed_at"=>1446048736, - "closing_at"=>1446034321, - "updated_at"=>1446048736, - "name"=>"api_bulk_job", - "state"=>"completed", - "links"=> + "app_id" => "app_id", + "id" => "super_awesome_job", + "created_at" => 1446033421, + "completed_at" => 1446048736, + "closing_at" => 1446034321, + "updated_at" => 1446048736, + "name" => "api_bulk_job", + "state" => "completed", + "links" => { - "error"=>"https://api.intercom.io/jobs/super_awesome_job/error", - "self"=>"https://api.intercom.io/jobs/super_awesome_job" + "error" => "https://api.intercom.io/jobs/super_awesome_job/error", + "self" => "https://api.intercom.io/jobs/super_awesome_job" }, - "tasks"=> + "tasks" => [ { - "id"=>"super_awesome_task", - "item_count"=>2, - "created_at"=>1446033421, - "started_at"=>1446033709, - "completed_at"=>1446033709, - "state"=>"completed" + "id" => "super_awesome_task", + "item_count" => 2, + "created_at" => 1446033421, + "started_at" => 1446033709, + "completed_at" => 1446033709, + "state" => "completed" } ] - } + } } let (:error_feed) { { - "app_id"=>"app_id", - "job_id"=>"super_awesome_job", - "pages"=>{}, - "items"=>[] + "app_id" => "app_id", + "job_id" => "super_awesome_job", + "pages" => {}, + "items" => [] } } diff --git a/spec/unit/intercom/lead_spec.rb b/spec/unit/intercom/lead_spec.rb new file mode 100644 index 00000000..4bbcd989 --- /dev/null +++ b/spec/unit/intercom/lead_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Intercom::Lead' do + let (:client) { Intercom::Client.new(token: 'token') } + + it 'should be listable' do + proxy = client.deprecated__leads.all + _(proxy.resource_name).must_equal 'contacts' + _(proxy.resource_class).must_equal Intercom::Lead + end + + it 'should not throw ArgumentErrors when there are no parameters' do + client.expects(:post) + client.deprecated__leads.create + end + + it 'can update a lead with an id' do + lead = Intercom::Lead.new(id: 'de45ae78gae1289cb') + client.expects(:put).with('/contacts/de45ae78gae1289cb', 'custom_attributes' => {}) + client.deprecated__leads.save(lead) + end + + describe 'converting' do + let(:lead) { Intercom::Lead.from_api(user_id: 'contact_id') } + let(:user) { Intercom::User.from_api(id: 'user_id') } + + it do + client.expects(:post).with( + '/contacts/convert', + contact: { user_id: lead.user_id }, + user: { 'id' => user.id } + ).returns(test_user) + + client.deprecated__leads.convert(lead, user) + end + end + + it 'returns a DeprecatedLeadsCollectionProxy for all without making any requests' do + client.expects(:execute_request).never + all = client.deprecated__leads.all + _(all).must_be_instance_of(Intercom::DeprecatedLeadsCollectionProxy) + end + + it 'deletes a lead' do + lead = Intercom::Lead.new('id' => '1') + client.expects(:delete).with('/contacts/1', {}).returns(lead) + client.deprecated__leads.delete(lead) + end + + it 'sends a request for a hard deletion' do + lead = Intercom::Lead.new('id' => '1') + client.expects(:post).with('/user_delete_requests', intercom_user_id: '1').returns(id: lead.id) + client.deprecated__leads.request_hard_delete(lead) + end +end diff --git a/spec/unit/intercom/lib/flat_store_spec.rb b/spec/unit/intercom/lib/flat_store_spec.rb index 435cc1eb..3188b579 100644 --- a/spec/unit/intercom/lib/flat_store_spec.rb +++ b/spec/unit/intercom/lib/flat_store_spec.rb @@ -1,29 +1,31 @@ -require "spec_helper" +# frozen_string_literal: true + +require 'spec_helper' describe Intercom::Lib::FlatStore do - it "raises if you try to set or merge in nested hash structures" do - data = Intercom::Lib::FlatStore.new() - proc { data["thing"] = [1] }.must_raise ArgumentError - proc { data["thing"] = {1 => 2} }.must_raise ArgumentError - proc { Intercom::Lib::FlatStore.new({1 => {2 => 3}}) }.must_raise ArgumentError + it 'raises if you try to set or merge in nested hash structures' do + data = Intercom::Lib::FlatStore.new + _(proc { data['thing'] = [1] }).must_raise ArgumentError + _(proc { data['thing'] = { 1 => 2 } }).must_raise ArgumentError + _(proc { Intercom::Lib::FlatStore.new(1 => { 2 => 3 }) }).must_raise ArgumentError end - it "raises if you try to use a non string key" do - data =Intercom::Lib::FlatStore.new() - proc { data[1] = "something" }.must_raise ArgumentError + it 'raises if you try to use a non string key' do + data = Intercom::Lib::FlatStore.new + _(proc { data[1] = 'something' }).must_raise ArgumentError end - it "sets and merges valid entries" do - data = Intercom::Lib::FlatStore.new() - data["a"] = 1 + it 'sets and merges valid entries' do + data = Intercom::Lib::FlatStore.new + data['a'] = 1 data[:b] = 2 - data[:a].must_equal 1 - data["b"].must_equal 2 - data[:b].must_equal 2 - data = Intercom::Lib::FlatStore.new({"a" => 1, :b => 2}) - data["a"].must_equal 1 - data[:a].must_equal 1 - data["b"].must_equal 2 - data[:b].must_equal 2 + _(data[:a]).must_equal 1 + _(data['b']).must_equal 2 + _(data[:b]).must_equal 2 + data = Intercom::Lib::FlatStore.new('a' => 1, :b => 2) + _(data['a']).must_equal 1 + _(data[:a]).must_equal 1 + _(data['b']).must_equal 2 + _(data[:b]).must_equal 2 end end diff --git a/spec/unit/intercom/message_spec.rb b/spec/unit/intercom/message_spec.rb index 51890a98..f027a1e3 100644 --- a/spec/unit/intercom/message_spec.rb +++ b/spec/unit/intercom/message_spec.rb @@ -3,7 +3,7 @@ describe "Intercom::Message" do let (:user) {Intercom::User.new("email" => "jim@example.com", :user_id => "12345", :created_at => Time.now, :name => "Jim Bob")} - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } + let(:client) { Intercom::Client.new(token: 'token') } it 'creates an user message with symbol keys' do client.expects(:post).with('/messages', {'from' => { :type => 'user', :email => 'jim@example.com'}, 'body' => 'halp'}).returns(:status => 200) diff --git a/spec/unit/intercom/note_spec.rb b/spec/unit/intercom/note_spec.rb index e25899d7..1715b5ff 100644 --- a/spec/unit/intercom/note_spec.rb +++ b/spec/unit/intercom/note_spec.rb @@ -1,13 +1,7 @@ require "spec_helper" describe "notes" do - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } - - it "creates a note" do - client.expects(:post).with("/notes", {"body" => "Note to leave on user"}).returns({"body" => "

Note to leave on user

", "created_at" => 1234567890}) - note = client.notes.create("body" => "Note to leave on user") - note.body.must_equal "

Note to leave on user

" - end + let(:client) { Intercom::Client.new(token: 'token') } it 'gets a note' do client.expects(:get).with("/notes/123", {}).returns({"id" => "123", "body" => "

Note to leave on user

", "created_at" => 1234567890}) @@ -18,9 +12,9 @@ params = {"body" => "Note body", "email" => "me@example.com", :user_id => "abc123"} note = Intercom::Note.new(params) - note.to_hash.keys.sort.must_equal params.keys.map(&:to_s).sort - params.keys.each do | key| - note.send(key).must_equal params[key] + _(note.to_hash.keys.sort).must_equal params.keys.map(&:to_s).sort + params.keys.each do |key| + _(note.send(key)).must_equal params[key] end end end diff --git a/spec/unit/intercom/request_spec.rb b/spec/unit/intercom/request_spec.rb index 48556927..2310bc79 100644 --- a/spec/unit/intercom/request_spec.rb +++ b/spec/unit/intercom/request_spec.rb @@ -9,7 +9,7 @@ let(:default_body) { { data: "test" }.to_json } def execute! - req.execute(uri, username: 'ted', secret: '') + req.execute(uri, token: 'test-token') end it 'should call sleep for rate limit error three times and raise a rate limit error otherwise' do diff --git a/spec/unit/intercom/scroll_collection_proxy_spec.rb b/spec/unit/intercom/scroll_collection_proxy_spec.rb index ff4eefd1..49ab7aaf 100644 --- a/spec/unit/intercom/scroll_collection_proxy_spec.rb +++ b/spec/unit/intercom/scroll_collection_proxy_spec.rb @@ -1,56 +1,57 @@ -require "spec_helper" +# frozen_string_literal: true + +require 'spec_helper' describe Intercom::ScrollCollectionProxy do - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } + let(:client) { Intercom::Client.new(token: 'token') } - it "stops iterating if no users returned" do - client.expects(:get).with("/users/scroll", '').returns(users_scroll(false)) - emails = [] - client.users.scroll.each { |user| emails << user.email } - emails.must_equal %W() + it 'stops iterating if no companies returned' do + client.expects(:get).with('/companies/scroll', '').returns(companies_scroll(false)) + names = [] + client.companies.scroll.each { |company| names << company.name } + _(names).must_equal %w[] end - it "keeps iterating if users returned" do - client.expects(:get).with("/users/scroll", '').returns(users_scroll(true)) - client.expects(:get).with('/users/scroll', {:scroll_param => 'da6bbbac-25f6-4f07-866b-b911082d7'}).returns(users_scroll(false)) - emails = [] - client.users.scroll.each { |user| emails << user.email } + it 'keeps iterating if companies returned' do + client.expects(:get).with('/companies/scroll', '').returns(companies_scroll(true)) + client.expects(:get).with('/companies/scroll', scroll_param: 'da6bbbac-25f6-4f07-866b-b911082d7').returns(companies_scroll(false)) + names = [] + client.companies.scroll.each { |company| names << company.name } end - it "supports indexed array access" do - client.expects(:get).with("/users/scroll", '').returns(users_scroll(true)) - client.users.scroll[0].email.must_equal 'user1@example.com' + it 'supports indexed array access' do + client.expects(:get).with('/companies/scroll', '').returns(companies_scroll(true)) + _(client.companies.scroll[0].name).must_equal 'company1' end - it "supports map" do - client.expects(:get).with("/users/scroll", '').returns(users_scroll(true)) - client.expects(:get).with('/users/scroll', {:scroll_param => 'da6bbbac-25f6-4f07-866b-b911082d7'}).returns(users_scroll(false)) - emails = client.users.scroll.map { |user| user.email } - emails.must_equal %W(user1@example.com user2@example.com user3@example.com) + it 'supports map' do + client.expects(:get).with('/companies/scroll', '').returns(companies_scroll(true)) + client.expects(:get).with('/companies/scroll', scroll_param: 'da6bbbac-25f6-4f07-866b-b911082d7').returns(companies_scroll(false)) + names = client.companies.scroll.map(&:name) + _(names).must_equal %w[company1 company2 company3] end - it "returns one page scroll" do - client.expects(:get).with("/users/scroll", '').returns(users_scroll(true)) - scroll = client.users.scroll.next - emails = [] - scroll.records.each {|usr| emails << usr.email} - emails.must_equal %W(user1@example.com user2@example.com user3@example.com) + it 'returns one page scroll' do + client.expects(:get).with('/companies/scroll', '').returns(companies_scroll(true)) + scroll = client.companies.scroll.next + names = [] + scroll.records.each { |usr| names << usr.name } + _(names).must_equal %w[company1 company2 company3] end - it "keeps iterating if called with scroll_param" do - client.expects(:get).with("/users/scroll", '').returns(users_scroll(true)) - client.expects(:get).with('/users/scroll', {:scroll_param => 'da6bbbac-25f6-4f07-866b-b911082d7'}).returns(users_scroll(true)) - scroll = client.users.scroll.next - scroll = client.users.scroll.next('da6bbbac-25f6-4f07-866b-b911082d7') - emails =[] - scroll.records.each {|usr| puts usr.email} + it 'keeps iterating if called with scroll_param' do + client.expects(:get).with('/companies/scroll', '').returns(companies_scroll(true)) + client.expects(:get).with('/companies/scroll', scroll_param: 'da6bbbac-25f6-4f07-866b-b911082d7').returns(companies_scroll(true)) + scroll = client.companies.scroll.next + scroll = client.companies.scroll.next('da6bbbac-25f6-4f07-866b-b911082d7') + scroll.records.each(&:name) end - it "works with an empty list" do - client.expects(:get).with("/users/scroll", '').returns(users_scroll(false)) - scroll = client.users.scroll.next - emails = [] - scroll.records.each {|usr| emails << usr.email} - emails.must_equal %W() + it 'works with an empty list' do + client.expects(:get).with('/companies/scroll', '').returns(companies_scroll(false)) + scroll = client.companies.scroll.next + names = [] + scroll.records.each { |usr| names << usr.name } + _(names).must_equal %w[] end end diff --git a/spec/unit/intercom/search_collection_proxy_spec.rb b/spec/unit/intercom/search_collection_proxy_spec.rb index 3088c06e..ffc2ee3c 100644 --- a/spec/unit/intercom/search_collection_proxy_spec.rb +++ b/spec/unit/intercom/search_collection_proxy_spec.rb @@ -1,56 +1,60 @@ require "spec_helper" describe Intercom::SearchCollectionProxy do - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } + let(:client) { Intercom::Client.new(token: 'token') } - it "send query to the customer search endpoint" do - client.expects(:post).with("/customers/search", { query: {} }). returns(page_of_customers(false)) - client.customers.search(query: {}).first + it "sends query to the contact search endpoint" do + client.expects(:post).with("/contacts/search", { query: {} }).returns(page_of_contacts(false)) + client.contacts.search(query: {}).first end - it "send query to the customer search endpoint with sort_field" do - client.expects(:post).with("/customers/search", { query: {}, sort: { field: "name" } }). returns(page_of_customers(false)) - client.customers.search(query: {}, sort_field: "name").first + it "sends query to the conversation search endpoint" do + client.expects(:post).with("/conversations/search", { query: {} }).returns(test_conversation_list) + client.conversations.search(query: {}).first end - it "send query to the customer search endpoint with sort_field and sort_order" do - client.expects(:post).with("/customers/search", { query: {}, sort: { field: "name", order: "ascending" } }). returns(page_of_customers(false)) - client.customers.search(query: {}, sort_field: "name", sort_order: "ascending").first + it "sends query to the contact search endpoint with sort_field" do + client.expects(:post).with("/contacts/search", { query: {}, sort: { field: "name" } }).returns(page_of_contacts(false)) + client.contacts.search(query: {}, sort_field: "name").first end - it "send query to the customer search endpoint with per_page" do - client.expects(:post).with("/customers/search", { query: {}, pagination: { per_page: 10 }}). returns(page_of_customers(false)) - client.customers.search(query: {}, per_page: 10).first + it "sends query to the contact search endpoint with sort_field and sort_order" do + client.expects(:post).with("/contacts/search", { query: {}, sort: { field: "name", order: "ascending" } }).returns(page_of_contacts(false)) + client.contacts.search(query: {}, sort_field: "name", sort_order: "ascending").first end - it "send query to the customer search endpoint with starting_after" do - client.expects(:post).with("/customers/search", { query: {}, pagination: { starting_after: "EnCrYpTeDsTrInG" }}). returns(page_of_customers(false)) - client.customers.search(query: {}, starting_after: "EnCrYpTeDsTrInG").first + it "sends query to the contact search endpoint with per_page" do + client.expects(:post).with("/contacts/search", { query: {}, pagination: { per_page: 10 }}).returns(page_of_contacts(false)) + client.contacts.search(query: {}, per_page: 10).first + end + + it "sends query to the contact search endpoint with starting_after" do + client.expects(:post).with("/contacts/search", { query: {}, pagination: { starting_after: "EnCrYpTeDsTrInG" }}).returns(page_of_contacts(false)) + client.contacts.search(query: {}, starting_after: "EnCrYpTeDsTrInG").first end it "stops iterating if no starting_after value" do - client.expects(:post).with("/customers/search", { query: {} }). returns(page_of_customers(false)) + client.expects(:post).with("/contacts/search", { query: {} }).returns(page_of_contacts(false)) emails = [] - client.customers.search(query: {}).each { |user| emails << user.email } - emails.must_equal %W(user1@example.com user2@example.com user3@example.com) + client.contacts.search(query: {}).each { |contact| emails << contact.email } + _(emails).must_equal %w[test1@example.com test2@example.com test3@example.com] end it "keeps iterating if starting_after value" do - client.expects(:post).with("/customers/search", { query: {} }).returns(page_of_customers(true)) - client.expects(:post).with("/customers/search", { query: {}, pagination: { starting_after: "EnCrYpTeDsTrInG" }}).returns(page_of_customers(false)) + client.expects(:post).with("/contacts/search", { query: {} }).returns(page_of_contacts(true)) + client.expects(:post).with("/contacts/search", { query: {}, pagination: { starting_after: "EnCrYpTeDsTrInG" }}).returns(page_of_contacts(false)) emails = [] - client.customers.search(query: {}).each { |user| emails << user.email } + client.contacts.search(query: {}).each { |contact| emails << contact.email } end it "supports indexed array access" do - client.expects(:post).with("/customers/search", { query: {} }).returns(page_of_customers(false)) - client.customers.search(query: {})[0].email.must_equal 'user1@example.com' + client.expects(:post).with("/contacts/search", { query: {} }).returns(page_of_contacts(false)) + _(client.contacts.search(query: {})[0].email).must_equal 'test1@example.com' end it "supports map" do - client.expects(:post).with("/customers/search", { query: {} }).returns(page_of_customers(false)) - emails = client.customers.search(query: {}).map { |user| user.email } - emails.must_equal %W(user1@example.com user2@example.com user3@example.com) + client.expects(:post).with("/contacts/search", { query: {} }).returns(page_of_contacts(false)) + emails = client.contacts.search(query: {}).map { |contact| contact.email } + _(emails).must_equal %w[test1@example.com test2@example.com test3@example.com] end - end diff --git a/spec/unit/intercom/segment_spec.rb b/spec/unit/intercom/segment_spec.rb index d883c95f..def1dfea 100644 --- a/spec/unit/intercom/segment_spec.rb +++ b/spec/unit/intercom/segment_spec.rb @@ -2,11 +2,11 @@ describe "Intercom::Segment" do - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } + let(:client) { Intercom::Client.new(token: 'token') } it 'lists segments' do client.expects(:get).with('/segments', {}).returns(segment_list) segments = client.segments.all.to_a - segments[0].name.must_equal('Active') + _(segments[0].name).must_equal('Active') end end diff --git a/spec/unit/intercom/subscription_spec.rb b/spec/unit/intercom/subscription_spec.rb index 094a3f83..42c869cd 100644 --- a/spec/unit/intercom/subscription_spec.rb +++ b/spec/unit/intercom/subscription_spec.rb @@ -1,20 +1,19 @@ require 'spec_helper' describe "Intercom::Subscription" do - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } + let(:client) { Intercom::Client.new(token: 'token') } it "gets a subscription" do client.expects(:get).with("/subscriptions/nsub_123456789", {}).returns(test_subscription) subscription = client.subscriptions.find(:id => "nsub_123456789") - subscription.request.topics[0].must_equal "user.created" - subscription.request.topics[1].must_equal "conversation.user.replied" + _(subscription.request.topics[0]).must_equal "user.created" + _(subscription.request.topics[1]).must_equal "conversation.user.replied" end it "creates a subscription" do client.expects(:post).with("/subscriptions", {'url' => "http://example.com", 'topics' => ["user.created"]}).returns(test_subscription) subscription = client.subscriptions.create(:url => "http://example.com", :topics => ["user.created"]) - subscription.request.topics[0].must_equal "user.created" - subscription.request.url.must_equal "http://example.com" + _(subscription.request.topics[0]).must_equal "user.created" + _(subscription.request.url).must_equal "http://example.com" end - end diff --git a/spec/unit/intercom/tag_spec.rb b/spec/unit/intercom/tag_spec.rb index fe556445..07a7d4d9 100644 --- a/spec/unit/intercom/tag_spec.rb +++ b/spec/unit/intercom/tag_spec.rb @@ -1,23 +1,31 @@ +# frozen_string_literal: true + require 'spec_helper' -describe "Intercom::Tag" do - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } +describe 'Intercom::Tag' do + let(:client) { Intercom::Client.new(token: 'token') } + + it 'creates a tag' do + client.expects(:post).with('/tags', 'name' => 'Test Tag').returns(test_tag) + tag = client.tags.create(name: 'Test Tag') + _(tag.name).must_equal 'Test Tag' + end - it "creates a tag" do - client.expects(:post).with("/tags", {'name' => "Test Tag"}).returns(test_tag) - tag = client.tags.create(:name => "Test Tag") - tag.name.must_equal "Test Tag" + it 'finds a tag by id' do + client.expects(:get).with('/tags/4f73428b5e4dfc000b000112', {}).returns(test_tag) + tag = client.tags.find(id: '4f73428b5e4dfc000b000112') + _(tag.name).must_equal 'Test Tag' end - it "tags users" do - client.expects(:post).with("/tags", {'name' => "Test Tag", 'users' => [ { user_id: 'abc123' }, { user_id: 'def456' } ], 'tag_or_untag' => 'tag'}).returns(test_tag) - tag = client.tags.tag(:name => "Test Tag", :users => [ { user_id: "abc123" }, { user_id: "def456" } ] ) - tag.name.must_equal "Test Tag" - tag.tagged_user_count.must_equal 2 + it 'tags companies' do + client.expects(:post).with('/tags', 'name' => 'Test Tag', 'companies' => [{ company_id: 'abc123' }, { company_id: 'def456' }], 'tag_or_untag' => 'tag').returns(test_tag) + tag = client.tags.tag(name: 'Test Tag', companies: [{ company_id: 'abc123' }, { company_id: 'def456' }]) + _(tag.name).must_equal 'Test Tag' + _(tag.tagged_company_count).must_equal 2 end - it 'untags users' do - client.expects(:post).with("/tags", {'name' => "Test Tag", 'users' => [ { user_id: 'abc123', untag: true }, { user_id: 'def456', untag: true } ], 'tag_or_untag' => 'untag'}).returns(test_tag) - client.tags.untag(:name => "Test Tag", :users => [ { user_id: "abc123" }, { user_id: "def456" } ]) + it 'untags companies' do + client.expects(:post).with('/tags', 'name' => 'Test Tag', 'companies' => [{ company_id: 'abc123', untag: true }, { company_id: 'def456', untag: true }], 'tag_or_untag' => 'untag').returns(test_tag) + client.tags.untag(name: 'Test Tag', companies: [{ company_id: 'abc123' }, { company_id: 'def456' }]) end end diff --git a/spec/unit/intercom/team_spec.rb b/spec/unit/intercom/team_spec.rb index 1a5fc126..a3b3b785 100644 --- a/spec/unit/intercom/team_spec.rb +++ b/spec/unit/intercom/team_spec.rb @@ -1,12 +1,12 @@ require "spec_helper" describe "Intercom::Team" do - let (:client) { Intercom::Client.new(token: 'token') } + let(:client) { Intercom::Client.new(token: 'token') } it "returns a CollectionProxy for all without making any requests" do client.expects(:execute_request).never all = client.teams.all - all.must_be_instance_of(Intercom::ClientCollectionProxy) + _(all).must_be_instance_of(Intercom::ClientCollectionProxy) end it 'gets an team list' do diff --git a/spec/unit/intercom/traits/api_resource_spec.rb b/spec/unit/intercom/traits/api_resource_spec.rb index dfb95d0c..b5724556 100644 --- a/spec/unit/intercom/traits/api_resource_spec.rb +++ b/spec/unit/intercom/traits/api_resource_spec.rb @@ -1,98 +1,100 @@ -require "spec_helper" +# frozen_string_literal: true + +require 'spec_helper' describe Intercom::Traits::ApiResource do let(:object_json) do - {"type"=>"company", - "id"=>"aaaaaaaaaaaaaaaaaaaaaaaa", - "app_id"=>"some-app-id", - "name"=>"SuperSuite", - "plan_id"=>1, - "remote_company_id"=>"8", - "remote_created_at"=>103201, - "created_at"=>1374056196, - "user_count"=>1, - "custom_attributes"=>{}, - "metadata"=>{ - "type"=>"user", - "color"=>"cyan" - }} + { 'type' => 'company', + 'id' => 'aaaaaaaaaaaaaaaaaaaaaaaa', + 'app_id' => 'some-app-id', + 'name' => 'SuperSuite', + 'plan_id' => 1, + 'remote_company_id' => '8', + 'remote_created_at' => 103_201, + 'created_at' => 1_374_056_196, + 'user_count' => 1, + 'custom_attributes' => {}, + 'metadata' => { + 'type' => 'user', + 'color' => 'cyan' + } } end let(:object_hash) do { - type: "company", - id: "aaaaaaaaaaaaaaaaaaaaaaaa", - app_id: "some-app-id", - name: "SuperSuite", + type: 'company', + id: 'aaaaaaaaaaaaaaaaaaaaaaaa', + app_id: 'some-app-id', + name: 'SuperSuite', plan_id: 1, - remote_company_id: "8", - remote_created_at: 103201, - created_at: 1374056196, + remote_company_id: '8', + remote_created_at: 103_201, + created_at: 1_374_056_196, user_count: 1, - custom_attributes: { type: "ping" }, + custom_attributes: { type: 'ping' }, metadata: { - type: "user", - color: "cyan" + type: 'user', + color: 'cyan' } } end - let(:api_resource) { DummyClass.new.extend(Intercom::Traits::ApiResource)} + let(:api_resource) { DummyClass.new.extend(Intercom::Traits::ApiResource) } before(:each) { api_resource.from_response(object_json) } - it "coerces time on parsing json" do - assert_equal Time.at(1374056196), api_resource.created_at + it 'coerces time on parsing json' do + assert_equal Time.at(1_374_056_196), api_resource.created_at end - it "exposes string" do - assert_equal Time.at(1374056196), api_resource.created_at + it 'exposes string' do + assert_equal Time.at(1_374_056_196), api_resource.created_at end it "treats 'metadata' as a plain hash, not a typed object" do assert_equal Hash, api_resource.metadata.class end - it "dynamically defines accessors when a non-existent property is called that looks like a setter" do - api_resource.wont_respond_to :spiders + it 'dynamically defines accessors when a non-existent property is called that looks like a setter' do + _(api_resource).wont_respond_to :spiders api_resource.spiders = 4 - api_resource.must_respond_to :spiders + _(api_resource).must_respond_to :spiders end - it "calls dynamically defined getter when asked" do + it 'calls dynamically defined getter when asked' do api_resource.foo = 4 assert_equal 4, api_resource.foo end - it "accepts unix timestamps into dynamically defined date setters" do - api_resource.foo_at = 1401200468 - assert_equal 1401200468, api_resource.instance_variable_get(:@foo_at) + it 'accepts unix timestamps into dynamically defined date setters' do + api_resource.foo_at = 1_401_200_468 + assert_equal 1_401_200_468, api_resource.instance_variable_get(:@foo_at) end - it "exposes dates correctly for dynamically defined getters" do - api_resource.foo_at = 1401200468 - assert_equal Time.at(1401200468), api_resource.foo_at + it 'exposes dates correctly for dynamically defined getters' do + api_resource.foo_at = 1_401_200_468 + assert_equal Time.at(1_401_200_468), api_resource.foo_at end - it "throws regular method missing error when non-existent getter is called that is backed by an instance variable" do + it 'throws regular method missing error when non-existent getter is called that is backed by an instance variable' do api_resource.instance_variable_set(:@bar, 'you cant see me') - proc { api_resource.bar }.must_raise NoMethodError + _(proc { api_resource.bar }).must_raise NoMethodError end - it "throws attribute not set error when non-existent getter is called that is not backed by an instance variable" do - proc { api_resource.flubber }.must_raise Intercom::AttributeNotSetError + it 'throws attribute not set error when non-existent getter is called that is not backed by an instance variable' do + _(proc { api_resource.flubber }).must_raise Intercom::AttributeNotSetError end - it "throws regular method missing error when non-existent method is called that cannot be an accessor" do - proc { api_resource.flubber! }.must_raise NoMethodError - proc { api_resource.flubber? }.must_raise NoMethodError + it 'throws regular method missing error when non-existent method is called that cannot be an accessor' do + _(proc { api_resource.flubber! }).must_raise NoMethodError + _(proc { api_resource.flubber? }).must_raise NoMethodError end - it "throws regular method missing error when non-existent setter is called with multiple arguments" do - proc { api_resource.send(:flubber=, 'a', 'b') }.must_raise NoMethodError + it 'throws regular method missing error when non-existent setter is called with multiple arguments' do + _(proc { api_resource.send(:flubber=, 'a', 'b') }).must_raise NoMethodError end - it "an initialized ApiResource is equal to one generated from a response" do + it 'an initialized ApiResource is equal to one generated from a response' do class ConcreteApiResource; include Intercom::Traits::ApiResource; end initialized_api_resource = ConcreteApiResource.new(object_json) except(object_json, 'type').keys.each do |attribute| @@ -100,7 +102,7 @@ class ConcreteApiResource; include Intercom::Traits::ApiResource; end end end - it "initialized ApiResource using hash is equal to one generated from response" do + it 'initialized ApiResource using hash is equal to one generated from response' do class ConcreteApiResource; include Intercom::Traits::ApiResource; end api_resource.from_hash(object_hash) diff --git a/spec/unit/intercom/user_spec.rb b/spec/unit/intercom/user_spec.rb index 99ff42bb..fa6deddb 100644 --- a/spec/unit/intercom/user_spec.rb +++ b/spec/unit/intercom/user_spec.rb @@ -1,283 +1,284 @@ -require "spec_helper" +# frozen_string_literal: true -describe "Intercom::User" do - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } +require 'spec_helper' + +describe 'Intercom::User' do + let (:client) { Intercom::Client.new(token: 'token') } it "to_hash'es itself" do created_at = Time.now - user = Intercom::User.new(:email => "jim@example.com", :user_id => "12345", :created_at => created_at, :name => "Jim Bob") + user = Intercom::User.new(email: 'jim@example.com', user_id: '12345', created_at: created_at, name: 'Jim Bob') as_hash = user.to_hash - as_hash["email"].must_equal "jim@example.com" - as_hash["user_id"].must_equal "12345" - as_hash["created_at"].must_equal created_at.to_i - as_hash["name"].must_equal "Jim Bob" + _(as_hash['email']).must_equal 'jim@example.com' + _(as_hash['user_id']).must_equal '12345' + _(as_hash['created_at']).must_equal created_at.to_i + _(as_hash['name']).must_equal 'Jim Bob' end - it "presents created_at and last_impression_at as Date" do + it 'presents created_at and last_impression_at as Date' do now = Time.now - user = Intercom::User.from_api(:created_at => now, :last_impression_at => now) - user.created_at.must_be_kind_of Time - user.created_at.to_s.must_equal now.to_s - user.last_impression_at.must_be_kind_of Time - user.last_impression_at.to_s.must_equal now.to_s + user = Intercom::User.from_api(created_at: now, last_impression_at: now) + _(user.created_at).must_be_kind_of Time + _(user.created_at.to_s).must_equal now.to_s + _(user.last_impression_at).must_be_kind_of Time + _(user.last_impression_at.to_s).must_equal now.to_s end - it "is throws a Intercom::AttributeNotSetError on trying to access an attribute that has not been set" do + it 'is throws a Intercom::AttributeNotSetError on trying to access an attribute that has not been set' do user = Intercom::User.new - proc { user.foo_property }.must_raise Intercom::AttributeNotSetError + _(proc { user.foo_property }).must_raise Intercom::AttributeNotSetError end - it "presents a complete user record correctly" do + it 'presents a complete user record correctly' do user = Intercom::User.from_api(test_user) - user.user_id.must_equal 'id-from-customers-app' - user.email.must_equal 'bob@example.com' - user.name.must_equal 'Joe Schmoe' - user.app_id.must_equal 'the-app-id' - user.session_count.must_equal 123 - user.created_at.to_i.must_equal 1401970114 - user.remote_created_at.to_i.must_equal 1393613864 - user.updated_at.to_i.must_equal 1401970114 - - user.avatar.must_be_kind_of Intercom::Avatar - user.avatar.image_url.must_equal 'https://graph.facebook.com/1/picture?width=24&height=24' - - user.companies.must_be_kind_of Array - user.companies.size.must_equal 1 - user.companies[0].must_be_kind_of Intercom::Company - user.companies[0].company_id.must_equal "123" - user.companies[0].id.must_equal "bbbbbbbbbbbbbbbbbbbbbbbb" - user.companies[0].app_id.must_equal "the-app-id" - user.companies[0].name.must_equal "Company 1" - user.companies[0].remote_created_at.to_i.must_equal 1390936440 - user.companies[0].created_at.to_i.must_equal 1401970114 - user.companies[0].updated_at.to_i.must_equal 1401970114 - user.companies[0].last_request_at.to_i.must_equal 1401970113 - user.companies[0].monthly_spend.must_equal 0 - user.companies[0].session_count.must_equal 0 - user.companies[0].user_count.must_equal 1 - user.companies[0].tag_ids.must_equal [] - - user.custom_attributes.must_be_kind_of Intercom::Lib::FlatStore - user.custom_attributes["a"].must_equal "b" - user.custom_attributes["b"].must_equal 2 - - user.social_profiles.size.must_equal 4 + _(user.user_id).must_equal 'id-from-customers-app' + _(user.email).must_equal 'bob@example.com' + _(user.name).must_equal 'Joe Schmoe' + _(user.app_id).must_equal 'the-app-id' + _(user.session_count).must_equal 123 + _(user.created_at.to_i).must_equal 1_401_970_114 + _(user.remote_created_at.to_i).must_equal 1_393_613_864 + _(user.updated_at.to_i).must_equal 1_401_970_114 + + _(user.avatar).must_be_kind_of Intercom::Avatar + _(user.avatar.image_url).must_equal 'https://graph.facebook.com/1/picture?width=24&height=24' + + _(user.companies).must_be_kind_of Array + _(user.companies.size).must_equal 1 + _(user.companies[0]).must_be_kind_of Intercom::Company + _(user.companies[0].company_id).must_equal '123' + _(user.companies[0].id).must_equal 'bbbbbbbbbbbbbbbbbbbbbbbb' + _(user.companies[0].app_id).must_equal 'the-app-id' + _(user.companies[0].name).must_equal 'Company 1' + _(user.companies[0].remote_created_at.to_i).must_equal 1_390_936_440 + _(user.companies[0].created_at.to_i).must_equal 1_401_970_114 + _(user.companies[0].updated_at.to_i).must_equal 1_401_970_114 + _(user.companies[0].last_request_at.to_i).must_equal 1_401_970_113 + _(user.companies[0].monthly_spend).must_equal 0 + _(user.companies[0].session_count).must_equal 0 + _(user.companies[0].user_count).must_equal 1 + _(user.companies[0].tag_ids).must_equal [] + + _(user.custom_attributes).must_be_kind_of Intercom::Lib::FlatStore + _(user.custom_attributes['a']).must_equal 'b' + _(user.custom_attributes['b']).must_equal 2 + + _(user.social_profiles.size).must_equal 4 twitter_account = user.social_profiles.first - twitter_account.must_be_kind_of Intercom::SocialProfile - twitter_account.name.must_equal "twitter" - twitter_account.username.must_equal "abc" - twitter_account.url.must_equal "http://twitter.com/abc" - - user.location_data.must_be_kind_of Intercom::LocationData - user.location_data.city_name.must_equal "Dublin" - user.location_data.continent_code.must_equal "EU" - user.location_data.country_name.must_equal "Ireland" - user.location_data.latitude.must_equal "90" - user.location_data.longitude.must_equal "10" - user.location_data.country_code.must_equal "IRL" - - user.unsubscribed_from_emails.must_equal true - user.user_agent_data.must_equal "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11" + _(twitter_account).must_be_kind_of Intercom::SocialProfile + _(twitter_account.name).must_equal 'twitter' + _(twitter_account.username).must_equal 'abc' + _(twitter_account.url).must_equal 'http://twitter.com/abc' + + _(user.location_data).must_be_kind_of Intercom::LocationData + _(user.location_data.city_name).must_equal 'Dublin' + _(user.location_data.continent_code).must_equal 'EU' + _(user.location_data.country_name).must_equal 'Ireland' + _(user.location_data.latitude).must_equal '90' + _(user.location_data.longitude).must_equal '10' + _(user.location_data.country_code).must_equal 'IRL' + + _(user.unsubscribed_from_emails).must_equal true + _(user.user_agent_data).must_equal 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11' end it 'allows update_last_request_at' do - client.expects(:post).with("/users", {"user_id" => "1224242", 'update_last_request_at' => true, "custom_attributes" => {}}).returns({"user_id" => "i-1224242", "last_request_at" => 1414509439}) - client.users.create(user_id: '1224242', update_last_request_at: true) + client.expects(:post).with('/users', 'user_id' => '1224242', 'update_last_request_at' => true, 'custom_attributes' => {}).returns('user_id' => 'i-1224242', 'last_request_at' => 1_414_509_439) + client.deprecated__users.create(user_id: '1224242', update_last_request_at: true) end - it "allows easy setting of custom data" do + it 'allows easy setting of custom data' do now = Time.now user = Intercom::User.new - user.custom_attributes["mad"] = 123 - user.custom_attributes["other"] = now.to_i - user.custom_attributes["thing"] = "yay" - user.to_hash["custom_attributes"].must_equal "mad" => 123, "other" => now.to_i, "thing" => "yay" + user.custom_attributes['mad'] = 123 + user.custom_attributes['other'] = now.to_i + user.custom_attributes['thing'] = 'yay' + _(user.to_hash['custom_attributes']).must_equal 'mad' => 123, 'other' => now.to_i, 'thing' => 'yay' end - it "allows easy setting of multiple companies" do - user = Intercom::User.new() + it 'allows easy setting of multiple companies' do + user = Intercom::User.new companies = [ - {"name" => "Intercom", "company_id" => "6"}, - {"name" => "Test", "company_id" => "9"} + { 'name' => 'Intercom', 'company_id' => '6' }, + { 'name' => 'Test', 'company_id' => '9' } ] user.companies = companies - user.to_hash["companies"].must_equal companies + _(user.to_hash['companies']).must_equal companies end - it "rejects nested data structures in custom_attributes" do - user = Intercom::User.new() + it 'rejects nested data structures in custom_attributes' do + user = Intercom::User.new - proc { user.custom_attributes["thing"] = [1] }.must_raise(ArgumentError) - proc { user.custom_attributes["thing"] = {1 => 2} }.must_raise(ArgumentError) - proc { user.custom_attributes["thing"] = {1 => {2 => 3}} }.must_raise(ArgumentError) + _(proc { user.custom_attributes['thing'] = [1] }).must_raise(ArgumentError) + _(proc { user.custom_attributes['thing'] = { 1 => 2 } }).must_raise(ArgumentError) + _(proc { user.custom_attributes['thing'] = { 1 => { 2 => 3 } } }).must_raise(ArgumentError) user = Intercom::User.from_api(test_user) - proc { user.custom_attributes["thing"] = [1] }.must_raise(ArgumentError) + _(proc { user.custom_attributes['thing'] = [1] }).must_raise(ArgumentError) end - describe "incrementing custom_attributes fields" do + describe 'incrementing custom_attributes fields' do before :each do @now = Time.now - @user = Intercom::User.new("email" => "jo@example.com", :user_id => "i-1224242", :custom_attributes => {"mad" => 123, "another" => 432, "other" => @now.to_i, :thing => "yay"}) + @user = Intercom::User.new('email' => 'jo@example.com', :user_id => 'i-1224242', :custom_attributes => { 'mad' => 123, 'another' => 432, 'other' => @now.to_i, :thing => 'yay' }) end - it "increments up by 1 with no args" do - @user.increment("mad") - @user.to_hash["custom_attributes"]["mad"].must_equal 124 + it 'increments up by 1 with no args' do + @user.increment('mad') + _(@user.to_hash['custom_attributes']['mad']).must_equal 124 end - it "increments up by given value" do - @user.increment("mad", 4) - @user.to_hash["custom_attributes"]["mad"].must_equal 127 + it 'increments up by given value' do + @user.increment('mad', 4) + _(@user.to_hash['custom_attributes']['mad']).must_equal 127 end - it "increments down by given value" do - @user.increment("mad", -1) - @user.to_hash["custom_attributes"]["mad"].must_equal 122 + it 'increments down by given value' do + @user.increment('mad', -1) + _(@user.to_hash['custom_attributes']['mad']).must_equal 122 end - it "can increment new custom data fields" do - @user.increment("new_field", 3) - @user.to_hash["custom_attributes"]["new_field"].must_equal 3 + it 'can increment new custom data fields' do + @user.increment('new_field', 3) + _(@user.to_hash['custom_attributes']['new_field']).must_equal 3 end - it "can call increment on the same key twice and increment by 2" do - @user.increment("mad") - @user.increment("mad") - @user.to_hash["custom_attributes"]["mad"].must_equal 125 + it 'can call increment on the same key twice and increment by 2' do + @user.increment('mad') + @user.increment('mad') + _(@user.to_hash['custom_attributes']['mad']).must_equal 125 end end - describe "decrementing custom_attributes fields" do + describe 'decrementing custom_attributes fields' do before :each do @now = Time.now - @user = Intercom::User.new("email" => "jo@example.com", :user_id => "i-1224242", :custom_attributes => {"mad" => 123, "another" => 432, "other" => @now.to_i, :thing => "yay"}) + @user = Intercom::User.new('email' => 'jo@example.com', :user_id => 'i-1224242', :custom_attributes => { 'mad' => 123, 'another' => 432, 'other' => @now.to_i, :thing => 'yay' }) end - it "decrements down by 1 with no args" do - @user.decrement("mad") - @user.to_hash["custom_attributes"]["mad"].must_equal 122 + it 'decrements down by 1 with no args' do + @user.decrement('mad') + _(@user.to_hash['custom_attributes']['mad']).must_equal 122 end - it "decrements down by given value" do - @user.decrement("mad", 3) - @user.to_hash["custom_attributes"]["mad"].must_equal 120 + it 'decrements down by given value' do + @user.decrement('mad', 3) + _(@user.to_hash['custom_attributes']['mad']).must_equal 120 end - it "can decrement new custom data fields" do - @user.decrement("new_field", 5) - @user.to_hash["custom_attributes"]["new_field"].must_equal -5 + it 'can decrement new custom data fields' do + @user.decrement('new_field', 5) + _(@user.to_hash['custom_attributes']['new_field']).must_equal(-5) end - it "can call decrement on the same key twice and decrement by 2" do - @user.decrement("mad") - @user.decrement("mad") - @user.to_hash["custom_attributes"]["mad"].must_equal 121 + it 'can call decrement on the same key twice and decrement by 2' do + @user.decrement('mad') + @user.decrement('mad') + _(@user.to_hash['custom_attributes']['mad']).must_equal 121 end end - it "fetches a user" do - client.expects(:get).with("/users", {"email" => "bo@example.com"}).returns(test_user) - user = client.users.find("email" => "bo@example.com") - user.email.must_equal "bob@example.com" - user.name.must_equal "Joe Schmoe" - user.session_count.must_equal 123 + it 'fetches a user' do + client.expects(:get).with('/users', 'email' => 'bo@example.com').returns(test_user) + user = client.deprecated__users.find('email' => 'bo@example.com') + _(user.email).must_equal 'bob@example.com' + _(user.name).must_equal 'Joe Schmoe' + _(user.session_count).must_equal 123 end it 'gets users by tag' do - client.expects(:get).with("/users?tag_id=124", {}).returns(page_of_users(false)) - client.users.by_tag(124).each { |t| } + client.expects(:get).with('/users?tag_id=124', {}).returns(page_of_users(false)) + client.deprecated__users.by_tag(124).each { |t| } end it 'gets users by segment' do - client.expects(:get).with("/users?segment_id=124", {}).returns(page_of_users(false)) - client.users.by_segment(124).each { |t| } + client.expects(:get).with('/users?segment_id=124', {}).returns(page_of_users(false)) + client.deprecated__users.by_segment(124).each { |t| } end - it "saves a user (always sends custom_attributes)" do - user = Intercom::User.new("email" => "jo@example.com", :user_id => "i-1224242") - client.expects(:post).with("/users", {"email" => "jo@example.com", "user_id" => "i-1224242", "custom_attributes" => {}}).returns({"email" => "jo@example.com", "user_id" => "i-1224242"}) - client.users.save(user) + it 'saves a user (always sends custom_attributes)' do + user = Intercom::User.new('email' => 'jo@example.com', :user_id => 'i-1224242') + client.expects(:post).with('/users', 'email' => 'jo@example.com', 'user_id' => 'i-1224242', 'custom_attributes' => {}).returns('email' => 'jo@example.com', 'user_id' => 'i-1224242') + client.deprecated__users.save(user) end - it "saves a user with a company" do - user = Intercom::User.new("email" => "jo@example.com", :user_id => "i-1224242", :company => {'company_id' => 6, 'name' => "Intercom"}) - client.expects(:post).with("/users", {'custom_attributes' => {}, "user_id" => "i-1224242", "email" => "jo@example.com", "company" => {"company_id" => 6, "name" => "Intercom"}}).returns({"email" => "jo@example.com", "user_id" => "i-1224242"}) - client.users.save(user) + it 'saves a user with a company' do + user = Intercom::User.new('email' => 'jo@example.com', :user_id => 'i-1224242', :company => { 'company_id' => 6, 'name' => 'Intercom' }) + client.expects(:post).with('/users', 'custom_attributes' => {}, 'user_id' => 'i-1224242', 'email' => 'jo@example.com', 'company' => { 'company_id' => 6, 'name' => 'Intercom' }).returns('email' => 'jo@example.com', 'user_id' => 'i-1224242') + client.deprecated__users.save(user) end - it "saves a user with a companies" do - user = Intercom::User.new("email" => "jo@example.com", :user_id => "i-1224242", :companies => [{'company_id' => 6, 'name' => "Intercom"}]) - client.expects(:post).with("/users", {'custom_attributes' => {}, "email" => "jo@example.com", "user_id" => "i-1224242", "companies" => [{"company_id" => 6, "name" => "Intercom"}]}).returns({"email" => "jo@example.com", "user_id" => "i-1224242"}) - client.users.save(user) + it 'saves a user with a companies' do + user = Intercom::User.new('email' => 'jo@example.com', :user_id => 'i-1224242', :companies => [{ 'company_id' => 6, 'name' => 'Intercom' }]) + client.expects(:post).with('/users', 'custom_attributes' => {}, 'email' => 'jo@example.com', 'user_id' => 'i-1224242', 'companies' => [{ 'company_id' => 6, 'name' => 'Intercom' }]).returns('email' => 'jo@example.com', 'user_id' => 'i-1224242') + client.deprecated__users.save(user) end it 'can save a user with a nil email' do - user = Intercom::User.new("email" => nil, :user_id => "i-1224242", :companies => [{'company_id' => 6, 'name' => "Intercom"}]) - client.expects(:post).with("/users", {'custom_attributes' => {}, "email" => nil, "user_id" => "i-1224242", "companies" => [{"company_id" => 6, "name" => "Intercom"}]}).returns({"email" => nil, "user_id" => "i-1224242"}) - client.users.save(user) + user = Intercom::User.new('email' => nil, :user_id => 'i-1224242', :companies => [{ 'company_id' => 6, 'name' => 'Intercom' }]) + client.expects(:post).with('/users', 'custom_attributes' => {}, 'email' => nil, 'user_id' => 'i-1224242', 'companies' => [{ 'company_id' => 6, 'name' => 'Intercom' }]).returns('email' => nil, 'user_id' => 'i-1224242') + client.deprecated__users.save(user) end - it "archives a user" do - user = Intercom::User.new("id" => "1") - client.expects(:delete).with("/users/1", {}).returns(user) - client.users.archive(user) + it 'archives a user' do + user = Intercom::User.new('id' => '1') + client.expects(:delete).with('/users/1', {}).returns(user) + client.deprecated__users.archive(user) + end + it 'has an alias to archive a user' do + user = Intercom::User.new('id' => '1') + client.expects(:delete).with('/users/1', {}).returns(user) + client.deprecated__users.delete(user) end - it "has an alias to archive a user" do - user = Intercom::User.new("id" => "1") - client.expects(:delete).with("/users/1", {}).returns(user) - client.users.delete(user) - end - - it "sends a request for a hard deletion" do - user = Intercom::User.new("id" => "1") - client.expects(:post).with("/user_delete_requests", {intercom_user_id: "1"}).returns({id: user.id}) - client.users.request_hard_delete(user) + it 'sends a request for a hard deletion' do + user = Intercom::User.new('id' => '1') + client.expects(:post).with('/user_delete_requests', intercom_user_id: '1').returns(id: user.id) + client.deprecated__users.request_hard_delete(user) end - it "can use client.users.create for convenience" do - client.expects(:post).with("/users", {'custom_attributes' => {}, "email" => "jo@example.com", "user_id" => "i-1224242"}).returns({"email" => "jo@example.com", "user_id" => "i-1224242"}) - user = client.users.create("email" => "jo@example.com", :user_id => "i-1224242") - user.email.must_equal "jo@example.com" + it 'can use client.users.create for convenience' do + client.expects(:post).with('/users', 'custom_attributes' => {}, 'email' => 'jo@example.com', 'user_id' => 'i-1224242').returns('email' => 'jo@example.com', 'user_id' => 'i-1224242') + user = client.deprecated__users.create('email' => 'jo@example.com', :user_id => 'i-1224242') + _(user.email).must_equal 'jo@example.com' end - it "updates the user with attributes as set by the server" do - client.expects(:post).with("/users", {"email" => "jo@example.com", "user_id" => "i-1224242", 'custom_attributes' => {}}).returns({"email" => "jo@example.com", "user_id" => "i-1224242", "session_count" => 4}) - user = client.users.create("email" => "jo@example.com", :user_id => "i-1224242") - user.session_count.must_equal 4 + it 'updates the user with attributes as set by the server' do + client.expects(:post).with('/users', 'email' => 'jo@example.com', 'user_id' => 'i-1224242', 'custom_attributes' => {}).returns('email' => 'jo@example.com', 'user_id' => 'i-1224242', 'session_count' => 4) + user = client.deprecated__users.create('email' => 'jo@example.com', :user_id => 'i-1224242') + _(user.session_count).must_equal 4 end - it "allows setting dates to nil without converting them to 0" do - client.expects(:post).with("/users", {"email" => "jo@example.com", 'custom_attributes' => {}, "remote_created_at" => nil}).returns({"email" => "jo@example.com"}) - user = client.users.create("email" => "jo@example.com", "remote_created_at" => nil) + it 'allows setting dates to nil without converting them to 0' do + client.expects(:post).with('/users', 'email' => 'jo@example.com', 'custom_attributes' => {}, 'remote_created_at' => nil).returns('email' => 'jo@example.com') + user = client.deprecated__users.create('email' => 'jo@example.com', 'remote_created_at' => nil) assert_nil user.remote_created_at end - it "sets/gets rw keys" do - params = {"email" => "me@example.com", :user_id => "abc123", "name" => "Bob Smith", "last_seen_ip" => "1.2.3.4", "last_seen_user_agent" => "ie6", "created_at" => Time.now} + it 'sets/gets rw keys' do + params = { 'email' => 'me@example.com', :user_id => 'abc123', 'name' => 'Bob Smith', 'last_seen_ip' => '1.2.3.4', 'last_seen_user_agent' => 'ie6', 'created_at' => Time.now } user = Intercom::User.new(params) custom_attributes = (params.keys + ['custom_attributes']).map(&:to_s).sort - user.to_hash.keys.sort.must_equal custom_attributes + _(user.to_hash.keys.sort).must_equal custom_attributes params.keys.each do |key| - user.send(key).to_s.must_equal params[key].to_s + _(user.send(key).to_s).must_equal params[key].to_s end end - it "will allow extra attributes in response from api" do - user = Intercom::User.send(:from_api, {"new_param" => "some value"}) - user.new_param.must_equal "some value" + it 'will allow extra attributes in response from api' do + user = Intercom::User.send(:from_api, 'new_param' => 'some value') + _(user.new_param).must_equal 'some value' end - it "returns a ClientCollectionProxy for all without making any requests" do + it 'returns a ClientCollectionProxy for all without making any requests' do client.expects(:execute_request).never - all = client.users.all - all.must_be_instance_of(Intercom::ClientCollectionProxy) + all = client.deprecated__users.all + _(all).must_be_instance_of(Intercom::ClientCollectionProxy) end it 'can print users without crashing' do - client.expects(:get).with("/users", {"email" => "bo@example.com"}).returns(test_user) - user = client.users.find("email" => "bo@example.com") + client.expects(:get).with('/users', 'email' => 'bo@example.com').returns(test_user) + user = client.deprecated__users.find('email' => 'bo@example.com') begin orignal_stdout = $stdout @@ -285,91 +286,88 @@ puts user p user - ensure $stdout = orignal_stdout end end describe 'bulk operations' do - let (:job) { + let (:job) do { - "app_id"=>"app_id", - "id"=>"super_awesome_job", - "created_at"=>1446033421, - "completed_at"=>1446048736, - "closing_at"=>1446034321, - "updated_at"=>1446048736, - "name"=>"api_bulk_job", - "state"=>"completed", - "links"=> + 'app_id' => 'app_id', + 'id' => 'super_awesome_job', + 'created_at' => 1_446_033_421, + 'completed_at' => 1_446_048_736, + 'closing_at' => 1_446_034_321, + 'updated_at' => 1_446_048_736, + 'name' => 'api_bulk_job', + 'state' => 'completed', + 'links' => { - "error"=>"https://api.intercom.io/jobs/super_awesome_job/error", - "self"=>"https://api.intercom.io/jobs/super_awesome_job" + 'error' => 'https://api.intercom.io/jobs/super_awesome_job/error', + 'self' => 'https://api.intercom.io/jobs/super_awesome_job' }, - "tasks"=> + 'tasks' => [ { - "id"=>"super_awesome_task", - "item_count"=>2, - "created_at"=>1446033421, - "started_at"=>1446033709, - "completed_at"=>1446033709, - "state"=>"completed" + 'id' => 'super_awesome_task', + 'item_count' => 2, + 'created_at' => 1_446_033_421, + 'started_at' => 1_446_033_709, + 'completed_at' => 1_446_033_709, + 'state' => 'completed' } ] - } - } - let(:bulk_request) { - { + } + end + let(:bulk_request) do + { items: [ { - method: "post", - data_type: "user", - data:{ + method: 'post', + data_type: 'user', + data: { user_id: 25, - email: "alice@example.com" + email: 'alice@example.com' } }, { - method: "delete", - data_type: "user", - data:{ + method: 'delete', + data_type: 'user', + data: { user_id: 26, - email: "bob@example.com" + email: 'bob@example.com' } } ] } - } - let(:users_to_create) { + end + let(:users_to_create) do [ { user_id: 25, - email: "alice@example.com" + email: 'alice@example.com' } ] - } - let(:users_to_delete) { + end + let(:users_to_delete) do [ { user_id: 26, - email: "bob@example.com" + email: 'bob@example.com' } ] - } - - it "submits a bulk job" do - client.expects(:post).with("/bulk/users", bulk_request).returns(job) - client.users.submit_bulk_job(create_items: users_to_create, delete_items: users_to_delete) end - it "adds users to an existing bulk job" do - bulk_request[:job] = {id: 'super_awesome_job'} - client.expects(:post).with("/bulk/users", bulk_request).returns(job) - client.users.submit_bulk_job(create_items: users_to_create, delete_items: users_to_delete, job_id: 'super_awesome_job') + it 'submits a bulk job' do + client.expects(:post).with('/bulk/users', bulk_request).returns(job) + client.deprecated__users.submit_bulk_job(create_items: users_to_create, delete_items: users_to_delete) end + it 'adds users to an existing bulk job' do + bulk_request[:job] = { id: 'super_awesome_job' } + client.expects(:post).with('/bulk/users', bulk_request).returns(job) + client.deprecated__users.submit_bulk_job(create_items: users_to_create, delete_items: users_to_delete, job_id: 'super_awesome_job') + end end - end diff --git a/spec/unit/intercom/visitor_spec.rb b/spec/unit/intercom/visitor_spec.rb new file mode 100644 index 00000000..ebabb7ec --- /dev/null +++ b/spec/unit/intercom/visitor_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe 'Intercom::Visitor' do + let (:client) { Intercom::Client.new(token: 'token') } + + it 'can update a visitor with an id' do + visitor = Intercom::Visitor.new(:id => 'de45ae78gae1289cb') + client.expects(:put).with('/visitors/de45ae78gae1289cb', 'custom_attributes' => {}) + client.visitors.save(visitor) + end + + it 'can get a visitor' do + visitor = Intercom::Visitor.new(:id => 'de45ae78gae1289cb') + client.expects(:get).with('/visitors/de45ae78gae1289cb', {}).returns(test_visitor) + client.visitors.find(id: visitor.id) + end + + describe 'converting' do + let(:visitor) { Intercom::Visitor.from_api(user_id: 'visitor_id') } + let(:user) { Intercom::Contact.from_api(id: 'user_id', role: 'user') } + + it 'visitor to user' do + client.expects(:post).with( + '/visitors/convert', + visitor: { user_id: visitor.user_id }, + user: { 'id' => user.id }, + type: 'user' + ).returns(test_contact) + + client.visitors.convert(visitor, user) + end + + it 'visitor to lead' do + client.expects(:post).with( + '/visitors/convert', + visitor: { user_id: visitor.user_id }, + type: 'lead' + ).returns(test_contact(role: 'lead')) + + client.visitors.convert(visitor) + end + end + + it 'deletes a visitor' do + visitor = Intercom::Visitor.new('id' => '1') + client.expects(:delete).with('/visitors/1', {}).returns(visitor) + client.visitors.delete(visitor) + end +end diff --git a/spec/unit/intercom/visitors_spec.rb b/spec/unit/intercom/visitors_spec.rb deleted file mode 100644 index ffb50a4d..00000000 --- a/spec/unit/intercom/visitors_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -require "spec_helper" - -describe "Intercom::Visitors" do - let (:client) { Intercom::Client.new(app_id: 'app_id', api_key: 'api_key') } - - it 'should be listable' do - proxy = client.visitors.all - proxy.resource_name.must_equal 'visitors' - proxy.finder_url.must_equal '/visitors' - proxy.resource_class.must_equal Intercom::Visitor - end - - it 'can update a visitor with an id' do - visitor = Intercom::Visitor.new(:id => "de45ae78gae1289cb") - client.expects(:put).with("/visitors/de45ae78gae1289cb", {'custom_attributes' => {}}) - client.visitors.save(visitor) - end - - describe 'converting' do - let(:visitor) { Intercom::Visitor.from_api(user_id: 'visitor_id') } - let(:user) { Intercom::User.from_api(id: 'user_id') } - - it 'visitor to user' do - client.expects(:post).with( - "/visitors/convert", - { - visitor: { user_id: visitor.user_id }, - user: { 'id' => user.id }, - type:'user' - } - ).returns(test_user) - - client.visitors.convert(visitor, user) - end - - it 'visitor to lead' do - client.expects(:post).with( - "/visitors/convert", - { - visitor: { user_id: visitor.user_id }, - type:'lead' - } - ).returns(test_user) - - client.visitors.convert(visitor) - end - end - - it "returns a ClientCollectionProxy for all without making any requests" do - client.expects(:execute_request).never - all = client.visitors.all - all.must_be_instance_of(Intercom::ClientCollectionProxy) - end - - it "deletes a visitor" do - visitor = Intercom::Visitor.new("id" => "1") - client.expects(:delete).with("/visitors/1", {}).returns(visitor) - client.visitors.delete(visitor) - end - -end diff --git a/spec/unit/intercom_spec.rb b/spec/unit/intercom_spec.rb index 91c15f94..4c305d23 100644 --- a/spec/unit/intercom_spec.rb +++ b/spec/unit/intercom_spec.rb @@ -1,7 +1,9 @@ -require "spec_helper" +# frozen_string_literal: true + +require 'spec_helper' describe Intercom do - it "has a version number" do - Intercom::VERSION.must_match(/\d+\.\d+\.\d+/) + it 'has a version number' do + _(Intercom::VERSION).must_match(/\d+\.\d+\.\d+/) end end From 8409f8949e536fe1f88720cd4f6ef3b030bc3628 Mon Sep 17 00:00:00 2001 From: Matthew Murray Date: Wed, 29 Jan 2020 17:24:18 +0000 Subject: [PATCH 189/245] Update README --- README.md | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e7d5e630..21118879 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ For generating Intercom JavaScript script tags for Rails, please see https://git ## Upgrading information -Version 4 of intercom-ruby is not backwards compatible with previous versions. Please see our [migration guide](https://github.com/intercom/ruby_v4/wiki/Migration-guide-for-v4) for full details of breaking changes. +Version 4 of intercom-ruby is not backwards compatible with previous versions. Please see our [migration guide](https://github.com/intercom/intercom-ruby/wiki/Migration-guide-for-v4) for full details of breaking changes. This version of the gem is compatible with `Ruby 2.1` and above. @@ -66,6 +66,8 @@ Resources this API supports: ### Examples #### Contacts +Note that this is a new resource compatible only with the new [Contacts API](https://developers.intercom.com/intercom-api-reference/reference#contacts-model) released in API v2.0. + ```ruby # Create a contact with "lead" role contact = intercom.contacts.create(email: "some_contact2@example.com", role: "lead") @@ -122,10 +124,10 @@ contact.notes.each {|n| p n.body} # Add a contact to a company company = intercom.companies.find(id: "123") -contact.add_company(id: company.id}) +contact.add_company(id: company.id) # Remove a contact from a company -contact.remove_company(id: company.id}) +contact.remove_company(id: company.id) # List companies for a contact contact.companies.each {|c| p c.name} @@ -307,14 +309,19 @@ conversation = intercom.conversations.find(id: '1') # INTERACTING WITH THE PARTS OF A CONVERSATION # Getting the subject of a part (only applies to email-based conversations) conversation.source.subject + # Get the part_type of the first part conversation.conversation_parts.first.part_type + # Get the body of the second part conversation.conversation_parts[1].body + # Get statistics related to the conversation -conversation.statistics["first_contact_reply_at"] -# Get information about the rating of a conversation -conversation.conversation_rating.rating +conversation.statistics.time_to_admin_reply +conversation.statistics.last_assignment_at + +# Get information on the sla applied to a conversation +conversation.sla_applied.sla_name # REPLYING TO CONVERSATIONS # User (identified by email) replies with a comment @@ -366,14 +373,13 @@ intercom.conversations.run_assignment_rules(conversation.id) # For full detail on possible queries, please refer to the API documentation: # https://developers.intercom.com/intercom-api-reference/reference -# Search for open conversations, getting 10 per page and sorting by the created_at date +# Search for open conversations sorted by the created_at date conversations = intercom.conversations.search( query: { field: "open", operator: "=", value: true }, - per_page: 10, sort_field: "created_at", sort_order: "descending" ) @@ -387,10 +393,10 @@ conversation = intercom.conversations.find(id: "1") admin = intercom.admins.find(id: "1") # Add a tag to a conversation -conversation.add_tag(id: tag.id, admin_id: admin.id}) +conversation.add_tag(id: tag.id, admin_id: admin.id) # Remove a tag from a conversation -conversation.remove_tag(id: tag.id, admin_id: admin.id}) +conversation.remove_tag(id: tag.id, admin_id: admin.id) ``` From 13338dca58c432e8ee19102aef8c8fa88740ab3f Mon Sep 17 00:00:00 2001 From: conorkeating Date: Thu, 30 Jan 2020 11:58:02 +0000 Subject: [PATCH 190/245] Bump intercom to 4.0.0 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 99eb61c7..85502869 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "3.9.5" + VERSION = "4.0.0" end From 1bf66c2a78f933597e903deaadabea42a7223d5f Mon Sep 17 00:00:00 2001 From: conorkeating Date: Thu, 30 Jan 2020 11:59:50 +0000 Subject: [PATCH 191/245] changes --- changes.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/changes.txt b/changes.txt index bec3ec6d..c6e35cef 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,11 @@ +4.0 +New version to support API version 2.0. +- Added support for new Contacts API. +- Added support for Conversation Search and for Conversation model changes. +- New DataAttribute class to support the Data Attributes. See README for details on usage. +- New method to run assignment rules on a conversation: `intercom.conversations.run_assignment_rules()`. +- See Migration guide for breaking changes: https://github.com/intercom/intercom-ruby/wiki/Migration-guide-for-v4 + 3.9.5 Add Unstable version support From 33ecaad3c911e419f9bc636d582a06c447349b79 Mon Sep 17 00:00:00 2001 From: Mohamed Ziata Date: Fri, 6 Mar 2020 14:02:09 +0100 Subject: [PATCH 192/245] [bugfix] Missing client in the response of the object created Then you can not use the nested resources. Example: ```ruby contact = INTERCOM_CLIENT.contacts.new(email: "user@example.com") INTERCOM_CLIENT.contacts.save(contact) company = INTERCOM_CLIENT.contacts.new(company_id: "abc-company-example") contact.add_company(id: company.id) # this fails!! ``` --- lib/intercom/api_operations/save.rb | 1 + spec/unit/intercom/contact_spec.rb | 55 +++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/lib/intercom/api_operations/save.rb b/lib/intercom/api_operations/save.rb index caef1c94..8bab9c05 100644 --- a/lib/intercom/api_operations/save.rb +++ b/lib/intercom/api_operations/save.rb @@ -27,6 +27,7 @@ def save(object) else response = @client.post("/#{collection_name}", object.to_submittable_hash.merge(identity_hash(object))) end + object.client = @client object.from_response(response) if response # may be nil we received back a 202 end diff --git a/spec/unit/intercom/contact_spec.rb b/spec/unit/intercom/contact_spec.rb index 96aab94b..b0193fe4 100644 --- a/spec/unit/intercom/contact_spec.rb +++ b/spec/unit/intercom/contact_spec.rb @@ -311,5 +311,60 @@ client.expects(:delete).with("/contacts/1/companies/#{company.id}", "id": tag.id ).returns(test_company) contact.remove_company({ "id": tag.id }) end + + describe 'just after creating the contact' do + let(:contact) do + contact = Intercom::Contact.new('email' => 'jo@example.com', :external_id => 'i-1224242') + client.expects(:post).with('/contacts', 'email' => 'jo@example.com', 'external_id' => 'i-1224242', 'custom_attributes' => {}) + .returns('id' => 1, 'email' => 'jo@example.com', 'external_id' => 'i-1224242') + client.contacts.save(contact) + end + + it 'returns a collection proxy for listing notes' do + proxy = contact.notes + _(proxy.resource_name).must_equal 'notes' + _(proxy.url).must_equal '/contacts/1/notes' + _(proxy.resource_class).must_equal Intercom::Note + end + + it 'returns a collection proxy for listing tags' do + proxy = contact.tags + _(proxy.resource_name).must_equal 'tags' + _(proxy.url).must_equal '/contacts/1/tags' + _(proxy.resource_class).must_equal Intercom::Tag + end + + it 'returns a collection proxy for listing companies' do + proxy = contact.companies + _(proxy.resource_name).must_equal 'companies' + _(proxy.url).must_equal '/contacts/1/companies' + _(proxy.resource_class).must_equal Intercom::Company + end + + it 'adds a note to a contact' do + client.expects(:post).with('/contacts/1/notes', {body: note.body}).returns(note.to_hash) + contact.create_note({body: note.body}) + end + + it 'adds a tag to a contact' do + client.expects(:post).with('/contacts/1/tags', "id": tag.id).returns(tag.to_hash) + contact.add_tag({ "id": tag.id }) + end + + it 'removes a tag from a contact' do + client.expects(:delete).with("/contacts/1/tags/#{tag.id}", "id": tag.id ).returns(tag.to_hash) + contact.remove_tag({ "id": tag.id }) + end + + it 'adds a contact to a company' do + client.expects(:post).with('/contacts/1/companies', "id": company.id).returns(test_company) + contact.add_company({ "id": tag.id }) + end + + it 'removes a contact from a company' do + client.expects(:delete).with("/contacts/1/companies/#{company.id}", "id": tag.id ).returns(test_company) + contact.remove_company({ "id": tag.id }) + end + end end end From 3035aa7ace65dec4d2211a447760cd61f30b73bb Mon Sep 17 00:00:00 2001 From: Mohamed Ziata Date: Fri, 6 Mar 2020 18:18:52 +0100 Subject: [PATCH 193/245] [feature] Conversation : Add/remove nested resource 'contact' Fixes #515 --- README.md | 25 ++++++++++++++---------- lib/intercom/conversation.rb | 1 + spec/unit/intercom/conversation_spec.rb | 26 +++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 21118879..da96a59a 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Resources this API supports: https://api.intercom.io/counts https://api.intercom.io/subscriptions https://api.intercom.io/jobs - + ### Examples @@ -99,7 +99,7 @@ contacts = intercom.contacts.search( } ) contacts.each {|c| p c.email} -# For full detail on possible queries, please refer to the API documentation: +# For full detail on possible queries, please refer to the API documentation: # https://developers.intercom.com/intercom-api-reference/reference # Merge a lead into an existing user @@ -191,7 +191,7 @@ intercom.data_attributes.save(attribute) attribute.archived = true intercom.data_attributes.save(attribute) -# Find all customer attributes including archived +# Find all customer attributes including archived customer_attributes_incl_archived = intercom.data_attributes.find_all({"model": "contact", "include_archived": true}) customer_attributes_incl_archived.each { |attr| p attribute.name } ``` @@ -321,7 +321,7 @@ conversation.statistics.time_to_admin_reply conversation.statistics.last_assignment_at # Get information on the sla applied to a conversation -conversation.sla_applied.sla_name +conversation.sla_applied.sla_name # REPLYING TO CONVERSATIONS # User (identified by email) replies with a comment @@ -370,9 +370,9 @@ intercom.conversations.mark_read(conversation.id) intercom.conversations.run_assignment_rules(conversation.id) # Search for conversations -# For full detail on possible queries, please refer to the API documentation: -# https://developers.intercom.com/intercom-api-reference/reference - +# For full detail on possible queries, please refer to the API documentation: +# https://developers.intercom.com/intercom-api-reference/reference + # Search for open conversations sorted by the created_at date conversations = intercom.conversations.search( query: { @@ -386,18 +386,23 @@ conversations = intercom.conversations.search( conversations.each {|c| p c.id} # Tagging for conversations -tag = intercom.tags.find(id: "2") +tag = intercom.tags.find(id: "2") conversation = intercom.conversations.find(id: "1") # An Admin ID is required to add or remove tag on a conversation -admin = intercom.admins.find(id: "1") +admin = intercom.admins.find(id: "1") # Add a tag to a conversation conversation.add_tag(id: tag.id, admin_id: admin.id) # Remove a tag from a conversation conversation.remove_tag(id: tag.id, admin_id: admin.id) - + +# Add a contact to a conversation +conversation.add_contact(admin_id: admin.id, customer: { intercom_user_id: contact.id }) + +# Remove a contact from a conversation +conversation.remove_tag(id: contact.id, admin_id: admin.id) ``` #### Full loading of an embedded entity diff --git a/lib/intercom/conversation.rb b/lib/intercom/conversation.rb index e9edec39..64d7b843 100644 --- a/lib/intercom/conversation.rb +++ b/lib/intercom/conversation.rb @@ -7,5 +7,6 @@ class Conversation include ApiOperations::NestedResource nested_resource_methods :tag, operations: %i[add delete] + nested_resource_methods :contact, operations: %i[add delete], path: :customers end end diff --git a/spec/unit/intercom/conversation_spec.rb b/spec/unit/intercom/conversation_spec.rb index 50a494da..c777bda1 100644 --- a/spec/unit/intercom/conversation_spec.rb +++ b/spec/unit/intercom/conversation_spec.rb @@ -81,5 +81,31 @@ client.expects(:delete).with("/conversations/1/tags/1", { "id": tag.id }).returns(nil) _(proc { conversation.remove_tag({ "id": tag.id }) }).must_raise Intercom::HttpError end + + describe 'contacts' do + let(:response) do + { + customers: [ + { type: test_contact['type'], id: test_contact['id'] } + ] + } + end + + it 'adds a contact to a conversation' do + client.expects(:post) + .with("/conversations/1/customers", + { admin_id: test_admin['id'], customer: { intercom_user_id: test_contact['id'] } }) + .returns(response.to_hash) + conversation.add_contact(admin_id: test_admin['id'], customer: { intercom_user_id: test_contact['id'] }) + end + + it 'removes a contact from a conversation' do + client.expects(:delete) + .with("/conversations/1/customers/aaaaaaaaaaaaaaaaaaaaaaaa", + { id: 'aaaaaaaaaaaaaaaaaaaaaaaa', admin_id: '1234' }) + .returns(response.to_hash) + conversation.remove_contact(id: test_contact['id'], admin_id: test_admin['id']) + end + end end end From 0be174a771338dc9c55416b27eacd07726854567 Mon Sep 17 00:00:00 2001 From: Mohamed Ziata Date: Mon, 9 Mar 2020 13:03:58 +0100 Subject: [PATCH 194/245] [typo] README - Fix typo Co-Authored-By: Matthew Murray --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index da96a59a..09ef9279 100644 --- a/README.md +++ b/README.md @@ -402,7 +402,7 @@ conversation.remove_tag(id: tag.id, admin_id: admin.id) conversation.add_contact(admin_id: admin.id, customer: { intercom_user_id: contact.id }) # Remove a contact from a conversation -conversation.remove_tag(id: contact.id, admin_id: admin.id) +conversation.remove_contact(id: contact.id, admin_id: admin.id) ``` #### Full loading of an embedded entity From 0ec2dbdaf1a02778043d489647b1c2182b96dd5e Mon Sep 17 00:00:00 2001 From: Matthew Murray Date: Tue, 10 Mar 2020 10:15:14 +0000 Subject: [PATCH 195/245] Bump intercom to 4.0.1 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 85502869..5f2ea446 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "4.0.0" + VERSION = "4.0.1" end From 7740007a30fdcceb36d20da9320bc2de5e9c02ad Mon Sep 17 00:00:00 2001 From: Matthew Murray Date: Tue, 10 Mar 2020 10:24:42 +0000 Subject: [PATCH 196/245] Bump version to 4.0.1 --- changes.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/changes.txt b/changes.txt index c6e35cef..1073568c 100644 --- a/changes.txt +++ b/changes.txt @@ -1,4 +1,8 @@ -4.0 +4.0.1 +- Fixed bug with nested resources. +- Support for add/remove contact on conversation object. + +4.0.0 New version to support API version 2.0. - Added support for new Contacts API. - Added support for Conversation Search and for Conversation model changes. From fae9d35ebcbb60015a898f011e20356d466e3af7 Mon Sep 17 00:00:00 2001 From: Rahul D'souza Date: Tue, 14 Jul 2020 18:09:07 +0530 Subject: [PATCH 197/245] Release v4.1.0 (#531) * Articles endpoint SDK support * Collections Endpoint Support (#523) * Sections Endpoint Support (#524) * Added Collections endpoint SDK support * Added test coverage * Added dirty_tracking support for nested objects (#525) * Added support to check for equality of two resources (#527) * Updated README and logged changes for v4.1.0 (#526) --- README.md | 102 +++++++++++++++++- lib/intercom.rb | 6 ++ lib/intercom/article.rb | 7 ++ lib/intercom/client.rb | 12 +++ lib/intercom/collection.rb | 7 ++ lib/intercom/section.rb | 23 ++++ lib/intercom/service/article.rb | 20 ++++ lib/intercom/service/collection.rb | 24 +++++ lib/intercom/service/section.rb | 7 ++ lib/intercom/traits/api_resource.rb | 17 ++- lib/intercom/traits/dirty_tracking.rb | 9 +- spec/spec_helper.rb | 98 +++++++++++++++++ spec/unit/intercom/article_spec.rb | 40 +++++++ spec/unit/intercom/collection_spec.rb | 32 ++++++ spec/unit/intercom/section_spec.rb | 32 ++++++ .../unit/intercom/traits/api_resource_spec.rb | 57 +++++++++- 16 files changed, 488 insertions(+), 5 deletions(-) create mode 100644 lib/intercom/article.rb create mode 100644 lib/intercom/collection.rb create mode 100644 lib/intercom/section.rb create mode 100644 lib/intercom/service/article.rb create mode 100644 lib/intercom/service/collection.rb create mode 100644 lib/intercom/service/section.rb create mode 100644 spec/unit/intercom/article_spec.rb create mode 100644 spec/unit/intercom/collection_spec.rb create mode 100644 spec/unit/intercom/section_spec.rb diff --git a/README.md b/README.md index 09ef9279..59503821 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ intercom = Intercom::Client.new(token: 'my_token') ```ruby # With a versioned app: -intercom = Intercom::Client.new(token: 'my_token', api_version: '2.0') +intercom = Intercom::Client.new(token: 'my_token', api_version: '2.1') ``` If you are building a third party application you can get your access_tokens by [setting-up-oauth](https://developers.intercom.io/page/setting-up-oauth) for Intercom. @@ -61,6 +61,9 @@ Resources this API supports: https://api.intercom.io/counts https://api.intercom.io/subscriptions https://api.intercom.io/jobs + https://api.intercom.io/articles + https://api.intercom.io/help_center/collections + https://api.intercom.io/help_center/sections ### Examples @@ -525,6 +528,103 @@ intercom.subscriptions.delete(subscription) intercom.subscriptions.all ``` +#### Articles +```ruby +# Create an article +article = intercom.articles.create(title: "New Article", author_id: "123456") + +# Create an article with translations +article = intercom.articles.create(title: "New Article", + author_id: "123456", + translated_content: {fr: {title: "Nouvel Article"}, es: {title: "Nuevo artículo"}}) + +# Fetch an article +intercom.articles.find(id: "123456") + +# List all articles +articles = intercom.articles.all +articles.each { |article| p article.title } + +# Update an article +article.title = "Article Updated!" +intercom.articles.save(article) + +# Update an article's existing translation +article.translated_content.en.title = "English Updated!" +intercom.articles.save(article) + +# Update an article by adding a new translation +article.translated_content.es = {title: "Artículo en español"} +intercom.articles.save(article) + +# Delete an article +intercom.articles.delete(article) +``` + +#### Collections +```ruby +# Create a collection +collection = intercom.collections.create(name: "New Collection") + +# Create a collection with translations +collection = intercom.collections.create(name: "New Collection", + translated_content: {fr: {name: "Nouvelle collection"}, es: {name: "Nueva colección"}}) + +# Fetch a collection +intercom.collections.find(id: "123456") + +# List all collections +collections = intercom.collections.all +collections.each { |collection| p collection.name } + +# Update a collection +collection.name = "Collection updated!" +intercom.collections.save(collection) + +# Update a collection's existing translation +collection.translated_content.en.name = "English Updated!" +intercom.collections.save(collection) + +# Update a collection by adding a new translation +collection.translated_content.es = {name: "Colección en español", description: "Descripción en español"} +intercom.collections.save(collection) + +# Delete an collection +intercom.collections.delete(collection) +``` + +#### Sections +```ruby +# Create a section +section = intercom.sections.create(name: "New Section", parent_id: "123456") + +# Create a section with translations +section = intercom.sections.create(name: "New Section", + translated_content: {fr: {name: "Nouvelle section"}, es: {name: "Nueva sección"}}) + +# Fetch a section +intercom.sections.find(id: "123456") + +# List all sections +sections = intercom.sections.all +sections.each { |section| p section.name } + +# Update a section +section.name = "Section updated!" +intercom.sections.save(section) + +# Update a section's existing translation +section.translated_content.en.name = "English Updated!" +intercom.collections.save(section) + +# Update a section by adding a new translation +section.translated_content.es = {name: "Sección en español"} +intercom.collections.save(section) + +# Delete an section +intercom.sections.delete(section) +``` + ### Errors There are different styles for error handling - some people prefer exceptions; some prefer nil and check; some prefer error objects/codes. Balancing these preferences alongside our wish to provide an idiomatic gem has brought us to use the current mechanism of throwing specific exceptions. Our approach in the client is to propagate errors and signal our failure loudly so that erroneous data does not get propagated through our customers' systems - in other words, if you see a `Intercom::ServiceUnavailableError` you know where the problem is. diff --git a/lib/intercom.rb b/lib/intercom.rb index f9587801..b1793064 100644 --- a/lib/intercom.rb +++ b/lib/intercom.rb @@ -2,6 +2,8 @@ require 'intercom/version' require 'intercom/service/admin' +require 'intercom/service/article' +require 'intercom/service/collection' require 'intercom/service/company' require 'intercom/service/contact' require 'intercom/service/conversation' @@ -12,6 +14,7 @@ require 'intercom/service/job' require 'intercom/service/subscription' require 'intercom/service/segment' +require 'intercom/service/section' require 'intercom/service/tag' require 'intercom/service/team' require 'intercom/service/visitor' @@ -24,16 +27,19 @@ require 'intercom/user' require 'intercom/lead' require 'intercom/count' +require 'intercom/collection' require 'intercom/company' require 'intercom/service/data_attribute' require 'intercom/note' require 'intercom/job' require 'intercom/tag' require 'intercom/segment' +require 'intercom/section' require 'intercom/event' require 'intercom/conversation' require 'intercom/message' require 'intercom/admin' +require 'intercom/article' require 'intercom/request' require 'intercom/subscription' require 'intercom/team' diff --git a/lib/intercom/article.rb b/lib/intercom/article.rb new file mode 100644 index 00000000..d1ace3bc --- /dev/null +++ b/lib/intercom/article.rb @@ -0,0 +1,7 @@ +require 'intercom/traits/api_resource' + +module Intercom + class Article + include Traits::ApiResource + end +end diff --git a/lib/intercom/client.rb b/lib/intercom/client.rb index 433c4097..ddc89940 100644 --- a/lib/intercom/client.rb +++ b/lib/intercom/client.rb @@ -48,6 +48,10 @@ def admins Intercom::Service::Admin.new(self) end + def articles + Intercom::Service::Article.new(self) + end + def companies Intercom::Service::Company.new(self) end @@ -84,6 +88,10 @@ def segments Intercom::Service::Segment.new(self) end + def sections + Intercom::Service::Section.new(self) + end + def tags Intercom::Service::Tag.new(self) end @@ -108,6 +116,10 @@ def data_attributes Intercom::Service::DataAttribute.new(self) end + def collections + Intercom::Service::Collection.new(self) + end + def get(path, params) execute_request Intercom::Request.get(path, params) end diff --git a/lib/intercom/collection.rb b/lib/intercom/collection.rb new file mode 100644 index 00000000..3fc7ab26 --- /dev/null +++ b/lib/intercom/collection.rb @@ -0,0 +1,7 @@ +require 'intercom/traits/api_resource' + +module Intercom + class Collection + include Traits::ApiResource + end +end diff --git a/lib/intercom/section.rb b/lib/intercom/section.rb new file mode 100644 index 00000000..75e387ed --- /dev/null +++ b/lib/intercom/section.rb @@ -0,0 +1,23 @@ +require 'intercom/api_operations/list' +require 'intercom/api_operations/find' +require 'intercom/api_operations/save' +require 'intercom/api_operations/delete' + +module Intercom + module Service + class Section < BaseService + include ApiOperations::List + include ApiOperations::Find + include ApiOperations::Save + include ApiOperations::Delete + + def collection_class + Intercom::Section + end + + def collection_name + 'help_center/sections' + end + end + end +end diff --git a/lib/intercom/service/article.rb b/lib/intercom/service/article.rb new file mode 100644 index 00000000..d21bae74 --- /dev/null +++ b/lib/intercom/service/article.rb @@ -0,0 +1,20 @@ +require 'intercom/service/base_service' +require 'intercom/api_operations/find' +require 'intercom/api_operations/list' +require 'intercom/api_operations/delete' +require 'intercom/api_operations/save' + +module Intercom + module Service + class Article < BaseService + include ApiOperations::Find + include ApiOperations::List + include ApiOperations::Delete + include ApiOperations::Save + + def collection_class + Intercom::Article + end + end + end +end diff --git a/lib/intercom/service/collection.rb b/lib/intercom/service/collection.rb new file mode 100644 index 00000000..cbe7a6f2 --- /dev/null +++ b/lib/intercom/service/collection.rb @@ -0,0 +1,24 @@ +require 'intercom/service/base_service' +require 'intercom/api_operations/list' +require 'intercom/api_operations/find' +require 'intercom/api_operations/delete' +require 'intercom/api_operations/save' + +module Intercom + module Service + class Collection < BaseService + include ApiOperations::List + include ApiOperations::Find + include ApiOperations::Delete + include ApiOperations::Save + + def collection_class + Intercom::Collection + end + + def collection_name + "help_center/collections" + end + end + end +end diff --git a/lib/intercom/service/section.rb b/lib/intercom/service/section.rb new file mode 100644 index 00000000..2f03e746 --- /dev/null +++ b/lib/intercom/service/section.rb @@ -0,0 +1,7 @@ +require 'intercom/traits/api_resource' + +module Intercom + class Section + include Traits::ApiResource + end +end diff --git a/lib/intercom/traits/api_resource.rb b/lib/intercom/traits/api_resource.rb index 52707794..8749c196 100644 --- a/lib/intercom/traits/api_resource.rb +++ b/lib/intercom/traits/api_resource.rb @@ -17,6 +17,10 @@ def initialize(attributes = {}) from_hash(attributes) end + def ==(other) + self.class == other.class && to_json == other.to_json + end + def from_response(response) from_hash(response) reset_changed_fields! @@ -37,10 +41,21 @@ def to_hash end end + def to_json(*args) + instance_variables_excluding_dirty_tracking_field.each_with_object({}) do |variable, hash| + next if variable == :@client + + value = instance_variable_get(variable) + hash[variable.to_s.delete('@')] = value.respond_to?(:to_json) ? value.to_json(*args) : value + end + end + def to_submittable_hash submittable_hash = {} to_hash.each do |attribute, value| - submittable_hash[attribute] = value if submittable_attribute?(attribute, value) + next unless submittable_attribute?(attribute, value) + + submittable_hash[attribute] = value.respond_to?(:to_submittable_hash) ? value.to_submittable_hash : value end submittable_hash end diff --git a/lib/intercom/traits/dirty_tracking.rb b/lib/intercom/traits/dirty_tracking.rb index 0d120094..d155c2d9 100644 --- a/lib/intercom/traits/dirty_tracking.rb +++ b/lib/intercom/traits/dirty_tracking.rb @@ -22,7 +22,14 @@ def mark_field_as_changed!(field_name) def field_changed?(field_name) @changed_fields ||= Set.new - @changed_fields.include?(field_name.to_s) + field = instance_variable_get("@#{field_name}") + if field.respond_to?(:field_changed?) + field.to_hash.any? do |attribute, _| + field.field_changed?(attribute) + end + else + @changed_fields.include?(field_name.to_s) + end end def instance_variables_excluding_dirty_tracking_field diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ea084a15..d94db57d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,6 +8,32 @@ require 'pry' include WebMock::API +def test_article + { + "id": "1", + "type": "article", + "workspace_id": "tx2p130c", + "title": "new title", + "description": "test Finished articles are visible when they're added to a Help Center collection", + "body": "

thingbop

", + "author_id": 1, + "state": "draft" + } +end + +def test_updated_article + { + "id": "1", + "type": "article", + "workspace_id": "tx2p130c", + "title": "new updated title", + "description": "test Finished articles are visible when they're added to a Help Center collection", + "body": "

thingbop

", + "author_id": 1, + "state": "draft" + } +end + def test_user(email = 'bob@example.com') { 'type' => 'user', @@ -131,6 +157,41 @@ def test_contact(email = 'bob@example.com', role = 'user') } end +def test_collection + { + 'id' => '1', + 'workspace_id' => 'tx2p130c', + 'name' => 'Collection 1', + 'url' => 'http://www.intercom.test/help/', + 'order' => 1, + 'type' => 'collection', + 'description' => 'Collection desc', + 'icon' => 'book-bookmark' + } +end + +def test_collection_list + { + 'type' => 'list', + 'total_count' => 1, + 'pages' => { + 'page' => 1, + 'per_page' => 20, + 'total_pages' => 1 + }, + 'data' => [{ + 'id' => '1', + 'workspace_id' => 'tx2p130c', + 'name' => 'Collection 1', + 'url' => 'http://www.intercom.test/help/', + 'order' => 1, + 'type' => 'collection', + 'description' => 'Collection desc', + 'icon' => 'book-bookmark' + }] + } +end + def test_visitor { 'type' => 'visitor', @@ -789,6 +850,43 @@ def test_app_count } end +def test_section + { + 'id' => '18', + 'workspace_id' => 'tx2p130c', + 'name' => 'Section 1', + 'url' => 'http://www.intercom.test/help/', + 'order' => 0, + 'created_at' => 1_589_801_953, + 'updated_at' => 1_589_801_953, + 'type' => 'section', + 'parent_id' => 1 + } +end + +def test_section_list + { + 'type' => 'list', + 'total_count' => 1, + 'pages' => { + 'page' => 1, + 'per_page' => 20, + 'total_pages' => 1 + }, + 'data' => [{ + 'id' => '18', + 'workspace_id' => 'tx2p130c', + 'name' => 'Section 1', + 'url' => 'http://www.intercom.test/help/', + 'order' => 0, + 'created_at' => 1_589_801_953, + 'updated_at' => 1_589_801_953, + 'type' => 'section', + 'parent_id' => 1 + }] + } +end + def test_segment_count { 'type' => 'count', diff --git a/spec/unit/intercom/article_spec.rb b/spec/unit/intercom/article_spec.rb new file mode 100644 index 00000000..9fa3a695 --- /dev/null +++ b/spec/unit/intercom/article_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe "Intercom::Article" do + let(:client) { Intercom::Client.new(token: 'token') } + + describe "Getting an Article" do + it "successfully finds an article" do + client.expects(:get).with("/articles/1", {}).returns(test_article) + client.articles.find(id: "1") + end + end + + describe "Creating an Article" do + it "successfully creates and article with information passed individually" do + client.expects(:post).with("/articles", {"title" => "new title", "author_id" => 1, "body" => "

thingbop

", "state" => "draft"}).returns(test_article) + client.articles.create(:title => "new title", :author_id => 1, :body => "

thingbop

", :state => "draft") + end + + it "successfully creates and article with information in json" do + client.expects(:post).with("/articles", {"title" => "new title", "author_id" => 1, "body" => "

thingbop

", "state" => "draft"}).returns(test_article) + client.articles.create({title: "new title", author_id: 1, body: "

thingbop

", state: "draft"}) + end + end + + describe "Updating an article" do + it "successfully updates an article" do + article = Intercom::Article.new(id: 12345) + client.expects(:put).with('/articles/12345', {}) + client.articles.save(article) + end + end + + describe "Deleting an article" do + it "successfully deletes an article" do + article = Intercom::Article.new(id: 12345) + client.expects(:delete).with('/articles/12345', {}) + client.articles.delete(article) + end + end +end \ No newline at end of file diff --git a/spec/unit/intercom/collection_spec.rb b/spec/unit/intercom/collection_spec.rb new file mode 100644 index 00000000..87388c9c --- /dev/null +++ b/spec/unit/intercom/collection_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe Intercom::Collection do + let(:client) { Intercom::Client.new(token: 'token') } + + it 'creates a collection' do + client.expects(:post).with('/help_center/collections', { 'name' => 'Collection 1', 'description' => 'Collection desc' }).returns(test_collection) + client.collections.create(:name => 'Collection 1', :description => 'Collection desc') + end + + it 'lists collections' do + client.expects(:get).with('/help_center/collections', {}).returns(test_collection_list) + client.collections.all.each { |t| } + end + + it 'finds a collection' do + client.expects(:get).with('/help_center/collections/1', {}).returns(test_collection) + client.collections.find(id: '1') + end + + it 'updates a collection' do + collection = Intercom::Collection.new(id: '12345') + client.expects(:put).with('/help_center/collections/12345', {}) + client.collections.save(collection) + end + + it 'deletes a collection' do + collection = Intercom::Collection.new(id: '12345') + client.expects(:delete).with('/help_center/collections/12345', {}) + client.collections.delete(collection) + end +end \ No newline at end of file diff --git a/spec/unit/intercom/section_spec.rb b/spec/unit/intercom/section_spec.rb new file mode 100644 index 00000000..ff73f672 --- /dev/null +++ b/spec/unit/intercom/section_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe Intercom::Section do + let(:client) { Intercom::Client.new(token: 'token') } + + it 'creates a section' do + client.expects(:post).with('/help_center/sections', { 'name' => 'Section 1', 'parent_id' => '1' }).returns(test_section) + client.sections.create(:name => 'Section 1', :parent_id => '1') + end + + it 'lists sections' do + client.expects(:get).with('/help_center/sections', {}).returns(test_section_list) + client.sections.all.each { |t| } + end + + it 'finds a section' do + client.expects(:get).with('/help_center/sections/1', {}).returns(test_section) + client.sections.find(id: '1') + end + + it 'updates a section' do + section = Intercom::Section.new(id: '12345') + client.expects(:put).with('/help_center/sections/12345', {}) + client.sections.save(section) + end + + it 'deletes a section' do + section = Intercom::Section.new(id: '12345') + client.expects(:delete).with('/help_center/sections/12345', {}) + client.sections.delete(section) + end +end \ No newline at end of file diff --git a/spec/unit/intercom/traits/api_resource_spec.rb b/spec/unit/intercom/traits/api_resource_spec.rb index b5724556..7afc144f 100644 --- a/spec/unit/intercom/traits/api_resource_spec.rb +++ b/spec/unit/intercom/traits/api_resource_spec.rb @@ -17,7 +17,15 @@ 'metadata' => { 'type' => 'user', 'color' => 'cyan' - } } + }, + 'nested_fields' => { + 'type' => 'nested_fields_content', + 'field_1' => { + 'type' => 'field_content', + 'name' => 'Nested Field' + } + } + } end let(:object_hash) do @@ -35,6 +43,13 @@ metadata: { type: 'user', color: 'cyan' + }, + nested_fields: { + type: 'nested_fields_content', + field_1: { + type: 'field_content', + name: 'Nested Field' + } } } end @@ -107,12 +122,50 @@ class ConcreteApiResource; include Intercom::Traits::ApiResource; end api_resource.from_hash(object_hash) initialized_api_resource = ConcreteApiResource.new(object_hash) - except(object_json, 'type').keys.each do |attribute| assert_equal initialized_api_resource.send(attribute), api_resource.send(attribute) end end + describe 'correctly equates two resources' do + class DummyResource; include Intercom::Traits::ApiResource; end + + specify 'if each resource has the same class and same value' do + api_resource1 = DummyResource.new(object_json) + api_resource2 = DummyResource.new(object_json) + assert_equal (api_resource1 == api_resource2), true + end + + specify 'if each resource has the same class and different value' do + object2_json = object_json.merge('id' => 'bbbbbb') + api_resource1 = DummyResource.new(object_json) + api_resource2 = DummyResource.new(object2_json) + assert_equal (api_resource1 == api_resource2), false + end + + specify 'if each resource has a different class' do + dummy_resource = DummyResource.new(object_json) + assert_equal (dummy_resource == api_resource), false + end + end + + it 'correctly generates submittable hash when no updates' do + assert_equal api_resource.to_submittable_hash, {} + end + + it 'correctly generates submittable hash when there are updates' do + api_resource.name = 'SuperSuite updated' + api_resource.nested_fields.field_1.name = 'Updated Name' + assert_equal api_resource.to_submittable_hash, { + 'name' => 'SuperSuite updated', + 'nested_fields' => { + 'field_1' => { + 'name' => 'Updated Name' + } + } + } + end + def except(h, *keys) keys.each { |key| h.delete(key) } h From f4413b9b881504f0790305c60a0e72f9fe51fa3c Mon Sep 17 00:00:00 2001 From: Rahul D'souza Date: Tue, 14 Jul 2020 18:46:05 +0530 Subject: [PATCH 198/245] Bump intercom to 4.1.0 (#534) --- README.md | 2 +- changes.txt | 7 +++++++ lib/intercom/version.rb | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 59503821..6f21c67f 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ This version of the gem is compatible with `Ruby 2.1` and above. Using bundler: - gem 'intercom', '~> 4.0' + gem 'intercom', '~> 4.1' ## Basic Usage diff --git a/changes.txt b/changes.txt index 1073568c..03d56f0e 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,10 @@ +4.1.0 +- Added support for new Articles API. +- Added support for new Collections API. +- Added support for new Sections API. +- Added support to equate two resources. +- Fixed issue for dirty tracking nested typed objects. + 4.0.1 - Fixed bug with nested resources. - Support for add/remove contact on conversation object. diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 5f2ea446..a831c139 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "4.0.1" + VERSION = "4.1.0" end From 60bcb5c7d09db9d103f17ed87cbb5b95becbb36b Mon Sep 17 00:00:00 2001 From: theandrewykim Date: Thu, 16 Jul 2020 20:11:20 -0400 Subject: [PATCH 199/245] update readme to show untag company action --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 6f21c67f..4354deb9 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,9 @@ intercom.tags.all.map {|tag| tag.name } # Tag companies tag = intercom.tags.tag(name: 'blue', companies: [{company_id: "42ea2f1b93891f6a99000427"}]) + +# Untag Companies +tag = intercom.tags.untag(name: 'blue', companies: [{ company_id: "42ea2f1b93891f6a99000427" }]) ``` #### Notes From e855241f5f779dad368aa7805c94a73c74789f0c Mon Sep 17 00:00:00 2001 From: Matthew Murray Date: Tue, 11 Aug 2020 16:43:35 +0100 Subject: [PATCH 200/245] Add leads method to client --- lib/intercom/client.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/intercom/client.rb b/lib/intercom/client.rb index ddc89940..883a99ae 100644 --- a/lib/intercom/client.rb +++ b/lib/intercom/client.rb @@ -104,6 +104,10 @@ def users Intercom::Service::User.new(self) end + def leads + Intercom::Service::Lead.new(self) + end + def visitors Intercom::Service::Visitor.new(self) end From 114fcf78b643db6dac48b05279d666c21edafac0 Mon Sep 17 00:00:00 2001 From: Matthew Murray Date: Tue, 11 Aug 2020 16:52:07 +0100 Subject: [PATCH 201/245] Bump intercom to 4.1.1 --- lib/intercom/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index a831c139..9ea75368 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "4.1.0" + VERSION = "4.1.1" end From 1d335c0087d617e61eabe69b931df75989aef4db Mon Sep 17 00:00:00 2001 From: Matthew Murray Date: Tue, 11 Aug 2020 16:56:04 +0100 Subject: [PATCH 202/245] Bump version to 4.1.1 --- changes.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changes.txt b/changes.txt index 03d56f0e..e2960ea4 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +4.1.1 +- Fixed bug with deprecated lead resource. + 4.1.0 - Added support for new Articles API. - Added support for new Collections API. From a12d7c92f3bc972e4893d1ceffb224c372b54fda Mon Sep 17 00:00:00 2001 From: theandrewykim Date: Wed, 30 Sep 2020 15:13:15 -0500 Subject: [PATCH 203/245] refactor error handling --- lib/intercom/request.rb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 0be75505..9caf9e45 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -158,13 +158,7 @@ def execute(target_base_url = nil, token:, read_timeout: 90, open_timeout: 30, a private def raise_errors_on_failure(res) code = res.code.to_i - if code == 404 - raise Intercom::ResourceNotFound, 'Resource Not Found' - elsif code == 401 - raise Intercom::AuthenticationError, 'Unauthorized' - elsif code == 403 - raise Intercom::AuthenticationError, 'Forbidden' - elsif code == 429 + if code ==429 raise Intercom::RateLimitExceeded, 'Rate Limit Exceeded' elsif code == 500 raise Intercom::ServerError, 'Server Error' From 4f683c07c9a6411f1a791acddb9cd8f4a5010b68 Mon Sep 17 00:00:00 2001 From: theandrewykim Date: Wed, 30 Sep 2020 15:15:08 -0500 Subject: [PATCH 204/245] add space --- lib/intercom/request.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 9caf9e45..481f1b1e 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -158,7 +158,7 @@ def execute(target_base_url = nil, token:, read_timeout: 90, open_timeout: 30, a private def raise_errors_on_failure(res) code = res.code.to_i - if code ==429 + if code == 429 raise Intercom::RateLimitExceeded, 'Rate Limit Exceeded' elsif code == 500 raise Intercom::ServerError, 'Server Error' From 08ded8bfaa31ce30382321e56addb236208c831a Mon Sep 17 00:00:00 2001 From: Clayton Passmore Date: Mon, 19 Oct 2020 19:10:32 +0000 Subject: [PATCH 205/245] Allow companies to be deleted --- lib/intercom/service/company.rb | 2 ++ spec/unit/intercom/company_spec.rb | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/lib/intercom/service/company.rb b/lib/intercom/service/company.rb index f23a97c6..b627c452 100644 --- a/lib/intercom/service/company.rb +++ b/lib/intercom/service/company.rb @@ -1,4 +1,5 @@ require 'intercom/service/base_service' +require 'intercom/api_operations/delete' require 'intercom/api_operations/list' require 'intercom/api_operations/scroll' require 'intercom/api_operations/find' @@ -11,6 +12,7 @@ module Intercom module Service class Company < BaseService + include ApiOperations::Delete include ApiOperations::Find include ApiOperations::FindAll include ApiOperations::Load diff --git a/spec/unit/intercom/company_spec.rb b/spec/unit/intercom/company_spec.rb index c7e1c646..9bced631 100644 --- a/spec/unit/intercom/company_spec.rb +++ b/spec/unit/intercom/company_spec.rb @@ -35,4 +35,10 @@ _(proxy.url).must_equal '/companies/1/contacts' _(proxy.resource_class).must_equal Intercom::Contact end + + it "deletes a company" do + company = Intercom::Company.new("id" => "1") + client.expects(:delete).with("/companies/1", {}).returns(test_company) + client.companies.delete(company) + end end From bb23014ed63b606f9c69c8903e6755d55f0b8e67 Mon Sep 17 00:00:00 2001 From: Rahul D'souza Date: Thu, 29 Oct 2020 07:50:35 +0530 Subject: [PATCH 206/245] Add empty hash as payload for run assignment rules (#555) --- lib/intercom/service/conversation.rb | 2 +- spec/unit/intercom/conversation_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/intercom/service/conversation.rb b/lib/intercom/service/conversation.rb index bed5e93b..2a8d8ee4 100644 --- a/lib/intercom/service/conversation.rb +++ b/lib/intercom/service/conversation.rb @@ -58,7 +58,7 @@ def assign(reply_data) def run_assignment_rules(id) collection_name = Utils.resource_class_to_collection_name(collection_class) - response = @client.post("/#{collection_name}/#{id}/run_assignment_rules") + response = @client.post("/#{collection_name}/#{id}/run_assignment_rules", {}) collection_class.new.from_response(response) end end diff --git a/spec/unit/intercom/conversation_spec.rb b/spec/unit/intercom/conversation_spec.rb index c777bda1..79919d54 100644 --- a/spec/unit/intercom/conversation_spec.rb +++ b/spec/unit/intercom/conversation_spec.rb @@ -54,7 +54,7 @@ end it 'runs assignment rules on a conversation' do - client.expects(:post).with('/conversations/147/run_assignment_rules').returns(test_conversation) + client.expects(:post).with('/conversations/147/run_assignment_rules', {}).returns(test_conversation) client.conversations.run_assignment_rules('147') end From b62b5fd587c4ce8d3b0efbaa3a901e820fa829d7 Mon Sep 17 00:00:00 2001 From: Clayton Passmore Date: Wed, 28 Oct 2020 23:05:35 -0400 Subject: [PATCH 207/245] Update spec/unit/intercom/company_spec.rb Co-authored-by: Rahul D'souza --- spec/unit/intercom/company_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/intercom/company_spec.rb b/spec/unit/intercom/company_spec.rb index 9bced631..03fe762b 100644 --- a/spec/unit/intercom/company_spec.rb +++ b/spec/unit/intercom/company_spec.rb @@ -38,7 +38,7 @@ it "deletes a company" do company = Intercom::Company.new("id" => "1") - client.expects(:delete).with("/companies/1", {}).returns(test_company) + client.expects(:delete).with("/companies/1", {}) client.companies.delete(company) end end From 1aa86b04698a497aeea2f580c3ed1a9fa3539b1d Mon Sep 17 00:00:00 2001 From: Rahul D'souza Date: Thu, 29 Oct 2020 20:09:27 +0530 Subject: [PATCH 208/245] Fixed issue with scroll collection proxy (#556) --- lib/intercom/scroll_collection_proxy.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/intercom/scroll_collection_proxy.rb b/lib/intercom/scroll_collection_proxy.rb index 929c1425..48129b3b 100644 --- a/lib/intercom/scroll_collection_proxy.rb +++ b/lib/intercom/scroll_collection_proxy.rb @@ -26,7 +26,7 @@ def next(scroll_parameter = nil) raise Intercom::HttpError, 'Http Error - No response entity returned' unless response_hash @scroll_param = extract_scroll_param(response_hash) - top_level_entity_key = deserialize_response_hash(response_hash) + top_level_entity_key = entity_key_from_response(response_hash) response_hash[top_level_entity_key] = response_hash[top_level_entity_key].map do |object_json| Lib::TypedJsonDeserializer.new(object_json, @client).deserialize end @@ -44,7 +44,8 @@ def each(&block) end raise Intercom::HttpError, 'Http Error - No response entity returned' unless response_hash - response_hash[deserialize_response_hash(response_hash)].each do |object_json| + top_level_entity_key = entity_key_from_response(response_hash) + response_hash[top_level_entity_key].each do |object_json| block.call Lib::TypedJsonDeserializer.new(object_json, @client).deserialize end scroll_param = extract_scroll_param(response_hash) @@ -55,8 +56,8 @@ def each(&block) private - def deserialize_response_hash(response_hash) - top_level_type = response_hash.delete('type') + def entity_key_from_response(response_hash) + top_level_type = response_hash['type'] if resource_name == 'subscriptions' 'items' else @@ -65,7 +66,7 @@ def deserialize_response_hash(response_hash) end def records_present?(response_hash) - !response_hash[@resource_name].empty? + !response_hash[entity_key_from_response(response_hash)].empty? end def extract_scroll_param(response_hash) From a1bbff4afcd8939f85f45560ee14b17dbef27a2d Mon Sep 17 00:00:00 2001 From: Markus Muehlberger <1052277+mmuehlberger@users.noreply.github.com> Date: Fri, 30 Oct 2020 12:30:17 +0000 Subject: [PATCH 209/245] Add ability to archive/unarchive contacts (#551) Co-authored-by: Markus Muehlberger --- lib/intercom/service/contact.rb | 10 ++++++++++ spec/unit/intercom/contact_spec.rb | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/lib/intercom/service/contact.rb b/lib/intercom/service/contact.rb index 737dc96e..7b8b7504 100644 --- a/lib/intercom/service/contact.rb +++ b/lib/intercom/service/contact.rb @@ -33,6 +33,16 @@ def merge(lead, user) user.from_response(response) end + def archive(contact) + @client.post("/#{collection_name}/#{contact.id}/archive", {}) + contact + end + + def unarchive(contact) + @client.post("/#{collection_name}/#{contact.id}/unarchive", {}) + contact + end + private def raise_invalid_merge_error raise Intercom::InvalidMergeError, 'Merging can only be performed on a lead into a user' end diff --git a/spec/unit/intercom/contact_spec.rb b/spec/unit/intercom/contact_spec.rb index b0193fe4..79fc185b 100644 --- a/spec/unit/intercom/contact_spec.rb +++ b/spec/unit/intercom/contact_spec.rb @@ -248,6 +248,18 @@ client.contacts.delete(contact) end + it 'archives a contact' do + contact = Intercom::Contact.new('id' => '1') + client.expects(:post).with('/contacts/1/archive', {}) + client.contacts.archive(contact) + end + + it 'unarchives a contact' do + contact = Intercom::Contact.new('id' => '1') + client.expects(:post).with('/contacts/1/unarchive', {}) + client.contacts.unarchive(contact) + end + describe 'merging' do let(:lead) { Intercom::Contact.from_api(external_id: 'contact_id', role: 'lead') } let(:user) { Intercom::Contact.from_api(id: 'external_id', role: 'user') } From c62d7006a55c8bcef9f5f79980223018206ea0f8 Mon Sep 17 00:00:00 2001 From: Rahul D'souza Date: Fri, 30 Oct 2020 18:10:14 +0530 Subject: [PATCH 210/245] Revert "refactor error handling" (#558) --- lib/intercom/request.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 481f1b1e..0be75505 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -158,7 +158,13 @@ def execute(target_base_url = nil, token:, read_timeout: 90, open_timeout: 30, a private def raise_errors_on_failure(res) code = res.code.to_i - if code == 429 + if code == 404 + raise Intercom::ResourceNotFound, 'Resource Not Found' + elsif code == 401 + raise Intercom::AuthenticationError, 'Unauthorized' + elsif code == 403 + raise Intercom::AuthenticationError, 'Forbidden' + elsif code == 429 raise Intercom::RateLimitExceeded, 'Rate Limit Exceeded' elsif code == 500 raise Intercom::ServerError, 'Server Error' From bab09db1e52d91191f3baeab23f01cbb8176a1ba Mon Sep 17 00:00:00 2001 From: Rahul D'souza Date: Fri, 30 Oct 2020 18:17:06 +0530 Subject: [PATCH 211/245] Add support for listing contact segments (#557) --- README.md | 3 +++ lib/intercom/contact.rb | 1 + lib/intercom/segment.rb | 4 ++++ spec/unit/intercom/contact_spec.rb | 7 +++++++ 4 files changed, 15 insertions(+) diff --git a/README.md b/README.md index 4354deb9..ce3d84e3 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,9 @@ contact.create_note(body: "

Text for the note

") # List notes for a contact contact.notes.each {|n| p n.body} +# List segments for a contact +contact.segments.each {|segment| p segment.name} + # Add a contact to a company company = intercom.companies.find(id: "123") contact.add_company(id: company.id) diff --git a/lib/intercom/contact.rb b/lib/intercom/contact.rb index 487b5b0f..f5332ab5 100644 --- a/lib/intercom/contact.rb +++ b/lib/intercom/contact.rb @@ -13,6 +13,7 @@ class Contact nested_resource_methods :tag, operations: %i[add delete list] nested_resource_methods :note, operations: %i[create list] nested_resource_methods :company, operations: %i[add delete list] + nested_resource_methods :segment, operations: %i[list] def self.collection_proxy_class Intercom::BaseCollectionProxy diff --git a/lib/intercom/segment.rb b/lib/intercom/segment.rb index 88de03d9..589ae474 100644 --- a/lib/intercom/segment.rb +++ b/lib/intercom/segment.rb @@ -3,5 +3,9 @@ module Intercom class Segment include Traits::ApiResource + + def self.collection_proxy_class + Intercom::BaseCollectionProxy + end end end diff --git a/spec/unit/intercom/contact_spec.rb b/spec/unit/intercom/contact_spec.rb index 79fc185b..aca5f935 100644 --- a/spec/unit/intercom/contact_spec.rb +++ b/spec/unit/intercom/contact_spec.rb @@ -285,6 +285,13 @@ _(proxy.resource_class).must_equal Intercom::Note end + it 'returns a collection proxy for listing segments' do + proxy = contact.segments + _(proxy.resource_name).must_equal 'segments' + _(proxy.url).must_equal '/contacts/1/segments' + _(proxy.resource_class).must_equal Intercom::Segment + end + it 'returns a collection proxy for listing tags' do proxy = contact.tags _(proxy.resource_name).must_equal 'tags' From 215c97e9225867631d534a13e08873437980221a Mon Sep 17 00:00:00 2001 From: Rahul D'souza Date: Fri, 30 Oct 2020 18:43:54 +0530 Subject: [PATCH 212/245] Readme updates (#559) --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index ce3d84e3..c30b4b40 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,12 @@ intercom.contacts.save(contact) contact.role = "user" intercom.contacts.save(contact) +# Archive a contact +intercom.contacts.archive(contact) + +# Unarchive a contact +intercom.contacts.unarchive(contact) + # Delete a contact permanently intercom.contacts.delete(contact) @@ -169,6 +175,9 @@ company = intercom.companies.find(id: "41e66f0313708347cb0000d0") company.name = 'Updated company name' intercom.companies.save(company) +# Delete a company +intercom.companies.delete(company) + # Iterate over all companies intercom.companies.all.each {|company| puts %Q(#{company.name} - #{company.custom_attributes["referral_source"]}) } intercom.companies.all.map {|company| company.name } From 93d18bf3722ae7e3ec82a86a50a612d039dd6fc5 Mon Sep 17 00:00:00 2001 From: Rahul D'souza Date: Fri, 30 Oct 2020 19:56:34 +0530 Subject: [PATCH 213/245] Bump version to 4.1.2 (#560) --- README.md | 2 +- changes.txt | 7 +++++++ lib/intercom/version.rb | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c30b4b40..bc9f24b8 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ intercom = Intercom::Client.new(token: 'my_token') ```ruby # With a versioned app: -intercom = Intercom::Client.new(token: 'my_token', api_version: '2.1') +intercom = Intercom::Client.new(token: 'my_token', api_version: '2.2') ``` If you are building a third party application you can get your access_tokens by [setting-up-oauth](https://developers.intercom.io/page/setting-up-oauth) for Intercom. diff --git a/changes.txt b/changes.txt index e2960ea4..08991b76 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,10 @@ +4.1.2 +- Adding support for company delete. +- Adding support for archiving/unarchiving contacts. +- Adding support for listing contact segments. +- Fixed issue with scroll collection proxy. +- Fixed issue with running assignment rules on a conversation. + 4.1.1 - Fixed bug with deprecated lead resource. diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 9ea75368..df41bd76 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "4.1.1" + VERSION = "4.1.2" end From 50db6c1a6f4a9c3215ae7bd1a54d49aa4154a855 Mon Sep 17 00:00:00 2001 From: Finn Lawrence Date: Wed, 20 Jan 2021 12:21:21 +1300 Subject: [PATCH 214/245] Update Readme with correct commands for running spec --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bc9f24b8..056de4d3 100644 --- a/README.md +++ b/README.md @@ -700,13 +700,13 @@ intercom = Intercom::Client.new(token: ENV['AT'], handle_rate_limit: true) ```bash # all tests -bundle exec spec +bundle exec rake spec # unit tests -bundle exec spec:unit +bundle exec rake spec:unit # integration tests -bundle exec spec:integration +bundle exec rake spec:integration # single test file bundle exec m spec/unit/intercom/job_spec.rb From 9bd034d9f1e7ef84105765712115ce84f23cab16 Mon Sep 17 00:00:00 2001 From: Finn Lawrence Date: Wed, 20 Jan 2021 13:01:56 +1300 Subject: [PATCH 215/245] Move dynamic accessors to instance rather than class --- lib/intercom/lib/dynamic_accessors.rb | 19 +++++++++---------- spec/unit/intercom/contact_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/lib/intercom/lib/dynamic_accessors.rb b/lib/intercom/lib/dynamic_accessors.rb index 1b053d2e..9d26b4fc 100644 --- a/lib/intercom/lib/dynamic_accessors.rb +++ b/lib/intercom/lib/dynamic_accessors.rb @@ -5,20 +5,19 @@ module DynamicAccessors class << self def define_accessors(attribute, value, object) - klass = object.class if attribute.to_s.end_with?('_at') && attribute.to_s != 'update_last_request_at' - define_date_based_accessors(attribute, value, klass) + define_date_based_accessors(attribute, value, object) elsif object.flat_store_attribute?(attribute) - define_flat_store_based_accessors(attribute, value, klass) + define_flat_store_based_accessors(attribute, value, object) else - define_standard_accessors(attribute, value, klass) + define_standard_accessors(attribute, value, object) end end private - def define_flat_store_based_accessors(attribute, value, klass) - klass.class_eval %Q" + def define_flat_store_based_accessors(attribute, value, object) + object.instance_eval %Q" def #{attribute}=(value) mark_field_as_changed!(:#{attribute}) @#{attribute} = Intercom::Lib::FlatStore.new(value) @@ -29,8 +28,8 @@ def #{attribute} " end - def define_date_based_accessors(attribute, value, klass) - klass.class_eval %Q" + def define_date_based_accessors(attribute, value, object) + object.instance_eval %Q" def #{attribute}=(value) mark_field_as_changed!(:#{attribute}) @#{attribute} = value.nil? ? nil : value.to_i @@ -41,8 +40,8 @@ def #{attribute} " end - def define_standard_accessors(attribute, value, klass) - klass.class_eval %Q" + def define_standard_accessors(attribute, value, object) + object.instance_eval %Q" def #{attribute}=(value) mark_field_as_changed!(:#{attribute}) @#{attribute} = value diff --git a/spec/unit/intercom/contact_spec.rb b/spec/unit/intercom/contact_spec.rb index aca5f935..92d5525b 100644 --- a/spec/unit/intercom/contact_spec.rb +++ b/spec/unit/intercom/contact_spec.rb @@ -274,6 +274,7 @@ describe 'nested resources' do let(:contact) { Intercom::Contact.new(id: '1', client: client) } + let(:contact_no_tags) { Intercom::Contact.new(id: '2', client: client, tags: []) } let(:company) { Intercom::Company.new(id: '1') } let(:tag) { Intercom::Tag.new(id: '1') } let(:note) { Intercom::Note.new(body: "

Text for the note

") } @@ -299,6 +300,27 @@ _(proxy.resource_class).must_equal Intercom::Tag end + it 'returns correct tags from differring contacts' do + client.expects(:get).with('/contacts/1/tags', {}).returns({ + 'type' => 'tag.list', + 'tags' => [ + { + 'type' => 'tag', + 'id' => '1', + 'name' => 'VIP Customer' + }, + { + 'type' => 'tag', + 'id' => '2', + 'name' => 'Test tag' + } + ] + }) + + _(contact_no_tags.tags.map{ |t| t.id }).must_equal [] + _(contact.tags.map{ |t| t.id }).must_equal ['1', '2'] + end + it 'returns a collection proxy for listing companies' do proxy = contact.companies _(proxy.resource_name).must_equal 'companies' From 35af24bf6047b2e2143981071ce75311678e6299 Mon Sep 17 00:00:00 2001 From: Adam Martinik Date: Thu, 15 Apr 2021 13:19:01 +0200 Subject: [PATCH 216/245] Fix readme typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bc9f24b8..6f2bc1ea 100644 --- a/README.md +++ b/README.md @@ -208,7 +208,7 @@ intercom.data_attributes.save(attribute) # Find all customer attributes including archived customer_attributes_incl_archived = intercom.data_attributes.find_all({"model": "contact", "include_archived": true}) -customer_attributes_incl_archived.each { |attr| p attribute.name } +customer_attributes_incl_archived.each { |attr| p attr.name } ``` #### Events From acd7d8916496f5721fe6a31f9ebfc1c00aef94bf Mon Sep 17 00:00:00 2001 From: Johnny Nader Date: Mon, 3 May 2021 18:59:08 +0100 Subject: [PATCH 217/245] update readme --- README.md | 88 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 6f2bc1ea..5359c114 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,26 @@ # intercom-ruby -Ruby bindings for the Intercom API (https://developers.intercom.io/reference). +[![Circle CI](https://circleci.com/gh/intercom/intercom-ruby.png?style=shield)](https://circleci.com/gh/intercom/intercom-ruby) +[![gem](https://img.shields.io/gem/v/intercom)](https://rubygems.org/gems/intercom) +![Intercom API Version](https://img.shields.io/badge/Intercom%20API%20Version-2.2-blue) + +> Ruby bindings for the [Intercom API](https://developers.intercom.io/reference). + +## Project Updates + +### Maintenance + +We're currently building a new team to provide in-depth and dedicated SDK support. + +In the meantime, we'll be operating on limited capacity, meaning all pull requests will be evaluated on a best effort basis and will be limited to critical issues. + +We'll communicate all relevant updates as we build this new team and support strategy in the coming months. [API Documentation](https://developers.intercom.io/docs) [Gem Documentation](http://rubydoc.info/github/intercom/intercom-ruby/master/frames) -For generating Intercom JavaScript script tags for Rails, please see https://github.com/intercom/intercom-rails. +For generating Intercom JavaScript script tags for Rails, please see [intercom/intercom-rails](https://github.com/intercom/intercom-rails) ## Upgrading information @@ -16,12 +30,15 @@ This version of the gem is compatible with `Ruby 2.1` and above. ## Installation - - gem install intercom +```bash +gem install intercom +``` Using bundler: - gem 'intercom', '~> 4.1' +```bundler +gem 'intercom', '~> 4.1' +``` ## Basic Usage @@ -46,29 +63,31 @@ You can also use the [omniauth-intercom lib](https://github.com/intercom/omniaut Resources this API supports: - https://api.intercom.io/contacts - https://api.intercom.io/visitors - https://api.intercom.io/companies - https://api.intercom.io/data_attributes - https://api.intercom.io/events - https://api.intercom.io/tags - https://api.intercom.io/notes - https://api.intercom.io/segments - https://api.intercom.io/conversations - https://api.intercom.io/messages - https://api.intercom.io/admins - https://api.intercom.io/teams - https://api.intercom.io/counts - https://api.intercom.io/subscriptions - https://api.intercom.io/jobs - https://api.intercom.io/articles - https://api.intercom.io/help_center/collections - https://api.intercom.io/help_center/sections - +```text +https://api.intercom.io/contacts +https://api.intercom.io/visitors +https://api.intercom.io/companies +https://api.intercom.io/data_attributes +https://api.intercom.io/events +https://api.intercom.io/tags +https://api.intercom.io/notes +https://api.intercom.io/segments +https://api.intercom.io/conversations +https://api.intercom.io/messages +https://api.intercom.io/admins +https://api.intercom.io/teams +https://api.intercom.io/counts +https://api.intercom.io/subscriptions +https://api.intercom.io/jobs +https://api.intercom.io/articles +https://api.intercom.io/help_center/collections +https://api.intercom.io/help_center/sections +``` ### Examples #### Contacts + Note that this is a new resource compatible only with the new [Contacts API](https://developers.intercom.com/intercom-api-reference/reference#contacts-model) released in API v2.0. ```ruby @@ -146,6 +165,7 @@ contact.companies.each {|c| p c.name} ``` #### Visitors + ```ruby # Get and update a visitor visitor = intercom.visitors.find(id: "5dd570e7b1b922452676af23") @@ -161,6 +181,7 @@ intercom.visitors.convert(visitor, user) ``` #### Companies + ```ruby # Find a company by company_id company = intercom.companies.find(company_id: "44") @@ -188,7 +209,9 @@ intercom.companies.scroll.each { |comp| puts comp.name} ``` #### Data Attributes + Data Attributes are a type of metadata used to describe your customer and company models. These include standard and custom attributes. + ```ruby # Create a new custom data attribute intercom.data_attributes.create({ name: "test_attribute", model: "contact", data_type: "string" }) @@ -212,6 +235,7 @@ customer_attributes_incl_archived.each { |attr| p attr.name } ``` #### Events + ```ruby intercom.events.create( event_name: "invited-friend", @@ -257,7 +281,8 @@ intercom.events.create( ) ``` -The metadata key values in the example are treated as follows- +The metadata key values in the example are treated as follows: + - order_date: a Date (key ends with '_date') - stripe_invoice: The identifier of the Stripe invoice (has a 'stripe_invoice' key) - order_number: a Rich Link (value contains 'url' and 'value' keys) @@ -266,6 +291,7 @@ The metadata key values in the example are treated as follows- *NB:* This version of the gem reserves the field name `type` in Event data. #### Tags + ```ruby # Iterate over all tags intercom.tags.all.each {|tag| "#{tag.id} - #{tag.name}" } @@ -279,12 +305,14 @@ tag = intercom.tags.untag(name: 'blue', companies: [{ company_id: "42ea2f1b93891 ``` #### Notes + ```ruby # Find a note by id note = intercom.notes.find(id: "123") ``` #### Segments + ```ruby # Find a segment segment = intercom.segments.find(id: segment_id) @@ -294,6 +322,7 @@ intercom.segments.all.each {|segment| puts "id: #{segment.id} name: #{segment.na ``` #### Conversations + ```ruby # Iterate over all conversations for your app intercom.conversations.all.each { |convo| ... } @@ -424,6 +453,7 @@ conversation.remove_contact(id: contact.id, admin_id: admin.id) ``` #### Full loading of an embedded entity + ```ruby # Given a conversation with a partial contact, load the full contact. This can be # done for any entity @@ -431,6 +461,7 @@ intercom.contacts.load(conversation.contacts.first) ``` #### Sending messages + ```ruby # InApp message from admin to user @@ -497,6 +528,7 @@ intercom.messages.create({ ``` #### Admins + ```ruby # Find access token owner (only with Personal Access Token and OAuth) intercom.admins.me @@ -507,6 +539,7 @@ intercom.admins.all.each {|admin| puts admin.email } ``` #### Teams + ```ruby # Find a team by id intercom.teams.find(id: team_id) @@ -544,6 +577,7 @@ intercom.subscriptions.all ``` #### Articles + ```ruby # Create an article article = intercom.articles.create(title: "New Article", author_id: "123456") @@ -577,6 +611,7 @@ intercom.articles.delete(article) ``` #### Collections + ```ruby # Create a collection collection = intercom.collections.create(name: "New Collection") @@ -609,6 +644,7 @@ intercom.collections.delete(collection) ``` #### Sections + ```ruby # Create a section section = intercom.sections.create(name: "New Section", parent_id: "123456") @@ -674,7 +710,7 @@ intercom.rate_limit_details You can handle the rate limits yourself but a simple option is to use the handle_rate_limit flag. This will automatically catch the 429 rate limit exceeded error and wait until the reset time to retry. After three retries a rate limit exception will be raised. Encountering this error frequently may require a revisiting of your usage of the API. -``` +```ruby intercom = Intercom::Client.new(token: ENV['AT'], handle_rate_limit: true) ``` From 095caa017e3da131cf3424e95a8c18af7dc2afb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Coelho?= Date: Tue, 1 Mar 2022 10:44:33 +0000 Subject: [PATCH 218/245] Fix collection successive iterations when there are multiple pages (#581) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Coelho Co-authored-by: Sean Healy --- lib/intercom/base_collection_proxy.rb | 1 + .../intercom/base_collection_proxy_spec.rb | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/intercom/base_collection_proxy.rb b/lib/intercom/base_collection_proxy.rb index 806e0125..bce14a9f 100644 --- a/lib/intercom/base_collection_proxy.rb +++ b/lib/intercom/base_collection_proxy.rb @@ -58,6 +58,7 @@ def has_next_link?(response_hash) @params[:starting_after] = paging_next['starting_after'] return true else + @params[:starting_after] = nil return false end end diff --git a/spec/unit/intercom/base_collection_proxy_spec.rb b/spec/unit/intercom/base_collection_proxy_spec.rb index ca37b720..473c4e39 100644 --- a/spec/unit/intercom/base_collection_proxy_spec.rb +++ b/spec/unit/intercom/base_collection_proxy_spec.rb @@ -4,7 +4,7 @@ let(:client) { Intercom::Client.new(token: 'token') } it "stops iterating if no starting after value" do - client.expects(:get).with("/contacts", {}). returns(page_of_contacts(false)) + client.expects(:get).with("/contacts", {}).returns(page_of_contacts(false)) emails = [] client.contacts.all.each { |contact| emails << contact.email } _(emails).must_equal %w[test1@example.com test2@example.com test3@example.com] @@ -15,6 +15,7 @@ client.expects(:get).with('/contacts', { starting_after: "EnCrYpTeDsTrInG" }).returns(page_of_contacts(false)) emails = [] client.contacts.all.each { |contact| emails << contact.email } + _(emails).must_equal %w[test1@example.com test2@example.com test3@example.com test1@example.com test2@example.com test3@example.com] end it "supports indexed array access" do @@ -27,4 +28,20 @@ emails = client.contacts.all.map { |contact| contact.email } _(emails).must_equal %w[test1@example.com test2@example.com test3@example.com] end + + it "keeps entire collection iterable after first iteration" do + contacts = client.contacts.all + emails_iter1 = [] + emails_iter2 = [] + expects_pagination = proc do + client.expects(:get).with("/contacts", {}).returns(page_of_contacts(true)) + client.expects(:get).with("/contacts", { starting_after: "EnCrYpTeDsTrInG" }).returns(page_of_contacts(false)) + end + + expects_pagination.call + contacts.each { |contact| emails_iter1 << contact.email } + expects_pagination.call + contacts.each { |contact| emails_iter2 << contact.email } + _(emails_iter1).must_equal emails_iter2 + end end From 39de43488146a51e38fd1cf7f1aa8aaf1b976c9a Mon Sep 17 00:00:00 2001 From: Farha Jawed Date: Tue, 1 Mar 2022 05:45:55 -0500 Subject: [PATCH 219/245] Add Intercom::GatewayTimeoutError to README (#574) Co-authored-by: Sean Healy Co-authored-by: Johnny Nader <30523158+johnnynader@users.noreply.github.com> --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fd21328a..467cd901 100644 --- a/README.md +++ b/README.md @@ -696,6 +696,7 @@ Intercom::RateLimitExceeded Intercom::AttributeNotSetError # Raised when you try to call a getter that does not exist on an object Intercom::MultipleMatchingUsersError Intercom::HttpError # Raised when response object is unexpectedly nil +Intercom::GatewayTimeoutError ``` ### Rate Limiting From b4b4be319a3578cc9fa7aa9d5bf1173f641ca394 Mon Sep 17 00:00:00 2001 From: Sean Healy Date: Tue, 1 Mar 2022 11:06:31 +0000 Subject: [PATCH 220/245] Bump intercom to 4.1.3 --- changes.txt | 5 +++++ lib/intercom/version.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/changes.txt b/changes.txt index 08991b76..0783aa8d 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,8 @@ +4.1.3 +- Updated ReadMe with more errors. +- Fixed issue where paginated requests could only be iterated through once. +- Moved Dynamic accessors from class level to instance level. + 4.1.2 - Adding support for company delete. - Adding support for archiving/unarchiving contacts. diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index df41bd76..211020ef 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "4.1.2" + VERSION = "4.1.3" end From 675bea4ec617181e9dc207be9ccf620a45a0c839 Mon Sep 17 00:00:00 2001 From: Suma Prakash Date: Thu, 10 Nov 2022 09:53:47 +0000 Subject: [PATCH 221/245] Updating delete tag endpoint to match API 2.5 version changes --- README.md | 9 +++++++++ lib/intercom/errors.rb | 3 +++ lib/intercom/request.rb | 2 ++ spec/unit/intercom/tag_spec.rb | 6 ++++++ 4 files changed, 20 insertions(+) diff --git a/README.md b/README.md index 467cd901..b7a7188f 100644 --- a/README.md +++ b/README.md @@ -303,6 +303,15 @@ tag = intercom.tags.tag(name: 'blue', companies: [{company_id: "42ea2f1b93891f6a # Untag Companies tag = intercom.tags.untag(name: 'blue', companies: [{ company_id: "42ea2f1b93891f6a99000427" }]) ``` +```ruby +#delete tags +``` +Note: If there any depedent objects for the tag we are trying to delete, then an error TagHasDependentObjects will be thrown + +```ruby +tag = intercom.tags.find(id:"123") +intercom.tags.delete(tag) +``` #### Notes diff --git a/lib/intercom/errors.rb b/lib/intercom/errors.rb index 5d7e594a..b3af0bd5 100644 --- a/lib/intercom/errors.rb +++ b/lib/intercom/errors.rb @@ -99,6 +99,9 @@ class InvalidDocumentError < IntercomError; end # Raised when a merge is invalid class InvalidMergeError < IntercomError; end + # Raised when a tag has dependent objects + class TagHasDependentObjects < IntercomError; end + # # Non-public errors (internal to the gem) # diff --git a/lib/intercom/request.rb b/lib/intercom/request.rb index 0be75505..40379939 100644 --- a/lib/intercom/request.rb +++ b/lib/intercom/request.rb @@ -222,6 +222,8 @@ def execute(target_base_url = nil, token:, read_timeout: 90, open_timeout: 30, a raise Intercom::ApiVersionInvalid.new(error_details['message'], error_context) when 'scroll_exists' raise Intercom::ScrollAlreadyExistsError.new(error_details['message'], error_context) + when 'tag_has_dependent_objects' + raise Intercom::TagHasDependentObjects.new(error_details['message'], error_context) when nil, '' raise Intercom::UnexpectedError.new(message_for_unexpected_error_without_type(error_details, parsed_http_code), error_context) else diff --git a/spec/unit/intercom/tag_spec.rb b/spec/unit/intercom/tag_spec.rb index 07a7d4d9..b18c97a7 100644 --- a/spec/unit/intercom/tag_spec.rb +++ b/spec/unit/intercom/tag_spec.rb @@ -28,4 +28,10 @@ client.expects(:post).with('/tags', 'name' => 'Test Tag', 'companies' => [{ company_id: 'abc123', untag: true }, { company_id: 'def456', untag: true }], 'tag_or_untag' => 'untag').returns(test_tag) client.tags.untag(name: 'Test Tag', companies: [{ company_id: 'abc123' }, { company_id: 'def456' }]) end + + it 'delete tags' do + tag = Intercom::Tag.new('id' => '1') + client.expects(:delete).with('/tags/1', {}).returns(tag) + client.tags.delete(tag) + end end From a7bddce1f3514c3353cbaecb53781762abdf499e Mon Sep 17 00:00:00 2001 From: Suma Prakash Date: Thu, 10 Nov 2022 15:32:32 +0000 Subject: [PATCH 222/245] Changing the readme --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b7a7188f..e697169a 100644 --- a/README.md +++ b/README.md @@ -302,13 +302,11 @@ tag = intercom.tags.tag(name: 'blue', companies: [{company_id: "42ea2f1b93891f6a # Untag Companies tag = intercom.tags.untag(name: 'blue', companies: [{ company_id: "42ea2f1b93891f6a99000427" }]) -``` -```ruby + + #delete tags -``` -Note: If there any depedent objects for the tag we are trying to delete, then an error TagHasDependentObjects will be thrown -```ruby +#Note: If there any depedent objects for the tag we are trying to delete, then an error TagHasDependentObjects will be thrown tag = intercom.tags.find(id:"123") intercom.tags.delete(tag) ``` From d4242b71e49f9d69cddb4e02bc86e007e7744bcb Mon Sep 17 00:00:00 2001 From: Suma Prakash Date: Fri, 11 Nov 2022 14:16:13 +0000 Subject: [PATCH 223/245] Updating readme according to new version changes --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 467cd901..d3bc574f 100644 --- a/README.md +++ b/README.md @@ -525,6 +525,16 @@ intercom.messages.create({ }, body: "halp" }) + +#From version 2.6 the type contact is not supported and you would have to use leads to send messages to a lead. + +intercom.messages.create({ + from: { + type: "lead", + id: "536e5643as316c83104c400671" + }, + body: "halp" +}) ``` #### Admins From 43d6fbfb6e58d887bb991b1dab1a4f9fbee6426c Mon Sep 17 00:00:00 2001 From: Sean Healy Date: Fri, 11 Nov 2022 16:06:23 +0000 Subject: [PATCH 224/245] Add switch + data export api support --- README.md | 23 ++++++++++++++++ lib/intercom.rb | 4 +++ lib/intercom/client.rb | 8 ++++++ lib/intercom/export_content.rb | 7 +++++ lib/intercom/phone_call_redirect.rb | 7 +++++ lib/intercom/service/conversation.rb | 4 +++ lib/intercom/service/export_content.rb | 30 +++++++++++++++++++++ lib/intercom/service/phone_call_redirect.rb | 15 +++++++++++ spec/unit/intercom/export_content_spec.rb | 28 +++++++++++++++++++ spec/unit/intercom/phone_call_redirect.rb | 12 +++++++++ 10 files changed, 138 insertions(+) create mode 100644 lib/intercom/export_content.rb create mode 100644 lib/intercom/phone_call_redirect.rb create mode 100644 lib/intercom/service/export_content.rb create mode 100644 lib/intercom/service/phone_call_redirect.rb create mode 100644 spec/unit/intercom/export_content_spec.rb create mode 100644 spec/unit/intercom/phone_call_redirect.rb diff --git a/README.md b/README.md index 467cd901..4f84104e 100644 --- a/README.md +++ b/README.md @@ -676,6 +676,29 @@ intercom.collections.save(section) intercom.sections.delete(section) ``` +#### Phone Call Redirect (switch) + +```ruby +# Create a redirect +redirect = intercom.phone_call_redirect.create(phone_number: "+353871234567") + +``` + +#### Data Content Export + +```ruby +# Create a data export +export = intercom.data_content_export.create(created_at_after: 1667566801, created_at_before: 1668085202) + + +#View a data export +export = intercom.data_content_export.find(id: 'k0e27ohsyvh8ef3m') + +# Cancel a data export +export = intercom.data_content_export.cancel('k0e27ohsyvh8ef3m') + +``` + ### Errors There are different styles for error handling - some people prefer exceptions; some prefer nil and check; some prefer error objects/codes. Balancing these preferences alongside our wish to provide an idiomatic gem has brought us to use the current mechanism of throwing specific exceptions. Our approach in the client is to propagate errors and signal our failure loudly so that erroneous data does not get propagated through our customers' systems - in other words, if you see a `Intercom::ServiceUnavailableError` you know where the problem is. diff --git a/lib/intercom.rb b/lib/intercom.rb index b1793064..c398e681 100644 --- a/lib/intercom.rb +++ b/lib/intercom.rb @@ -21,6 +21,8 @@ require 'intercom/service/user' require 'intercom/service/lead' require 'intercom/deprecated_resources.rb' +require 'intercom/service/export_content' +require 'intercom/service/phone_call_redirect' require 'intercom/options' require 'intercom/client' require 'intercom/contact' @@ -46,6 +48,8 @@ require 'intercom/errors' require 'intercom/visitor' require 'intercom/data_attribute' +require 'intercom/export_content' +require 'intercom/phone_call_redirect' require 'json' ## diff --git a/lib/intercom/client.rb b/lib/intercom/client.rb index 883a99ae..d2514596 100644 --- a/lib/intercom/client.rb +++ b/lib/intercom/client.rb @@ -124,6 +124,14 @@ def collections Intercom::Service::Collection.new(self) end + def export_content + Intercom::Service::ExportContent.new(self) + end + + def phone_call_redirect + Intercom::Service::PhoneCallRedirect.new(self) + end + def get(path, params) execute_request Intercom::Request.get(path, params) end diff --git a/lib/intercom/export_content.rb b/lib/intercom/export_content.rb new file mode 100644 index 00000000..9ea7c3d5 --- /dev/null +++ b/lib/intercom/export_content.rb @@ -0,0 +1,7 @@ +require 'intercom/traits/api_resource' + +module Intercom + class ExportContent + include Traits::ApiResource + end +end diff --git a/lib/intercom/phone_call_redirect.rb b/lib/intercom/phone_call_redirect.rb new file mode 100644 index 00000000..61db81bb --- /dev/null +++ b/lib/intercom/phone_call_redirect.rb @@ -0,0 +1,7 @@ +require 'intercom/traits/api_resource' + +module Intercom + class PhoneCallRedirect + include Traits::ApiResource + end +end diff --git a/lib/intercom/service/conversation.rb b/lib/intercom/service/conversation.rb index 2a8d8ee4..77685db0 100644 --- a/lib/intercom/service/conversation.rb +++ b/lib/intercom/service/conversation.rb @@ -21,6 +21,10 @@ def collection_class Intercom::Conversation end + def collection_proxy_class + Intercom::BaseCollectionProxy + end + def mark_read(id) @client.put("/conversations/#{id}", read: true) end diff --git a/lib/intercom/service/export_content.rb b/lib/intercom/service/export_content.rb new file mode 100644 index 00000000..7c1e3897 --- /dev/null +++ b/lib/intercom/service/export_content.rb @@ -0,0 +1,30 @@ +require 'intercom/service/base_service' +require 'intercom/api_operations/find' +require 'intercom/api_operations/list' +require 'intercom/api_operations/save' + +module Intercom + module Service + class ExportContent < BaseService + include ApiOperations::Load + include ApiOperations::List + include ApiOperations::Find + include ApiOperations::Save + + def collection_class + Intercom::ExportContent + end + + def collection_name + 'export/content/data' + end + + def cancel(id) + response = @client.post("/export/cancel/#{id}", {}) + collection_class.new.from_response(response) + end + + end + end +end + diff --git a/lib/intercom/service/phone_call_redirect.rb b/lib/intercom/service/phone_call_redirect.rb new file mode 100644 index 00000000..425b57d2 --- /dev/null +++ b/lib/intercom/service/phone_call_redirect.rb @@ -0,0 +1,15 @@ +require 'intercom/service/base_service' +require 'intercom/api_operations/save' + +module Intercom + module Service + class PhoneCallRedirect < BaseService + include ApiOperations::Save + + def collection_class + Intercom::PhoneCallRedirect + end + + end + end +end diff --git a/spec/unit/intercom/export_content_spec.rb b/spec/unit/intercom/export_content_spec.rb new file mode 100644 index 00000000..6c7787b7 --- /dev/null +++ b/spec/unit/intercom/export_content_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe "Intercom::ExportContent" do + let(:client) { Intercom::Client.new(token: 'token') } + let(:job) { + { + job_identifier: "k0e27ohsyvh8ef3m", + status: "no_data", + download_url: "", + download_expires_at: 0 + } + } + + it "creates an export job" do + client.expects(:post).with("/export/content/data", {"created_at_after" => 1667566801, "created_at_before" => 1668085202}).returns(job) + client.export_content.create({"created_at_after" => 1667566801, "created_at_before" => 1668085202}) + end + + it "can view an export job" do + client.expects(:get).with("/export/content/data/#{job[:job_identifier]}", {}).returns(job) + client.export_content.find(id: job[:job_identifier]) + end + + it "Cancels a export job redirect" do + client.expects(:post).with("/export/cancel/#{job[:job_identifier]}", {}).returns(job) + client.export_content.cancel(job[:job_identifier]) + end +end diff --git a/spec/unit/intercom/phone_call_redirect.rb b/spec/unit/intercom/phone_call_redirect.rb new file mode 100644 index 00000000..02a38863 --- /dev/null +++ b/spec/unit/intercom/phone_call_redirect.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe "Intercom::PhoneCallRedirect" do + let(:client) { Intercom::Client.new(token: 'token') } + + it "creates a phone redirect" do + + client.expects(:post).with("/phone_call_redirect", {phone_number: "+353871234567"}) + client.phone_call_redirect.create(phone_number: "+353871234567") + end + +end From 0ee5c538d4cccad2349d99520c40838b8d30e6d3 Mon Sep 17 00:00:00 2001 From: Suma Prakash Date: Mon, 14 Nov 2022 11:38:43 +0000 Subject: [PATCH 225/245] Adding additional tests --- spec/unit/intercom/request_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/spec/unit/intercom/request_spec.rb b/spec/unit/intercom/request_spec.rb index 2310bc79..6ec61cb8 100644 --- a/spec/unit/intercom/request_spec.rb +++ b/spec/unit/intercom/request_spec.rb @@ -124,6 +124,9 @@ def execute! let(:uri) {"https://api.intercom.io/conversations/reply"} let(:req) { Intercom::Request.put(uri, {}) } + let(:tag_uri) {"https://api.intercom.io/tags/"} + let(:del_req) { Intercom::Request.delete(tag_uri, {}) } + it 'should raise ResourceNotUniqueError error on resource_conflict code' do stub_request(:put, uri).to_return( status: [409, "Resource Already Exists"], @@ -153,5 +156,15 @@ def execute! expect { execute! }.must_raise(Intercom::ResourceNotFound) end + + it 'should raise TagHasDependentObjects error on tag_has_dependent_objects code' do + stub_request(:delete, tag_uri).to_return( + status: [400, "Bad Request"], + headers: { 'X-RateLimit-Reset' => (Time.now.utc + 10).to_i.to_s }, + body: { type: "error.list", errors: [ code: "tag_has_dependent_objects" ] }.to_json + ) + + expect { del_req.execute(tag_uri, token: 'test-token') }.must_raise(Intercom::TagHasDependentObjects) + end end end From a0970fd7862865cff2dcdd32a828b54440aa354e Mon Sep 17 00:00:00 2001 From: Suma Prakash Date: Mon, 14 Nov 2022 11:46:40 +0000 Subject: [PATCH 226/245] Updating readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a0d86a59..ef5b9fc1 100644 --- a/README.md +++ b/README.md @@ -306,7 +306,7 @@ tag = intercom.tags.untag(name: 'blue', companies: [{ company_id: "42ea2f1b93891 #delete tags -#Note: If there any depedent objects for the tag we are trying to delete, then an error TagHasDependentObjects will be thrown +# Note: If there any depedent objects for the tag we are trying to delete, then an error TagHasDependentObjects will be thrown. tag = intercom.tags.find(id:"123") intercom.tags.delete(tag) ``` From e5067f7ea80a58018a81d297948229c8170c9b1a Mon Sep 17 00:00:00 2001 From: Suma Prakash Date: Mon, 14 Nov 2022 11:49:25 +0000 Subject: [PATCH 227/245] Updating readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ef5b9fc1..f14c4364 100644 --- a/README.md +++ b/README.md @@ -304,9 +304,9 @@ tag = intercom.tags.tag(name: 'blue', companies: [{company_id: "42ea2f1b93891f6a tag = intercom.tags.untag(name: 'blue', companies: [{ company_id: "42ea2f1b93891f6a99000427" }]) -#delete tags +# Delete Tags -# Note: If there any depedent objects for the tag we are trying to delete, then an error TagHasDependentObjects will be thrown. +# Note : If there any depedent objects for the tag we are trying to delete, then an error TagHasDependentObjects will be thrown. tag = intercom.tags.find(id:"123") intercom.tags.delete(tag) ``` From 4fd8f0a38fb83d56d129f1b166723feb9d5e9374 Mon Sep 17 00:00:00 2001 From: Suma Prakash Date: Wed, 16 Nov 2022 17:00:59 +0000 Subject: [PATCH 228/245] Adding SDK endpoint to delete an archived contact --- README.md | 3 +++ lib/intercom/service/contact.rb | 4 ++++ spec/unit/intercom/contact_spec.rb | 6 ++++++ 3 files changed, 13 insertions(+) diff --git a/README.md b/README.md index 733b9051..6ffe3dd7 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,9 @@ intercom.contacts.unarchive(contact) # Delete a contact permanently intercom.contacts.delete(contact) +# Deletes an archived contact permanently +contact.delete_archived_contact("124") + # List all contacts contacts = intercom.contacts.all contacts.each { |contact| p contact.name } diff --git a/lib/intercom/service/contact.rb b/lib/intercom/service/contact.rb index 7b8b7504..3d01bf32 100644 --- a/lib/intercom/service/contact.rb +++ b/lib/intercom/service/contact.rb @@ -43,6 +43,10 @@ def unarchive(contact) contact end + def delete_archived_contact(id) + @client.delete("/#{collection_name}/#{id}", {}) + end + private def raise_invalid_merge_error raise Intercom::InvalidMergeError, 'Merging can only be performed on a lead into a user' end diff --git a/spec/unit/intercom/contact_spec.rb b/spec/unit/intercom/contact_spec.rb index 92d5525b..c8bd1a28 100644 --- a/spec/unit/intercom/contact_spec.rb +++ b/spec/unit/intercom/contact_spec.rb @@ -260,6 +260,12 @@ client.contacts.unarchive(contact) end + it 'deletes an archived contact' do + contact = Intercom::Contact.new('id' => '1','archived' =>true) + client.expects(:delete).with('/contacts/1', {}) + client.contacts.delete_archived_contact("1") + end + describe 'merging' do let(:lead) { Intercom::Contact.from_api(external_id: 'contact_id', role: 'lead') } let(:user) { Intercom::Contact.from_api(id: 'external_id', role: 'user') } From d5fe9b35df2f14e7edd979fe5a6bd4c8908c2cbf Mon Sep 17 00:00:00 2001 From: Sean Healy Date: Fri, 25 Nov 2022 11:45:23 +0000 Subject: [PATCH 229/245] Add opted out and in subscription types --- README.md | 23 +++++++++++++++++++ lib/intercom.rb | 2 ++ .../api_operations/nested_resource.rb | 2 -- lib/intercom/client.rb | 4 ++++ lib/intercom/contact.rb | 1 + lib/intercom/service/subscription_type.rb | 18 +++++++++++++++ lib/intercom/subscription_type.rb | 12 ++++++++++ lib/intercom/utils.rb | 6 ++++- spec/unit/intercom/contact_spec.rb | 11 +++++++++ 9 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 lib/intercom/service/subscription_type.rb create mode 100644 lib/intercom/subscription_type.rb diff --git a/README.md b/README.md index 733b9051..8226d686 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,16 @@ contact.remove_company(id: company.id) # List companies for a contact contact.companies.each {|c| p c.name} + +# attach a subscription_types on a contact +contact.create_subscription_types(id: '1') + +# List subscription_types for a contact +contact.subscription_types.each {|n| p n.body} + +# Remove subscription_types +contact.remove_subscription_type({ "id": subscription.id }) + ``` #### Visitors @@ -586,6 +596,19 @@ intercom.subscriptions.delete(subscription) intercom.subscriptions.all ``` + +#### Subscription Types + +Subscribe to events in Intercom to receive webhooks. + +```ruby + +# fetch a subscription +intercom.subscription_types.find(id: "1") + +intercom.subscription_types.all +``` + #### Articles ```ruby diff --git a/lib/intercom.rb b/lib/intercom.rb index c398e681..520b9036 100644 --- a/lib/intercom.rb +++ b/lib/intercom.rb @@ -12,6 +12,7 @@ require 'intercom/service/message' require 'intercom/service/note' require 'intercom/service/job' +require 'intercom/service/subscription_type' require 'intercom/service/subscription' require 'intercom/service/segment' require 'intercom/service/section' @@ -44,6 +45,7 @@ require 'intercom/article' require 'intercom/request' require 'intercom/subscription' +require 'intercom/subscription_type' require 'intercom/team' require 'intercom/errors' require 'intercom/visitor' diff --git a/lib/intercom/api_operations/nested_resource.rb b/lib/intercom/api_operations/nested_resource.rb index 3dedef7f..961d7362 100644 --- a/lib/intercom/api_operations/nested_resource.rb +++ b/lib/intercom/api_operations/nested_resource.rb @@ -13,9 +13,7 @@ def nested_resource_methods(resource, raise ArgumentError, 'operations array required' if operations.nil? resource_url_method = :"#{resource_plural}_url" - resource_name = Utils.resource_class_to_collection_name(self) - define_method(resource_url_method.to_sym) do |id, nested_id = nil| url = "/#{resource_name}/#{id}/#{path}" url += "/#{nested_id}" unless nested_id.nil? diff --git a/lib/intercom/client.rb b/lib/intercom/client.rb index d2514596..5b3f607f 100644 --- a/lib/intercom/client.rb +++ b/lib/intercom/client.rb @@ -84,6 +84,10 @@ def subscriptions Intercom::Service::Subscription.new(self) end + def subscription_types + Intercom::Service::SubscriptionType.new(self) + end + def segments Intercom::Service::Segment.new(self) end diff --git a/lib/intercom/contact.rb b/lib/intercom/contact.rb index f5332ab5..d9056082 100644 --- a/lib/intercom/contact.rb +++ b/lib/intercom/contact.rb @@ -12,6 +12,7 @@ class Contact nested_resource_methods :tag, operations: %i[add delete list] nested_resource_methods :note, operations: %i[create list] + nested_resource_methods :subscription_type, operations: %i[create delete list] nested_resource_methods :company, operations: %i[add delete list] nested_resource_methods :segment, operations: %i[list] diff --git a/lib/intercom/service/subscription_type.rb b/lib/intercom/service/subscription_type.rb new file mode 100644 index 00000000..c1de4879 --- /dev/null +++ b/lib/intercom/service/subscription_type.rb @@ -0,0 +1,18 @@ +require 'intercom/api_operations/list' +require 'intercom/api_operations/find_all' +require 'intercom/api_operations/find' + +module Intercom + module Service + class SubscriptionType < BaseService + include ApiOperations::List + include ApiOperations::Find + include ApiOperations::FindAll + include ApiOperations::Delete + + def collection_class + Intercom::SubscriptionType + end + end + end +end diff --git a/lib/intercom/subscription_type.rb b/lib/intercom/subscription_type.rb new file mode 100644 index 00000000..d53c2762 --- /dev/null +++ b/lib/intercom/subscription_type.rb @@ -0,0 +1,12 @@ + +require 'intercom/traits/api_resource' + +module Intercom + class SubscriptionType + include Traits::ApiResource + + def self.collection_proxy_class + Intercom::BaseCollectionProxy + end + end +end diff --git a/lib/intercom/utils.rb b/lib/intercom/utils.rb index ce0bcd74..2d39c0d1 100644 --- a/lib/intercom/utils.rb +++ b/lib/intercom/utils.rb @@ -25,6 +25,10 @@ def constantize(camel_cased_word) constant end + def camelize(snake_cased_word) + snake_cased_word.split(/_/).map(&:capitalize).join + end + def resource_class_to_singular_name(resource_class) resource_name = resource_class.to_s.split('::')[-1] resource_name = maybe_underscore_name(resource_name) @@ -40,7 +44,7 @@ def resource_class_to_collection_name(resource_class) end def constantize_resource_name(resource_name) - class_name = Utils.singularize(resource_name.capitalize) + class_name = camelize Utils.singularize(resource_name.capitalize) define_lightweight_class(class_name) unless Intercom.const_defined?(class_name, false) namespaced_class_name = "Intercom::#{class_name}" constantize namespaced_class_name diff --git a/spec/unit/intercom/contact_spec.rb b/spec/unit/intercom/contact_spec.rb index 92d5525b..f79f6b34 100644 --- a/spec/unit/intercom/contact_spec.rb +++ b/spec/unit/intercom/contact_spec.rb @@ -276,6 +276,7 @@ let(:contact) { Intercom::Contact.new(id: '1', client: client) } let(:contact_no_tags) { Intercom::Contact.new(id: '2', client: client, tags: []) } let(:company) { Intercom::Company.new(id: '1') } + let(:subscription) { Intercom::Subscription.new(id: '1', client: client) } let(:tag) { Intercom::Tag.new(id: '1') } let(:note) { Intercom::Note.new(body: "

Text for the note

") } @@ -338,6 +339,11 @@ contact.add_tag({ "id": tag.id }) end + it 'removes a subscription to a contact' do + client.expects(:delete).with("/contacts/1/subscription_types/#{subscription.id}", "id": subscription.id).returns(subscription.to_hash) + contact.remove_subscription_type({ "id": subscription.id }) + end + it 'removes a tag from a contact' do client.expects(:delete).with("/contacts/1/tags/#{tag.id}", "id": tag.id ).returns(tag.to_hash) contact.remove_tag({ "id": tag.id }) @@ -387,6 +393,11 @@ contact.create_note({body: note.body}) end + it 'adds a subscription to a contact' do + client.expects(:post).with('/contacts/1/subscription_types', "id": subscription.id).returns(subscription.to_hash) + contact.create_subscription_type({ "id": subscription.id }) + end + it 'adds a tag to a contact' do client.expects(:post).with('/contacts/1/tags', "id": tag.id).returns(tag.to_hash) contact.add_tag({ "id": tag.id }) From 428f6c4fbfef081328474066e72556846b37000d Mon Sep 17 00:00:00 2001 From: Suma Prakash Date: Fri, 2 Dec 2022 13:03:26 +0000 Subject: [PATCH 230/245] Updating the readme and url for subscriptions --- README.md | 19 +++++++++++-------- lib/intercom/contact.rb | 2 +- spec/unit/intercom/contact_spec.rb | 4 ++-- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 08e81fa3..45f8511c 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,9 @@ https://api.intercom.io/jobs https://api.intercom.io/articles https://api.intercom.io/help_center/collections https://api.intercom.io/help_center/sections +https://api.intercom.io/phone_call_redirects +https://api.intercom.io/subscription_types +https://api.intercom.io/export/content/data ``` ### Examples @@ -115,7 +118,7 @@ intercom.contacts.unarchive(contact) intercom.contacts.delete(contact) # Deletes an archived contact permanently -contact.delete_archived_contact("124") +intercom.contacts.delete_archived_contact(contact.id) # List all contacts contacts = intercom.contacts.all @@ -167,13 +170,13 @@ contact.remove_company(id: company.id) contact.companies.each {|c| p c.name} # attach a subscription_types on a contact -contact.create_subscription_types(id: '1') +contact.create_subscription_types(id: subscription_type.id) # List subscription_types for a contact -contact.subscription_types.each {|n| p n.body} +contact.subscription_types.each {|n| p n.id} # Remove subscription_types -contact.remove_subscription_type({ "id": subscription.id }) +contact.remove_subscription_type({ "id": subscription_type.id }) ``` @@ -609,7 +612,7 @@ intercom.subscriptions.all #### Subscription Types -Subscribe to events in Intercom to receive webhooks. +List all the subscription types that a contact can opt in to ```ruby @@ -731,14 +734,14 @@ redirect = intercom.phone_call_redirect.create(phone_number: "+353871234567") ```ruby # Create a data export -export = intercom.data_content_export.create(created_at_after: 1667566801, created_at_before: 1668085202) +export = intercom.export_content.create(created_at_after: 1667566801, created_at_before: 1668085202) #View a data export -export = intercom.data_content_export.find(id: 'k0e27ohsyvh8ef3m') +export = intercom.export_content.find(id: 'k0e27ohsyvh8ef3m') # Cancel a data export -export = intercom.data_content_export.cancel('k0e27ohsyvh8ef3m') +export = intercom.export_content.cancel('k0e27ohsyvh8ef3m') ``` diff --git a/lib/intercom/contact.rb b/lib/intercom/contact.rb index d9056082..6f656d9e 100644 --- a/lib/intercom/contact.rb +++ b/lib/intercom/contact.rb @@ -12,7 +12,7 @@ class Contact nested_resource_methods :tag, operations: %i[add delete list] nested_resource_methods :note, operations: %i[create list] - nested_resource_methods :subscription_type, operations: %i[create delete list] + nested_resource_methods :subscription_type, path: 'subscriptions', operations: %i[create delete list] nested_resource_methods :company, operations: %i[add delete list] nested_resource_methods :segment, operations: %i[list] diff --git a/spec/unit/intercom/contact_spec.rb b/spec/unit/intercom/contact_spec.rb index 0553dc37..a8ffd978 100644 --- a/spec/unit/intercom/contact_spec.rb +++ b/spec/unit/intercom/contact_spec.rb @@ -346,7 +346,7 @@ end it 'removes a subscription to a contact' do - client.expects(:delete).with("/contacts/1/subscription_types/#{subscription.id}", "id": subscription.id).returns(subscription.to_hash) + client.expects(:delete).with("/contacts/1/subscriptions/#{subscription.id}", "id": subscription.id).returns(subscription.to_hash) contact.remove_subscription_type({ "id": subscription.id }) end @@ -400,7 +400,7 @@ end it 'adds a subscription to a contact' do - client.expects(:post).with('/contacts/1/subscription_types', "id": subscription.id).returns(subscription.to_hash) + client.expects(:post).with('/contacts/1/subscriptions', "id": subscription.id).returns(subscription.to_hash) contact.create_subscription_type({ "id": subscription.id }) end From bf0ea223ec1874065ae752f5f9c3806be1949c26 Mon Sep 17 00:00:00 2001 From: Suma Prakash Date: Mon, 5 Dec 2022 12:23:39 +0000 Subject: [PATCH 231/245] Updating the documentation --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 45f8511c..dcb047e7 100644 --- a/README.md +++ b/README.md @@ -350,6 +350,8 @@ intercom.segments.all.each {|segment| puts "id: #{segment.id} name: #{segment.na # Iterate over all conversations for your app intercom.conversations.all.each { |convo| ... } +# The below method of finding conversations by using the find_all method work only for API versions 2.5 and below + # FINDING CONVERSATIONS FOR AN ADMIN # Iterate over all conversations (open and closed) assigned to an admin intercom.conversations.find_all(type: 'admin', id: '7').each {|convo| ... } From aec1e944566a786f92abb250874dc1616e71cb76 Mon Sep 17 00:00:00 2001 From: Suma Prakash Date: Tue, 6 Dec 2022 14:27:43 +0000 Subject: [PATCH 232/245] Adding a fix --- lib/intercom/base_collection_proxy.rb | 6 +++++- lib/intercom/lib/typed_json_deserializer.rb | 11 ++++++++--- lib/intercom/utils.rb | 1 + 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/intercom/base_collection_proxy.rb b/lib/intercom/base_collection_proxy.rb index bce14a9f..ee622d79 100644 --- a/lib/intercom/base_collection_proxy.rb +++ b/lib/intercom/base_collection_proxy.rb @@ -45,7 +45,11 @@ def deserialize_response_hash(response_hash, block) Utils.entity_key_from_type(top_level_type) end response_hash[top_level_entity_key].each do |object_json| - block.call Lib::TypedJsonDeserializer.new(object_json, @client).deserialize + if top_level_type == 'event.summary' + block.call Lib::TypedJsonDeserializer.new(object_json, @client, top_level_type).deserialize + else + block.call Lib::TypedJsonDeserializer.new(object_json, @client).deserialize + end end end diff --git a/lib/intercom/lib/typed_json_deserializer.rb b/lib/intercom/lib/typed_json_deserializer.rb index 17ed5ea3..155d2325 100644 --- a/lib/intercom/lib/typed_json_deserializer.rb +++ b/lib/intercom/lib/typed_json_deserializer.rb @@ -9,9 +9,10 @@ module Lib class TypedJsonDeserializer attr_reader :json - def initialize(json, client) + def initialize(json, client, type = nil) @json = json @client = client + @type = type end def deserialize @@ -27,7 +28,7 @@ def deserialize private def blank_object_type?(object_type) - object_type.nil? || object_type == '' + object_type.nil? || object_type == '' && @type.nil? end def list_object_type?(object_type) @@ -48,7 +49,11 @@ def deserialize_object(object_json) end def object_type - @object_type ||= json['type'] + if !@type.nil? + @object_type = @type + else + @object_type ||= json['type'] + end end def object_entity_key diff --git a/lib/intercom/utils.rb b/lib/intercom/utils.rb index 2d39c0d1..6ccba8cc 100644 --- a/lib/intercom/utils.rb +++ b/lib/intercom/utils.rb @@ -69,6 +69,7 @@ def entity_key_from_type(type) is_list = type.split('.')[1] == 'list' entity_name = type.split('.')[0] + return Utils.pluralize(entity_name) if entity_name == 'event' is_list ? Utils.pluralize(entity_name) : entity_name end end From c9d289fc70cddd389b3a007290d9e559b1970dcf Mon Sep 17 00:00:00 2001 From: Suma Prakash Date: Tue, 6 Dec 2022 15:46:53 +0000 Subject: [PATCH 233/245] Adding unit test cass --- README.md | 8 ++++++++ spec/unit/intercom/event_spec.rb | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/README.md b/README.md index dcb047e7..94a6e3c2 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,14 @@ intercom.events.create( # Retrieve event list for user with id:'123abc' intercom.events.find_all("type" => "user", "intercom_user_id" => "123abc") + +# Retrieve the event summary for user with id: 'abc' this will return an event object with the following characteristics: +# name - name of the event +# first - time when event first occured. +# last - time when event last occured +# count - nmber of times the event occured +# description - description of the event + events = intercom.events.find_all(type: 'user',intercom_user_id: 'abc',summary: true) ``` Metadata Objects support a few simple types that Intercom can present on your behalf diff --git a/spec/unit/intercom/event_spec.rb b/spec/unit/intercom/event_spec.rb index 125d9996..5d62551f 100644 --- a/spec/unit/intercom/event_spec.rb +++ b/spec/unit/intercom/event_spec.rb @@ -22,6 +22,13 @@ _(event_names).must_equal %W(invited-friend) end + it "able to fetch event summary" do + client.expects(:get).with("/events", type: 'user', email: 'joe@example.com', summary: true).returns(page_of_events(false)) + event_names = [] + client.events.find_all(type: 'user', email: 'joe@example.com',summary: true).each { |event| event_names << event.event_name } + _(event_names).must_equal %W(invited-friend) + end + it "keeps iterating if next link" do client.expects(:get).with("/events", type: 'user', email: 'joe@example.com').returns(page_of_events(true)) client.expects(:get).with("https://api.intercom.io/events?type=user&intercom_user_id=55a3b&before=144474756550", {}).returns(page_of_events(false)) From a6a2460827e88984fd1f7597ddb92856cc86aeb0 Mon Sep 17 00:00:00 2001 From: suma-prakash <107560946+suma-prakash@users.noreply.github.com> Date: Tue, 6 Dec 2022 16:00:01 +0000 Subject: [PATCH 234/245] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 94a6e3c2..1f29b37e 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,7 @@ intercom.events.create( # name - name of the event # first - time when event first occured. # last - time when event last occured -# count - nmber of times the event occured +# count - number of times the event occured # description - description of the event events = intercom.events.find_all(type: 'user',intercom_user_id: 'abc',summary: true) ``` From 60acc50c59a22c93cbb788077caa8572beeda759 Mon Sep 17 00:00:00 2001 From: Sean Healy Date: Wed, 7 Dec 2022 15:17:06 +0000 Subject: [PATCH 235/245] Bump intercom to 4.2.0 --- README.md | 4 ++-- lib/intercom/version.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1f29b37e..b60d9ac2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Circle CI](https://circleci.com/gh/intercom/intercom-ruby.png?style=shield)](https://circleci.com/gh/intercom/intercom-ruby) [![gem](https://img.shields.io/gem/v/intercom)](https://rubygems.org/gems/intercom) -![Intercom API Version](https://img.shields.io/badge/Intercom%20API%20Version-2.2-blue) +![Intercom API Version](https://img.shields.io/badge/Intercom%20API%20Version-2.6-blue) > Ruby bindings for the [Intercom API](https://developers.intercom.io/reference). @@ -274,7 +274,7 @@ intercom.events.create( # Retrieve event list for user with id:'123abc' intercom.events.find_all("type" => "user", "intercom_user_id" => "123abc") -# Retrieve the event summary for user with id: 'abc' this will return an event object with the following characteristics: +# Retrieve the event summary for user with id: 'abc' this will return an event object with the following characteristics: # name - name of the event # first - time when event first occured. # last - time when event last occured diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 211020ef..0529f64a 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "4.1.3" + VERSION = "4.2.0" end From 4a90839f76aa07d5bda6891e7fc3b1019e7cc63c Mon Sep 17 00:00:00 2001 From: Tom Dooner Date: Thu, 15 Dec 2022 11:01:03 -0800 Subject: [PATCH 236/245] Fix query params not being used in Intercom::BaseCollectionProxy Arbitrary @query params could be passed in to filter resources, e.g. via the `#find_all` method. These params were not being forwarded through to the underlying HTTP client by BaseCollectionProxy in the same way that they are in ClientCollectionProxy. --- lib/intercom/base_collection_proxy.rb | 5 +---- spec/unit/intercom/base_collection_proxy_spec.rb | 5 +++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/intercom/base_collection_proxy.rb b/lib/intercom/base_collection_proxy.rb index ee622d79..8fb59451 100644 --- a/lib/intercom/base_collection_proxy.rb +++ b/lib/intercom/base_collection_proxy.rb @@ -68,10 +68,7 @@ def has_next_link?(response_hash) end def payload - payload = {} - payload[:per_page] = @params[:per_page] if @params[:per_page] - payload[:starting_after] = @params[:starting_after] if @params[:starting_after] - payload + @params.keep_if { |k, v| !v.nil? }.to_h end end end diff --git a/spec/unit/intercom/base_collection_proxy_spec.rb b/spec/unit/intercom/base_collection_proxy_spec.rb index 473c4e39..02efed90 100644 --- a/spec/unit/intercom/base_collection_proxy_spec.rb +++ b/spec/unit/intercom/base_collection_proxy_spec.rb @@ -44,4 +44,9 @@ contacts.each { |contact| emails_iter2 << contact.email } _(emails_iter1).must_equal emails_iter2 end + + it "supports query params" do + client.expects(:get).with("/conversations", {:intercom_user_id => 'abcdef0000'}).returns(test_conversation_list) + _(client.conversations.find_all(:intercom_user_id => 'abcdef0000').map(&:id)).must_equal %w[147] + end end From e640a2dfde500335acf2b4c0962e4c241fa39308 Mon Sep 17 00:00:00 2001 From: Sean Healy Date: Fri, 16 Dec 2022 10:55:00 +0000 Subject: [PATCH 237/245] Add spec for find all on conversations --- spec/unit/intercom/company_spec.rb | 2 +- spec/unit/intercom/conversation_spec.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/unit/intercom/company_spec.rb b/spec/unit/intercom/company_spec.rb index 03fe762b..ac0f0e11 100644 --- a/spec/unit/intercom/company_spec.rb +++ b/spec/unit/intercom/company_spec.rb @@ -9,7 +9,7 @@ _(proc { client.companies.find(:company_id => "4")}).must_raise Intercom::HttpError end - it "on find_all" do + it "on all" do client.expects(:get).with("/companies", {}).returns(nil) _(proc { client.companies.all.each {|company| }}).must_raise Intercom::HttpError end diff --git a/spec/unit/intercom/conversation_spec.rb b/spec/unit/intercom/conversation_spec.rb index 79919d54..d585eea6 100644 --- a/spec/unit/intercom/conversation_spec.rb +++ b/spec/unit/intercom/conversation_spec.rb @@ -13,6 +13,11 @@ client.conversations.all.each { |c| } end + it "can filter conversations based on params" do + client.expects(:get).with("/conversations", {type: 'user', intercom_user_id: '123456789'}).returns(test_conversation_list) + client.conversations.find_all(type: 'user', intercom_user_id: '123456789').each { |c| } + end + it 'marks a conversation as read' do client.expects(:put).with('/conversations/147', { read: true }) client.conversations.mark_read('147') From d082c4040fbaf16e2edd29f35c63adcd545c8ab5 Mon Sep 17 00:00:00 2001 From: Jack Oddy Date: Fri, 17 Feb 2023 10:07:38 +0000 Subject: [PATCH 238/245] Fixes typo in README `create_subscription_types` does not exist on the contact class --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b60d9ac2..8ad928a4 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ contact.remove_company(id: company.id) contact.companies.each {|c| p c.name} # attach a subscription_types on a contact -contact.create_subscription_types(id: subscription_type.id) +contact.create_subscription_type(id: subscription_type.id) # List subscription_types for a contact contact.subscription_types.each {|n| p n.id} From 65d32ac6b5aeb9c80849572d695cd5d5c1c89979 Mon Sep 17 00:00:00 2001 From: Vedran Date: Mon, 1 Sep 2025 14:11:54 +0100 Subject: [PATCH 239/245] Fix depredacted mocha version (#620) --- intercom.gemspec | 2 +- spec/spec_helper.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/intercom.gemspec b/intercom.gemspec index a6878d55..e5ed7737 100644 --- a/intercom.gemspec +++ b/intercom.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'minitest', '~> 5.4' spec.add_development_dependency "m", "~> 1.5.0" spec.add_development_dependency 'rake', '~> 10.3' - spec.add_development_dependency 'mocha', '~> 1.0' + spec.add_development_dependency 'mocha', '~> 2.0' spec.add_development_dependency "fakeweb", ["~> 1.3"] spec.add_development_dependency "pry" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d94db57d..1dc07c62 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,7 +2,7 @@ require 'intercom' require 'minitest/autorun' -require 'mocha/setup' +require 'mocha/minitest' require 'webmock' require 'time' require 'pry' From 0d3e6a969ec3c3e03caaaea879855c9c4a26fbde Mon Sep 17 00:00:00 2001 From: Vedran Date: Mon, 1 Sep 2025 14:57:22 +0100 Subject: [PATCH 240/245] Remove the hash validation and remove them from the payload on save (#619) * Remove the hash validation and remove them from the payload on save * Update tests for contact and user * Fix typo --- lib/intercom/lib/flat_store.rb | 11 +++++-- spec/unit/intercom/contact_spec.rb | 4 +-- spec/unit/intercom/lib/flat_store_spec.rb | 36 +++++++++++++++++++++-- spec/unit/intercom/user_spec.rb | 4 +-- 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/lib/intercom/lib/flat_store.rb b/lib/intercom/lib/flat_store.rb index 6971ca75..2077a2bd 100644 --- a/lib/intercom/lib/flat_store.rb +++ b/lib/intercom/lib/flat_store.rb @@ -2,7 +2,7 @@ module Intercom module Lib # Sub-class of {Hash} for storing custom data attributes. - # Doesn't allow nested Hashes or Arrays. And requires {String} or {Symbol} keys. + # Doesn't allow Arrays. And requires {String} or {Symbol} keys. class FlatStore < Hash def initialize(attributes={}) @@ -21,9 +21,16 @@ def [](key) super(key.to_s) end + def to_submittable_hash + # Filter out Custom Object references when submitting to API + self.reject do |key, value| + value.is_a?(Hash) + end + end + private def validate_key_and_value(key, value) - raise ArgumentError.new("This does not support nested data structures (key: #{key}, value: #{value}") if value.is_a?(Array) || value.is_a?(Hash) + raise ArgumentError.new("This does not support nested data structures (key: #{key}, value: #{value}") if value.is_a?(Array) raise ArgumentError.new("Key must be String or Symbol: #{key}") unless key.is_a?(String) || key.is_a?(Symbol) end end diff --git a/spec/unit/intercom/contact_spec.rb b/spec/unit/intercom/contact_spec.rb index a8ffd978..6361905d 100644 --- a/spec/unit/intercom/contact_spec.rb +++ b/spec/unit/intercom/contact_spec.rb @@ -90,12 +90,10 @@ _(contact.to_hash['custom_attributes']).must_equal 'mad' => 123, 'other' => now.to_i, 'thing' => 'yay' end - it 'rejects nested data structures in custom_attributes' do + it 'rejects lists in custom_attributes' do contact = Intercom::Contact.new _(proc { contact.custom_attributes['thing'] = [1] }).must_raise(ArgumentError) - _(proc { contact.custom_attributes['thing'] = { 1 => 2 } }).must_raise(ArgumentError) - _(proc { contact.custom_attributes['thing'] = { 1 => { 2 => 3 } } }).must_raise(ArgumentError) contact = Intercom::Contact.new(test_contact) _(proc { contact.custom_attributes['thing'] = [1] }).must_raise(ArgumentError) diff --git a/spec/unit/intercom/lib/flat_store_spec.rb b/spec/unit/intercom/lib/flat_store_spec.rb index 3188b579..412ed717 100644 --- a/spec/unit/intercom/lib/flat_store_spec.rb +++ b/spec/unit/intercom/lib/flat_store_spec.rb @@ -3,11 +3,15 @@ require 'spec_helper' describe Intercom::Lib::FlatStore do - it 'raises if you try to set or merge in nested hash structures' do + it 'raises if you try to set arrays but allows hashes' do data = Intercom::Lib::FlatStore.new _(proc { data['thing'] = [1] }).must_raise ArgumentError - _(proc { data['thing'] = { 1 => 2 } }).must_raise ArgumentError - _(proc { Intercom::Lib::FlatStore.new(1 => { 2 => 3 }) }).must_raise ArgumentError + + data['thing'] = { 'key' => 'value' } + _(data['thing']).must_equal({ 'key' => 'value' }) + + flat_store = Intercom::Lib::FlatStore.new('custom_object' => { 'type' => 'Order.list', 'instances' => [{'id' => '123'}] }) + _(flat_store['custom_object']).must_equal({ 'type' => 'Order.list', 'instances' => [{'id' => '123'}] }) end it 'raises if you try to use a non string key' do @@ -28,4 +32,30 @@ _(data['b']).must_equal 2 _(data[:b]).must_equal 2 end + + describe '#to_submittable_hash' do + it 'filters out all hash values' do + data = Intercom::Lib::FlatStore.new( + 'regular_attr' => 'value', + 'number_attr' => 42, + 'custom_object' => { + 'type' => 'Order.list', + 'instances' => [ + { 'id' => '31', 'external_id' => 'ext_123' } + ] + }, + 'regular_hash' => { 'key' => 'value' }, + 'metadata' => { 'source' => 'api', 'version' => 2 } + ) + + submittable = data.to_submittable_hash + + _(submittable['regular_attr']).must_equal 'value' + _(submittable['number_attr']).must_equal 42 + + _(submittable.key?('custom_object')).must_equal false + _(submittable.key?('regular_hash')).must_equal false + _(submittable.key?('metadata')).must_equal false + end + end end diff --git a/spec/unit/intercom/user_spec.rb b/spec/unit/intercom/user_spec.rb index fa6deddb..a570abfe 100644 --- a/spec/unit/intercom/user_spec.rb +++ b/spec/unit/intercom/user_spec.rb @@ -106,12 +106,10 @@ _(user.to_hash['companies']).must_equal companies end - it 'rejects nested data structures in custom_attributes' do + it 'rejects lists in custom_attributes' do user = Intercom::User.new _(proc { user.custom_attributes['thing'] = [1] }).must_raise(ArgumentError) - _(proc { user.custom_attributes['thing'] = { 1 => 2 } }).must_raise(ArgumentError) - _(proc { user.custom_attributes['thing'] = { 1 => { 2 => 3 } } }).must_raise(ArgumentError) user = Intercom::User.from_api(test_user) _(proc { user.custom_attributes['thing'] = [1] }).must_raise(ArgumentError) From fd1d603aac38bba97f252c9877b210c43fe8971b Mon Sep 17 00:00:00 2001 From: Vedran Date: Mon, 1 Sep 2025 15:15:36 +0100 Subject: [PATCH 241/245] Bump version to 4.2.1 (#621) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Version bump to 4.2.1 - Updated changelog with FlatStore improvements 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude --- changes.txt | 4 ++++ lib/intercom/version.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/changes.txt b/changes.txt index 0783aa8d..cac520a9 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,7 @@ +4.2.1 +- Fixed FlatStore to skip hash values when building API request payloads +- Removed hash validation on FlatStore reads to allow for custom objects + 4.1.3 - Updated ReadMe with more errors. - Fixed issue where paginated requests could only be iterated through once. diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 0529f64a..8e27d974 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "4.2.0" + VERSION = "4.2.1" end From fbdf7120f419bde8b2d905920f559ebf55419e74 Mon Sep 17 00:00:00 2001 From: Vedran Date: Mon, 1 Sep 2025 15:37:24 +0100 Subject: [PATCH 242/245] chore: bump version to 4.2.2 (#623) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Version bump from 4.2.1 to 4.2.2 - Updated changelog with FlatStore improvements 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude --- changes.txt | 2 +- lib/intercom/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changes.txt b/changes.txt index cac520a9..b9f617f3 100644 --- a/changes.txt +++ b/changes.txt @@ -1,4 +1,4 @@ -4.2.1 +4.2.2 - Fixed FlatStore to skip hash values when building API request payloads - Removed hash validation on FlatStore reads to allow for custom objects diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 8e27d974..7406b336 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "4.2.1" + VERSION = "4.2.2" end From acb7deb7627fdd8e536a4175cc08e412788e9c20 Mon Sep 17 00:00:00 2001 From: DecIntercom <49399969+DecIntercom@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:47:30 +0100 Subject: [PATCH 243/245] Route CI gem installs through a scanning registry mirror (#625) --- .circleci/config.yml | 62 +++++++++++++++++++++++++++++++++++++++++--- Gemfile | 2 +- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 23fcf9e1..ce245629 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,6 +6,25 @@ jobs: working_directory: ~/intercom-ruby steps: - checkout + - run: + name: Route Bundler through the Socket Firewall registry + command: | + set -euo pipefail + if [ -z "${SOCKET_GEM_URL:-}" ]; then + # CircleCI does not share context credentials with forked PRs, so a + # fork cannot be routed. Say so rather than failing the contributor. + if [ -n "${CIRCLE_PR_REPONAME:-}" ]; then + echo "Forked PR: no Socket Firewall credentials available, gems will come from rubygems.org." + exit 0 + fi + echo "FATAL: SOCKET_GEM_URL is required (provided by the socket-firewall context)" + exit 1 + fi + # Legacy two-argument form: these images ship Bundler 1.x, where + # `bundle config set --global` does not exist and silently no-ops. + bundle config mirror.https://rubygems.org "$SOCKET_GEM_URL" + bundle config mirror.https://rubygems.org | grep -q socket-firewall-registry \ + || { echo "FAIL: bundler mirror not pointing at Socket Firewall"; exit 1; } - run: bundle install - run: bundle exec rake "Test against Ruby 2.5": @@ -14,6 +33,21 @@ jobs: working_directory: ~/intercom-ruby steps: - checkout + - run: + name: Route Bundler through the Socket Firewall registry + command: | + set -euo pipefail + if [ -z "${SOCKET_GEM_URL:-}" ]; then + if [ -n "${CIRCLE_PR_REPONAME:-}" ]; then + echo "Forked PR: no Socket Firewall credentials available, gems will come from rubygems.org." + exit 0 + fi + echo "FATAL: SOCKET_GEM_URL is required (provided by the socket-firewall context)" + exit 1 + fi + bundle config mirror.https://rubygems.org "$SOCKET_GEM_URL" + bundle config mirror.https://rubygems.org | grep -q socket-firewall-registry \ + || { echo "FAIL: bundler mirror not pointing at Socket Firewall"; exit 1; } - run: bundle install - run: bundle exec rake "Test against Ruby 2.6": @@ -22,6 +56,21 @@ jobs: working_directory: ~/intercom-ruby steps: - checkout + - run: + name: Route Bundler through the Socket Firewall registry + command: | + set -euo pipefail + if [ -z "${SOCKET_GEM_URL:-}" ]; then + if [ -n "${CIRCLE_PR_REPONAME:-}" ]; then + echo "Forked PR: no Socket Firewall credentials available, gems will come from rubygems.org." + exit 0 + fi + echo "FATAL: SOCKET_GEM_URL is required (provided by the socket-firewall context)" + exit 1 + fi + bundle config mirror.https://rubygems.org "$SOCKET_GEM_URL" + bundle config mirror.https://rubygems.org | grep -q socket-firewall-registry \ + || { echo "FAIL: bundler mirror not pointing at Socket Firewall"; exit 1; } - run: bundle install - run: bundle exec rake @@ -29,7 +78,12 @@ workflows: version: 2 build_and_test: jobs: - - "Test against Ruby 2.4" - - "Test against Ruby 2.5" - - "Test against Ruby 2.6" - + - "Test against Ruby 2.4": + context: + - socket-firewall + - "Test against Ruby 2.5": + context: + - socket-firewall + - "Test against Ruby 2.6": + context: + - socket-firewall diff --git a/Gemfile b/Gemfile index 4516ffc0..0cfe52d3 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ -source "http://rubygems.org" +source "https://rubygems.org" gem 'webmock' gemspec From d0a805931a99eea54736c8a5696d942730fd0244 Mon Sep 17 00:00:00 2001 From: Anubhav Bohidar Date: Wed, 7 Oct 2026 13:33:45 +0100 Subject: [PATCH 244/245] Fix SyntaxError on articles with hyphenated locales (#626) * Define dynamic accessors without evaluating source strings Parsing an article, collection or section whose translated_content had a locale key such as "pt-BR" or "zh-CN" raised a SyntaxError, because the key was interpolated into a string passed to instance_eval and is not a valid method name. The error is not a StandardError so callers could not rescue it. Define the accessors with define_method and instance_variable_get/set instead, normalising the instance variable name. Behaviour for ordinary keys is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) * Keep original attribute keys in serialised output Hyphenated keys such as pt-BR were emitted as pt_BR by to_hash, to_json and to_submittable_hash. Co-Authored-By: Claude Opus 5.5 (1M context) * Upgrade CircleCI config to version 2.1 CircleCI no longer accepts version 2 configs, so pipelines on this repo error before any job runs and the required test checks never report. Co-Authored-By: Claude Opus 5.5 (1M context) * Keep CI check names while using 2.1-compatible job keys Version 2.1 rejects dots in job keys. Setting the original names on the workflow jobs keeps the reported check names unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) --------- Co-authored-by: Claude Opus 5.5 (1M context) --- .circleci/config.yml | 18 +++++---- lib/intercom/lib/dynamic_accessors.rb | 55 +++++++++++++++------------ lib/intercom/traits/api_resource.rb | 12 +++++- lib/intercom/traits/dirty_tracking.rb | 4 +- spec/unit/intercom/article_spec.rb | 20 +++++++++- 5 files changed, 72 insertions(+), 37 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ce245629..9ad5d45b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ -version: 2 +version: 2.1 jobs: - "Test against Ruby 2.4": + test-ruby-2-4: docker: - image: circleci/ruby:2.4.9 working_directory: ~/intercom-ruby @@ -27,7 +27,7 @@ jobs: || { echo "FAIL: bundler mirror not pointing at Socket Firewall"; exit 1; } - run: bundle install - run: bundle exec rake - "Test against Ruby 2.5": + test-ruby-2-5: docker: - image: circleci/ruby:2.5.7 working_directory: ~/intercom-ruby @@ -50,7 +50,7 @@ jobs: || { echo "FAIL: bundler mirror not pointing at Socket Firewall"; exit 1; } - run: bundle install - run: bundle exec rake - "Test against Ruby 2.6": + test-ruby-2-6: docker: - image: circleci/ruby:2.6.5 working_directory: ~/intercom-ruby @@ -75,15 +75,17 @@ jobs: - run: bundle exec rake workflows: - version: 2 build_and_test: jobs: - - "Test against Ruby 2.4": + - test-ruby-2-4: + name: "Test against Ruby 2.4" context: - socket-firewall - - "Test against Ruby 2.5": + - test-ruby-2-5: + name: "Test against Ruby 2.5" context: - socket-firewall - - "Test against Ruby 2.6": + - test-ruby-2-6: + name: "Test against Ruby 2.6" context: - socket-firewall diff --git a/lib/intercom/lib/dynamic_accessors.rb b/lib/intercom/lib/dynamic_accessors.rb index 9d26b4fc..70543397 100644 --- a/lib/intercom/lib/dynamic_accessors.rb +++ b/lib/intercom/lib/dynamic_accessors.rb @@ -17,39 +17,46 @@ def define_accessors(attribute, value, object) private def define_flat_store_based_accessors(attribute, value, object) - object.instance_eval %Q" - def #{attribute}=(value) - mark_field_as_changed!(:#{attribute}) - @#{attribute} = Intercom::Lib::FlatStore.new(value) + ivar = ivar_name(attribute, object) + object.singleton_class.class_eval do + define_method("#{attribute}=") do |val| + mark_field_as_changed!(attribute.to_sym) + instance_variable_set(ivar, Intercom::Lib::FlatStore.new(val)) end - def #{attribute} - @#{attribute} - end - " + define_method(attribute) { instance_variable_get(ivar) } + end end def define_date_based_accessors(attribute, value, object) - object.instance_eval %Q" - def #{attribute}=(value) - mark_field_as_changed!(:#{attribute}) - @#{attribute} = value.nil? ? nil : value.to_i + ivar = ivar_name(attribute, object) + object.singleton_class.class_eval do + define_method("#{attribute}=") do |val| + mark_field_as_changed!(attribute.to_sym) + instance_variable_set(ivar, val.nil? ? nil : val.to_i) end - def #{attribute} - @#{attribute}.nil? ? nil : Time.at(@#{attribute}) + define_method(attribute) do + time = instance_variable_get(ivar) + time.nil? ? nil : Time.at(time) end - " + end end def define_standard_accessors(attribute, value, object) - object.instance_eval %Q" - def #{attribute}=(value) - mark_field_as_changed!(:#{attribute}) - @#{attribute} = value - end - def #{attribute} - @#{attribute} - end - " + ivar = ivar_name(attribute, object) + object.singleton_class.class_eval do + define_method("#{attribute}=") do |val| + mark_field_as_changed!(attribute.to_sym) + instance_variable_set(ivar, val) + end + define_method(attribute) { instance_variable_get(ivar) } + end + end + + # Keys such as "pt-BR" are not valid identifiers, so normalise them for the ivar name. + def ivar_name(attribute, object) + ivar = "@#{attribute.to_s.gsub(/\W/, '_')}" + object.register_attribute_key(ivar, attribute) if ivar != "@#{attribute}" + ivar end end diff --git a/lib/intercom/traits/api_resource.rb b/lib/intercom/traits/api_resource.rb index 8749c196..7a27b399 100644 --- a/lib/intercom/traits/api_resource.rb +++ b/lib/intercom/traits/api_resource.rb @@ -37,7 +37,7 @@ def from_hash(hash) def to_hash instance_variables_excluding_dirty_tracking_field.each_with_object({}) do |variable, hash| - hash[variable.to_s.delete('@')] = instance_variable_get(variable) + hash[attribute_key(variable)] = instance_variable_get(variable) end end @@ -46,10 +46,18 @@ def to_json(*args) next if variable == :@client value = instance_variable_get(variable) - hash[variable.to_s.delete('@')] = value.respond_to?(:to_json) ? value.to_json(*args) : value + hash[attribute_key(variable)] = value.respond_to?(:to_json) ? value.to_json(*args) : value end end + def register_attribute_key(ivar, attribute) + (@attribute_keys ||= {})[ivar.to_sym] = attribute.to_s + end + + def attribute_key(variable) + (@attribute_keys || {}).fetch(variable, variable.to_s.delete('@')) + end + def to_submittable_hash submittable_hash = {} to_hash.each do |attribute, value| diff --git a/lib/intercom/traits/dirty_tracking.rb b/lib/intercom/traits/dirty_tracking.rb index d155c2d9..baad8508 100644 --- a/lib/intercom/traits/dirty_tracking.rb +++ b/lib/intercom/traits/dirty_tracking.rb @@ -22,7 +22,7 @@ def mark_field_as_changed!(field_name) def field_changed?(field_name) @changed_fields ||= Set.new - field = instance_variable_get("@#{field_name}") + field = instance_variable_get("@#{field_name.to_s.gsub(/\W/, '_')}") if field.respond_to?(:field_changed?) field.to_hash.any? do |attribute, _| field.field_changed?(attribute) @@ -33,7 +33,7 @@ def field_changed?(field_name) end def instance_variables_excluding_dirty_tracking_field - instance_variables.reject{|var| var == :@changed_fields} + instance_variables.reject{|var| var == :@changed_fields || var == :@attribute_keys} end end end diff --git a/spec/unit/intercom/article_spec.rb b/spec/unit/intercom/article_spec.rb index 9fa3a695..67007ee7 100644 --- a/spec/unit/intercom/article_spec.rb +++ b/spec/unit/intercom/article_spec.rb @@ -37,4 +37,22 @@ client.articles.delete(article) end end -end \ No newline at end of file + + describe "Parsing an Article" do + it "handles hyphenated locale keys in translated content" do + article = Intercom::Article.from_api( + "id" => "1", + "type" => "article", + "translated_content" => { + "type" => "article_translated_content", + "fr" => { "type" => "article_content", "title" => "Bonjour" }, + "pt-BR" => { "type" => "article_content", "title" => "Ola" } + } + ) + _(article.translated_content.fr.title).must_equal "Bonjour" + _(article.translated_content.public_send("pt-BR").title).must_equal "Ola" + article.translated_content.public_send("pt-BR").title = "Oi" + _(article.to_submittable_hash).must_equal("translated_content" => { "pt-BR" => { "title" => "Oi" } }) + end + end +end From 7d8a2a98a9af2d1f5f35621f29ee092e968f4e77 Mon Sep 17 00:00:00 2001 From: Anubhav Bohidar Date: Thu, 8 Oct 2026 13:11:45 +0100 Subject: [PATCH 245/245] Bump version to 4.2.3 (#627) Co-authored-by: Claude Opus 5.5 (1M context) --- changes.txt | 3 +++ lib/intercom/version.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/changes.txt b/changes.txt index b9f617f3..a881bab0 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,6 @@ +4.2.3 +- Fixed SyntaxError when reading articles with hyphenated locales + 4.2.2 - Fixed FlatStore to skip hash values when building API request payloads - Removed hash validation on FlatStore reads to allow for custom objects diff --git a/lib/intercom/version.rb b/lib/intercom/version.rb index 7406b336..a5795aa0 100644 --- a/lib/intercom/version.rb +++ b/lib/intercom/version.rb @@ -1,3 +1,3 @@ module Intercom #:nodoc: - VERSION = "4.2.2" + VERSION = "4.2.3" end