From 9d01d8b446c2f1ac444ead6e25226982f8026adc Mon Sep 17 00:00:00 2001 From: Edoardo Serra Date: Sun, 18 Feb 2024 18:01:39 -0500 Subject: [PATCH 1/4] add track delivery metric --- lib/customerio/client.rb | 46 ++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/lib/customerio/client.rb b/lib/customerio/client.rb index 41fb315..b3e8967 100644 --- a/lib/customerio/client.rb +++ b/lib/customerio/client.rb @@ -8,11 +8,21 @@ class IdentifierType end class Client - PUSH_OPENED = 'opened' - PUSH_CONVERTED = 'converted' - PUSH_DELIVERED = 'delivered' - - VALID_PUSH_EVENTS = [PUSH_OPENED, PUSH_CONVERTED, PUSH_DELIVERED] + DELIVERY_OPENED = 'opened' + DELIVERY_CONVERTED = 'converted' + DELIVERY_DELIVERED = 'delivered' + DELIVERY_BOUNCED = 'bounced' + DELIVERY_CLICKED = 'clicked' + DELIVERY_DEFERRED = 'deferred' + DELIVERY_DROPPED = 'dropped' + DELIVERY_SPAMMED = 'spammed' + + VALID_PUSH_EVENTS = [DELIVERY_OPENED, DELIVERY_CONVERTED, DELIVERY_DELIVERED] + + # The valid delivery events depend on the channel + # However, there is no way to validate the channel prior the API request + # https://customer.io/docs/api/track/#operation/metrics + VALID_DELIVERY_METRICS = VALID_PUSH_EVENTS + [DELIVERY_BOUNCED, DELIVERY_CLICKED, DELIVERY_DEFERRED, DELIVERY_DROPPED, DELIVERY_SPAMMED] class MissingIdAttributeError < RuntimeError; end class ParamError < RuntimeError; end @@ -90,8 +100,9 @@ def delete_device(customer_id, device_id) @client.request_and_verify_response(:delete, device_id_path(customer_id, device_id)) end + # Customer.io deprecated per https://customer.io/docs/api/track/#operation/pushMetrics def track_push_notification_event(event_name, attributes = {}) - keys = [:delivery_id, :device_id, :timestamp] + keys = [:delivery_id, :device_id, :timestamp] attributes = Hash[attributes.map { |(k,v)| [ k.to_sym, v ] }]. select { |k, v| keys.include?(k) } @@ -103,6 +114,19 @@ def track_push_notification_event(event_name, attributes = {}) @client.request_and_verify_response(:post, track_push_notification_event_path, attributes.merge(event: event_name)) end + def track_delivery_metric(metric_name, attributes = {}) + keys = [:delivery_id, :timestamp, :recipient, :reason, :href] + attributes = Hash[attributes.map { |(k,v)| [ k.to_sym, v ] }]. + select { |k, v| keys.include?(k) } + + raise ParamError.new('metric_name must be one of bounced, clicked, converted, deferred, delivered, dropped, opened, and spammed') unless VALID_DELIVERY_METRICS.include?(metric_name) + raise ParamError.new('delivery_id must be a non-empty string') unless attributes[:delivery_id] != "" and !attributes[:delivery_id].nil? + raise ParamError.new('timestamp must be a valid timestamp') unless valid_timestamp?(attributes[:timestamp]) + raise ParamError.new('href must be a valid url') if attributes[:href].present? && ! valid_url?(attributes[:href].present?) + + @client.request_and_verify_response(:post, track_delivery_metric_path, attributes.merge(metric: metric_name)) + end + def merge_customers(primary_id_type, primary_id, secondary_id_type, secondary_id) raise ParamError.new("invalid primary_id_type") if !is_valid_id_type?(primary_id_type) raise ParamError.new("primary_id must be a non-empty string") if is_empty?(primary_id) @@ -145,6 +169,10 @@ def track_push_notification_event_path "/push/events" end + def track_delivery_metric_path + "/metrics" + end + def merge_customers_path "/api/v1/merge_customers" end @@ -221,6 +249,12 @@ def valid_timestamp?(timestamp) timestamp && timestamp.is_a?(Integer) && timestamp > 999999999 && timestamp < 100000000000 end + def valid_url?(url) + %w[http https].include?(Addressable::URI.parse(url)&.scheme) + rescue Addressable::URI::InvalidURIError + false + end + def is_empty?(val) val.nil? || (val.is_a?(String) && val.strip == "") end From f6631afa2f1f23c814026bdb15a8fef6b82f4288 Mon Sep 17 00:00:00 2001 From: Edoardo Serra Date: Sun, 18 Feb 2024 18:06:25 -0500 Subject: [PATCH 2/4] fix indentation --- lib/customerio/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/customerio/client.rb b/lib/customerio/client.rb index b3e8967..2f86de9 100644 --- a/lib/customerio/client.rb +++ b/lib/customerio/client.rb @@ -102,7 +102,7 @@ def delete_device(customer_id, device_id) # Customer.io deprecated per https://customer.io/docs/api/track/#operation/pushMetrics def track_push_notification_event(event_name, attributes = {}) - keys = [:delivery_id, :device_id, :timestamp] + keys = [:delivery_id, :device_id, :timestamp] attributes = Hash[attributes.map { |(k,v)| [ k.to_sym, v ] }]. select { |k, v| keys.include?(k) } From 314d8dc9329463cc9c1a7a39f59a8eca14249896 Mon Sep 17 00:00:00 2001 From: Edoardo Serra Date: Sun, 18 Feb 2024 18:47:09 -0500 Subject: [PATCH 3/4] add specs --- lib/customerio/client.rb | 4 +-- spec/client_spec.rb | 66 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/lib/customerio/client.rb b/lib/customerio/client.rb index 2f86de9..634c207 100644 --- a/lib/customerio/client.rb +++ b/lib/customerio/client.rb @@ -121,8 +121,8 @@ def track_delivery_metric(metric_name, attributes = {}) raise ParamError.new('metric_name must be one of bounced, clicked, converted, deferred, delivered, dropped, opened, and spammed') unless VALID_DELIVERY_METRICS.include?(metric_name) raise ParamError.new('delivery_id must be a non-empty string') unless attributes[:delivery_id] != "" and !attributes[:delivery_id].nil? - raise ParamError.new('timestamp must be a valid timestamp') unless valid_timestamp?(attributes[:timestamp]) - raise ParamError.new('href must be a valid url') if attributes[:href].present? && ! valid_url?(attributes[:href].present?) + raise ParamError.new('timestamp must be a valid timestamp') if attributes[:timestamp] && !valid_timestamp?(attributes[:timestamp]) + raise ParamError.new('href must be a valid url') if attributes[:href] && !valid_url?(attributes[:href].present?) @client.request_and_verify_response(:post, track_delivery_metric_path, attributes.merge(metric: metric_name)) end diff --git a/spec/client_spec.rb b/spec/client_spec.rb index 27ba3db..8d95676 100644 --- a/spec/client_spec.rb +++ b/spec/client_spec.rb @@ -685,6 +685,72 @@ def json(data) end end + describe "#track_delivery_metric" do + attr_accessor :client, :attributes + + before(:each) do + @client = Customerio::Client.new("SITE_ID", "API_KEY", :json => true) + @attributes = { + :delivery_id => 'foo', + :timestamp => Time.now.to_i, + :href => nil, + :recipient => nil, + :reason => nil, + } + end + + it "sends a POST request to customer.io's /metrics endpoint" do + stub_request(:post, api_uri('/metrics')). + with( + :body => json(attributes.merge({ + :metric => 'opened' + })), + :headers => { + 'Content-Type' => 'application/json' + }). + to_return(:status => 200, :body => "", :headers => {}) + + client.track_delivery_metric('opened', attributes) + end + + it "should raise if event is invalid" do + stub_request(:post, api_uri('/metrics')). + to_return(:status => 200, :body => "", :headers => {}) + + expect { + client.track_delivery_metric('closed', attributes.merge({ :delivery_id => nil })) + }.to raise_error(Customerio::Client::ParamError, 'metric_name must be one of bounced, clicked, converted, deferred, delivered, dropped, opened, and spammed') + end + + it "should raise if delivery_id is invalid" do + stub_request(:post, api_uri('/metrics')). + to_return(:status => 200, :body => "", :headers => {}) + + expect { + client.track_delivery_metric('opened', attributes.merge({ :delivery_id => nil })) + }.to raise_error(Customerio::Client::ParamError, 'delivery_id must be a non-empty string') + + expect { + client.track_delivery_metric('opened', attributes.merge({ :delivery_id => '' })) + }.to raise_error(Customerio::Client::ParamError, 'delivery_id must be a non-empty string') + end + + it "should raise if timestamp is invalid" do + stub_request(:post, api_uri('/metrics')). + to_return(:status => 200, :body => "", :headers => {}) + + client.track_delivery_metric('opened', attributes.merge({ :timestamp => nil })) + + expect { + client.track_delivery_metric('opened', attributes.merge({ :timestamp => 999999999 })) + }.to raise_error(Customerio::Client::ParamError, 'timestamp must be a valid timestamp') + + expect { + client.track_delivery_metric('opened', attributes.merge({ :timestamp => 100000000000 })) + }.to raise_error(Customerio::Client::ParamError, 'timestamp must be a valid timestamp') + end + end + describe "#merge_customers" do before(:each) do @client = Customerio::Client.new("SITE_ID", "API_KEY", :json => true) From 7e7e3b53890eeeb12019c13fb5f9dc0cb533bd87 Mon Sep 17 00:00:00 2001 From: Edoardo Serra Date: Sun, 18 Feb 2024 19:24:01 -0500 Subject: [PATCH 4/4] update endpoint path --- lib/customerio/client.rb | 4 ++-- spec/client_spec.rb | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/customerio/client.rb b/lib/customerio/client.rb index 634c207..44fc3d7 100644 --- a/lib/customerio/client.rb +++ b/lib/customerio/client.rb @@ -166,11 +166,11 @@ def unsuppress_path(customer_id) end def track_push_notification_event_path - "/push/events" + "/push/events" end def track_delivery_metric_path - "/metrics" + "/api/v1/metrics" end def merge_customers_path diff --git a/spec/client_spec.rb b/spec/client_spec.rb index 8d95676..a35e6f4 100644 --- a/spec/client_spec.rb +++ b/spec/client_spec.rb @@ -699,8 +699,8 @@ def json(data) } end - it "sends a POST request to customer.io's /metrics endpoint" do - stub_request(:post, api_uri('/metrics')). + it "sends a POST request to customer.io's /api/v1/metrics endpoint" do + stub_request(:post, api_uri('/api/v1/metrics')). with( :body => json(attributes.merge({ :metric => 'opened' @@ -714,7 +714,7 @@ def json(data) end it "should raise if event is invalid" do - stub_request(:post, api_uri('/metrics')). + stub_request(:post, api_uri('/api/v1/metrics')). to_return(:status => 200, :body => "", :headers => {}) expect { @@ -723,7 +723,7 @@ def json(data) end it "should raise if delivery_id is invalid" do - stub_request(:post, api_uri('/metrics')). + stub_request(:post, api_uri('/api/v1/metrics')). to_return(:status => 200, :body => "", :headers => {}) expect { @@ -736,7 +736,7 @@ def json(data) end it "should raise if timestamp is invalid" do - stub_request(:post, api_uri('/metrics')). + stub_request(:post, api_uri('/api/v1/metrics')). to_return(:status => 200, :body => "", :headers => {}) client.track_delivery_metric('opened', attributes.merge({ :timestamp => nil }))