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

Support for Criteo #17

Merged
merged 19 commits into from
Mar 26, 2015
Merged
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
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,24 +328,48 @@ It will render the following to the site source:

[Criteo](http://www.criteo.com/) retargeting service.

#### Basic configuration

```
config.middleware.use(Rack::Tracker) do
handler :criteo, { set_account: '1234' }
end
```

Other global criteo handler options are:
* `setCustomerId` (value can be a static or a dynamic, e.g. `lambda { |env| env['rack.session']['user_id'] }`)
* `setSiteType` (`m`, `t`, `d`)
* `set_customer_id: 'x'`
* `set_site_type: 'd'` - possible values are `m` (mobile), `t` (tablet), `d` (desktop)

Option values can be either static or dynamic by providing a lambda being reevaluated for each request, e.g. `set_customer_id: lambda { |env| env['rack.session']['user_id'] }`

#### Tracking events

This will track a basic event:

```
def show
tracker do |t|
t.criteo :track, { event: 'viewItem', item: 'P0001' }
t.criteo :view_item, { item: 'P0001' }
end
end
```

This will render to the follwing code in the JS:

```
window.criteo_q.push({"event": "viewItem", "item": "P001" });
```

The first argument for `t.criteo` is always the criteo event (e.g. `:view_item`, `:view_list`, `:track_transaction`, `:view_basket`) and the second argument are additional properties for the event.

Another example

```
t.criteo :track_transaction, { id: 'id', item: { id: "P0038", price: "6.54", quantity: 1 } }
```

end

### Custom Handlers

Tough we give you handlers for a few tracking services right out of the box, you might
Expand Down
1 change: 1 addition & 0 deletions lib/rack/tracker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
require "rack/tracker/facebook/facebook"
require "rack/tracker/vwo/vwo"
require "rack/tracker/go_squared/go_squared"
require "rack/tracker/criteo/criteo"

module Rack
class Tracker
Expand Down
36 changes: 36 additions & 0 deletions lib/rack/tracker/criteo/criteo.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
class Criteo < Rack::Tracker::Handler

TRACKER_EVENTS = {
# event name => event key name, e.g. { event: 'setSiteType', type: '' }
set_site_type: :type,
set_account: :account,
set_customer_id: :id
}

class Event < OpenStruct
def write
to_h.to_json
end
end

self.position = :body

# global events (setSiteType, setAccount, ...) for each tracker instance
def tracker_events
@tracker_events ||= [].tap do |tracker_events|
options.slice(*TRACKER_EVENTS.keys).each do |key, value|
if option_value = value.respond_to?(:call) ? value.call(env) : value
tracker_events << Event.new(:event => "#{key}".camelize(:lower), TRACKER_EVENTS[key] => "#{option_value}")
end
end
end
end

def render
Tilt.new( File.join( File.dirname(__FILE__), 'template', 'criteo.erb') ).render(self)
end

def self.track(name, event_name, event_args = {})
{ name.to_s => [{ 'class_name' => 'Event', 'event' => event_name.to_s.camelize(:lower) }.merge(event_args)] }
end
end
9 changes: 9 additions & 0 deletions lib/rack/tracker/criteo/template/criteo.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<% if events.any? %>
<script type='text/javascript' src='//static.criteo.net/js/ld/ld.js' async='true'></script>
<script type='text/javascript'>
window.criteo_q = window.criteo_q || [];
<% (tracker_events + events).each do |event| %>
window.criteo_q.push(<%= event.write %>);
<% end %>
</script>
<% end %>
122 changes: 122 additions & 0 deletions spec/handler/criteo_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
RSpec.describe Rack::Tracker::Criteo do

describe Rack::Tracker::Criteo::Event do

subject { described_class.new(event: "viewItem", item: 'P001') }

describe '#write' do
specify { expect(subject.write).to eq("{\"event\":\"viewItem\",\"item\":\"P001\"}") }
end
end

def env
{}
end

it 'will be placed in the body' do
expect(described_class.position).to eq(:body)
expect(described_class.new(env).position).to eq(:body)
end

describe '#render' do
context 'with events' do
let(:env) {
{
'tracker' => {
'criteo' =>
[
{
'event' => 'viewItem',
'item' => 'P001',
'class_name' => 'Event'
}
]
}
}
}

subject { described_class.new(env).render }

it 'will push the tracking events to the queue' do
expect(subject).to include 'window.criteo_q.push({"event":"viewItem","item":"P001"});'
end
end

context 'without events' do
let(:env) {
{
'tracker' => {
'criteo' => []
}
}
}

subject { described_class.new(env, { user_id: ->(env){ '123' } }).render }

it 'should render nothing' do
expect(subject).to eql ""
end
end
end

describe '#tracker_events' do
subject { described_class.new(env, options) }

context 'nil value' do
let(:options) { { set_account: nil } }

it 'should ignore option' do
expect(subject.tracker_events).to match_array []
end
end

context 'static string value' do
let(:options) { { set_account: '1234' } }

it 'should set the value' do
expect(subject.tracker_events).to match_array [
Rack::Tracker::Criteo::Event.new(event: 'setAccount', account: '1234')
]
end
end

context 'static integer value' do
let(:options) { { set_customer_id: 1234 } }

it 'should set the value as string' do
expect(subject.tracker_events).to match_array [
Rack::Tracker::Criteo::Event.new(event: 'setCustomerId', id: '1234')
]
end
end

context 'unsupported option' do
let(:options) { { unsupported: "option" } }

subject { described_class.new(env, options) }

it 'should ignore the option' do
expect(subject.tracker_events).to match_array []
end
end

context 'proc returning value' do
let(:options) { { set_site_type: ->(env){ 'm' } } }

it 'should set the value' do
expect(subject.tracker_events).to match_array [
Rack::Tracker::Criteo::Event.new(event: 'setSiteType', type: 'm')
]
end
end

context 'proc returning nil' do
let(:options) { { set_account: ->(env){ nil } } }

it 'should ignore the option' do
expect(subject.tracker_events).to match_array []
end
end
end

end
44 changes: 44 additions & 0 deletions spec/integration/criteo_integration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
require 'support/capybara_app_helper'

RSpec.describe "Criteo Integration" do
before do
setup_app(action: :criteo) do |tracker|
tracker.handler(:criteo, {
set_account: '1234',
set_customer_id: ->(env){ '4711' },
set_site_type: ->(env){ 'm' }
})
end
visit '/'
end

subject { page }

it 'should include all the events' do
# tracker_events
expect(page.find("body")).to have_content "window.criteo_q.push({\"event\":\"setAccount\",\"account\":\"1234\"});"
expect(page.find("body")).to have_content "window.criteo_q.push({\"event\":\"setSiteType\",\"type\":\"m\"});"
expect(page.find("body")).to have_content "window.criteo_q.push({\"event\":\"setCustomerId\",\"id\":\"4711\"});"

# events
expect(page.find("body")).to have_content "window.criteo_q.push({\"event\":\"viewItem\",\"item\":\"P001\"});"
expect(page.find("body")).to have_content "window.criteo_q.push({\"event\":\"viewList\",\"item\":[\"P001\",\"P002\"]});"
expect(page.find("body")).to have_content "window.criteo_q.push({\"event\":\"trackTransaction\",\"id\":\"id\",\"item\":{\"id\":\"P0038\",\"price\":\"6.54\",\"quantity\":1}});"
expect(page.find("body")).to have_content "window.criteo_q.push({\"event\":\"viewBasket\",\"item\":[{\"id\":\"P001\",\"price\":\"6.54\",\"quantity\":1},{\"id\":\"P0038\",\"price\":\"2.99\",\"quantity\":1}]});"
end

describe 'adjust tracker position via options' do
before do
setup_app(action: :criteo) do |tracker|
tracker.handler :criteo, { set_account: '1234', position: :head }
end
visit '/'
end

it "will be placed in the specified tag" do
expect(page.find("body")).to_not have_content('criteo')
expect(page.find("head")).to have_content("{\"event\":\"setAccount\",\"account\":\"1234\"}")
end

end
end
10 changes: 10 additions & 0 deletions spec/support/metal_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,14 @@ def go_squared
end
render "metal/index"
end

def criteo
tracker do |t|
t.criteo :view_item, { item: 'P001' }
t.criteo :view_list, { item: ['P001', 'P002'] }
t.criteo :track_transaction, { id: 'id', item: { id: "P0038", price:"6.54", quantity:1 } }
t.criteo :view_basket, { item: [{ id: "P001", price:"6.54", quantity:1 }, { id: "P0038", price:"2.99", quantity:1 }] }
end
render 'metal/index'
end
end