Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add track delivery metric #112

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 40 additions & 6 deletions lib/customerio/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -90,6 +100,7 @@ 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]
attributes = Hash[attributes.map { |(k,v)| [ k.to_sym, v ] }].
Expand All @@ -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') 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

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)
Expand Down Expand Up @@ -142,7 +166,11 @@ def unsuppress_path(customer_id)
end

def track_push_notification_event_path
"/push/events"
"/push/events"
end

def track_delivery_metric_path
"/api/v1/metrics"
end

def merge_customers_path
Expand Down Expand Up @@ -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
Expand Down
66 changes: 66 additions & 0 deletions spec/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 /api/v1/metrics endpoint" do
stub_request(:post, api_uri('/api/v1/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('/api/v1/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('/api/v1/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('/api/v1/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)
Expand Down