diff --git a/README.md b/README.md index e1b7927..e00f21c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +# This is for a proof-of-concept implementation of rack-tracker for our BillDoctor repo. +[Proof-of-concept PR here](https://github.com/cpcmarketing/billdoctor-web/pull/1404) + # Rack::Tracker [![Code Climate](https://codeclimate.com/github/railslove/rack-tracker/badges/gpa.svg)](https://codeclimate.com/github/railslove/rack-tracker) [![Build Status](https://travis-ci.org/railslove/rack-tracker.svg?branch=master)](https://travis-ci.org/railslove/rack-tracker) @@ -283,11 +286,11 @@ Google Tag manager code snippet supports the container id end ``` -You can also use an experimental feature to track pageviews under turbolinks, which adds a `pageView` event with a `virtualUrl` of the current url. +You can also use an experimental feature to track pageviews under turbo, which adds a `pageView` event with a `virtualUrl` of the current url. ```ruby config.middleware.use(Rack::Tracker) do - handler :google_tag_manager, { container: 'GTM-XXXXXX', turbolinks: true } + handler :google_tag_manager, { container: 'GTM-XXXXXX', turbo: true } end ``` diff --git a/lib/rack/tracker.rb b/lib/rack/tracker.rb index 4e10d87..3d843a5 100644 --- a/lib/rack/tracker.rb +++ b/lib/rack/tracker.rb @@ -1,9 +1,6 @@ require "rack" require "tilt" -require "active_support/core_ext/class/attribute" -require "active_support/core_ext/hash" -require "active_support/json" -require "active_support/inflector" +require "active_support/all" require "rack/tracker/version" require "rack/tracker/extensions" @@ -27,6 +24,10 @@ require "rack/tracker/hubspot/hubspot" require "rack/tracker/drift/drift" require "rack/tracker/heap/heap" +require "rack/tracker/tiktok_pixel/tiktok_pixel" +require "rack/tracker/impact/impact" +require "rack/tracker/cordial/cordial" +require "rack/tracker/braze/braze" module Rack class Tracker diff --git a/lib/rack/tracker/braze/braze.rb b/lib/rack/tracker/braze/braze.rb new file mode 100644 index 0000000..7286f0c --- /dev/null +++ b/lib/rack/tracker/braze/braze.rb @@ -0,0 +1,37 @@ +class Rack::Tracker::Braze < Rack::Tracker::Handler + class Event < OpenStruct + def write; end + end + + class AutomaticallyShowInAppMessages < Event + def name + 'automaticallyShowInAppMessages' + end + end + + class ChangeUser < Event + def name + 'changeUser' + end + + def write + user_id.to_json + end + end + + class LogCustomEvent < Event + def name + 'logCustomEvent' + end + + def write + event_name.to_json << ", #{properties.to_json}" + end + end + + class OpenSession < Event + def name + 'openSession' + end + end +end diff --git a/lib/rack/tracker/braze/template/braze.erb b/lib/rack/tracker/braze/template/braze.erb new file mode 100644 index 0000000..0c256f8 --- /dev/null +++ b/lib/rack/tracker/braze/template/braze.erb @@ -0,0 +1,22 @@ + + +<% if events.any? %> + +<% end %> diff --git a/lib/rack/tracker/cordial/cordial.rb b/lib/rack/tracker/cordial/cordial.rb new file mode 100644 index 0000000..3780a80 --- /dev/null +++ b/lib/rack/tracker/cordial/cordial.rb @@ -0,0 +1,40 @@ +class Rack::Tracker::Cordial < Rack::Tracker::Handler + class Event < OpenStruct + def write + meta_data = action_name.present? ? action_name_to_json : "" + properties.present? ? meta_data << properties_to_json : meta_data + end + + def action_name_to_json + "#{action_name.to_json}, " + end + + def properties_to_json + # TODO: Set this up so we can pass in JavaScript variable names. Currently, we can only pass in strings since we're using to_json. + # Ex: crdl("event", "NPSSubmit", {"cookie_id":"8ded0052-4668-4b57-9e14-bd8501678f92","email":null,"first_name":null,"rating":"e.detail.recommendation_rating.value"}); + props = properties.to_json + end + end + + class Connect < Event + def name + 'connect' + end + end + + class Contact < Event + def name + 'contact' + end + + def write + "#{auth_data.to_json}, #{contact_data.to_json}" + end + end + + class CustomEvent < Event + def name + 'event' + end + end +end diff --git a/lib/rack/tracker/cordial/template/cordial.erb b/lib/rack/tracker/cordial/template/cordial.erb new file mode 100644 index 0000000..acdef19 --- /dev/null +++ b/lib/rack/tracker/cordial/template/cordial.erb @@ -0,0 +1,28 @@ + + +<% if events.any? %> + +<% end %> diff --git a/lib/rack/tracker/criteo/criteo.rb b/lib/rack/tracker/criteo/criteo.rb index 5a71c7b..9f538dd 100644 --- a/lib/rack/tracker/criteo/criteo.rb +++ b/lib/rack/tracker/criteo/criteo.rb @@ -30,4 +30,9 @@ def tracker_events 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 + + def turbo_event? + # TODO: Make this work so we can set eventListeners + options[:turbo_event] + end end diff --git a/lib/rack/tracker/criteo/template/criteo.erb b/lib/rack/tracker/criteo/template/criteo.erb index 18ea6c6..3e8e751 100644 --- a/lib/rack/tracker/criteo/template/criteo.erb +++ b/lib/rack/tracker/criteo/template/criteo.erb @@ -1,9 +1,18 @@ <% if events.any? %> - - + <% end %> diff --git a/lib/rack/tracker/facebook_pixel/facebook_pixel.rb b/lib/rack/tracker/facebook_pixel/facebook_pixel.rb index f6015dd..ce81717 100644 --- a/lib/rack/tracker/facebook_pixel/facebook_pixel.rb +++ b/lib/rack/tracker/facebook_pixel/facebook_pixel.rb @@ -1,10 +1,11 @@ class Rack::Tracker::FacebookPixel < Rack::Tracker::Handler - self.position = :body self.allowed_tracker_options = [:id] class Event < OpenStruct def write - options.present? ? type_to_json << options_to_json : type_to_json + meta_data = type_to_json + options.present? ? meta_data << options_to_json : meta_data + event_id.present? ? meta_data << event_id_to_json : meta_data end private @@ -13,11 +14,25 @@ def type_to_json type.to_json end + def event_id_to_json + ", #{event_id.to_json}" + end + def options_to_json ", #{options.to_json}" end end + class Init < Event + def name + 'init' + end + + def write + options.present? ? options_to_json : nil + end + end + class Track < Event def name 'track' @@ -29,4 +44,25 @@ def name 'trackCustom' end end + + def inject(response) + # Sub! is enough, in well formed html there's only one head or body tag. + # Block syntax need to be used, otherwise backslashes in input will mess the output. + # @see http://stackoverflow.com/a/4149087/518204 and https://github.com/railslove/rack-tracker/issues/50 + response.sub! %r{} do |m| + m.to_s << self.render_head + end + response.sub! %r{} do |m| + m.to_s << self.render_body + end + response + end + + def render_head + Tilt.new( File.join( File.dirname(__FILE__), 'template', 'facebook_pixel_head.erb') ).render(self) + end + + def render_body + Tilt.new( File.join( File.dirname(__FILE__), 'template', 'facebook_pixel_body.erb') ).render(self) + end end diff --git a/lib/rack/tracker/facebook_pixel/template/facebook_pixel_body.erb b/lib/rack/tracker/facebook_pixel/template/facebook_pixel_body.erb new file mode 100644 index 0000000..0d95d12 --- /dev/null +++ b/lib/rack/tracker/facebook_pixel/template/facebook_pixel_body.erb @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/lib/rack/tracker/facebook_pixel/template/facebook_pixel.erb b/lib/rack/tracker/facebook_pixel/template/facebook_pixel_head.erb similarity index 67% rename from lib/rack/tracker/facebook_pixel/template/facebook_pixel.erb rename to lib/rack/tracker/facebook_pixel/template/facebook_pixel_head.erb index 2901c52..7cbd2b0 100644 --- a/lib/rack/tracker/facebook_pixel/template/facebook_pixel.erb +++ b/lib/rack/tracker/facebook_pixel/template/facebook_pixel_head.erb @@ -6,19 +6,17 @@ n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0; t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window, document,'script','//connect.facebook.net/en_US/fbevents.js'); - - fbq('init', '<%= tracker_options[:id] %>'); - fbq('track', "PageView"); } - <% if events.any? %> <% end %> diff --git a/lib/rack/tracker/google_adwords_conversion/template/google_adwords_conversion.erb b/lib/rack/tracker/google_adwords_conversion/template/google_adwords_conversion.erb index e91e75e..2c8e8ff 100644 --- a/lib/rack/tracker/google_adwords_conversion/template/google_adwords_conversion.erb +++ b/lib/rack/tracker/google_adwords_conversion/template/google_adwords_conversion.erb @@ -11,7 +11,7 @@ diff --git a/lib/rack/tracker/google_tag_manager/google_tag_manager.rb b/lib/rack/tracker/google_tag_manager/google_tag_manager.rb index 1a05489..d5098ef 100644 --- a/lib/rack/tracker/google_tag_manager/google_tag_manager.rb +++ b/lib/rack/tracker/google_tag_manager/google_tag_manager.rb @@ -19,10 +19,6 @@ def inject(response) response end - def container - options[:container].respond_to?(:call) ? options[:container].call(env) : options[:container] - end - def render_head Tilt.new( File.join( File.dirname(__FILE__), 'template', 'google_tag_manager_head.erb') ).render(self) end diff --git a/lib/rack/tracker/google_tag_manager/template/google_tag_manager_body.erb b/lib/rack/tracker/google_tag_manager/template/google_tag_manager_body.erb index cc30f77..dbeb4d7 100644 --- a/lib/rack/tracker/google_tag_manager/template/google_tag_manager_body.erb +++ b/lib/rack/tracker/google_tag_manager/template/google_tag_manager_body.erb @@ -1,4 +1,2 @@ -<% if container %> - -<% end %> diff --git a/lib/rack/tracker/google_tag_manager/template/google_tag_manager_head.erb b/lib/rack/tracker/google_tag_manager/template/google_tag_manager_head.erb index 2df1117..b47652e 100644 --- a/lib/rack/tracker/google_tag_manager/template/google_tag_manager_head.erb +++ b/lib/rack/tracker/google_tag_manager/template/google_tag_manager_head.erb @@ -1,34 +1,32 @@ -<% if container %> - - <% if options[:turbolinks] %> - document.addEventListener('turbolinks:load', function(event) { - var url = event.data.url; - dataLayer.push({'event':'pageView','virtualUrl': url}); - }); - document.addEventListener('turbo:load', function(event) { - var url = event.detail.url; - dataLayer.push({'event':'pageView','virtualUrl': url}); - }); - <% end %> +<% if options[:universal_analytics_id] %> + +<% end %> - <% if events.any? %> - - <% end %> - +<% if events.any? %> + <%# Generates unique script tags to ensure that turbo includes %> + <%# them in the document even if the same event happens more than once %> + // Unique Script ID: <%= SecureRandom.base64(8) %> + dataLayer.push(<%= events.map(&:write).join(', ') %>); + <% end %> - - diff --git a/lib/rack/tracker/heap/heap.rb b/lib/rack/tracker/heap/heap.rb index dac9efb..2453174 100644 --- a/lib/rack/tracker/heap/heap.rb +++ b/lib/rack/tracker/heap/heap.rb @@ -1,2 +1,30 @@ class Rack::Tracker::Heap < Rack::Tracker::Handler + class Event < OpenStruct + def write + return if !properties.present? + properties.to_h + end + end + + class Identify < Event + def name + 'identify' + end + + def write + "#{id.to_s}" + end + end + + class AddUser < Event + def name + 'addUserProperties' + end + end + + class AddEvent < Event + def name + 'addEventProperties' + end + end end diff --git a/lib/rack/tracker/heap/template/heap.erb b/lib/rack/tracker/heap/template/heap.erb index b3ac665..77aa26a 100644 --- a/lib/rack/tracker/heap/template/heap.erb +++ b/lib/rack/tracker/heap/template/heap.erb @@ -1,4 +1,19 @@ + +<% if events.any? %> + +<% end %> diff --git a/lib/rack/tracker/hotjar/hotjar.rb b/lib/rack/tracker/hotjar/hotjar.rb index 482e5d3..fc73dcd 100644 --- a/lib/rack/tracker/hotjar/hotjar.rb +++ b/lib/rack/tracker/hotjar/hotjar.rb @@ -1,2 +1,10 @@ class Rack::Tracker::Hotjar < Rack::Tracker::Handler + class Event < OpenStruct + end + + class Identify < Event + def name + 'identify' + end + end end diff --git a/lib/rack/tracker/hotjar/template/hotjar.erb b/lib/rack/tracker/hotjar/template/hotjar.erb index 885ff26..c3fc660 100644 --- a/lib/rack/tracker/hotjar/template/hotjar.erb +++ b/lib/rack/tracker/hotjar/template/hotjar.erb @@ -1,10 +1,18 @@ + +<% if events.any? %> + +<% end %> diff --git a/lib/rack/tracker/impact/impact.rb b/lib/rack/tracker/impact/impact.rb new file mode 100644 index 0000000..9144577 --- /dev/null +++ b/lib/rack/tracker/impact/impact.rb @@ -0,0 +1,2 @@ +class Rack::Tracker::Impact < Rack::Tracker::Handler +end diff --git a/lib/rack/tracker/impact/template/impact.erb b/lib/rack/tracker/impact/template/impact.erb new file mode 100644 index 0000000..4a9507b --- /dev/null +++ b/lib/rack/tracker/impact/template/impact.erb @@ -0,0 +1 @@ + diff --git a/lib/rack/tracker/tiktok_pixel/template/tiktok_pixel.erb b/lib/rack/tracker/tiktok_pixel/template/tiktok_pixel.erb new file mode 100644 index 0000000..afe90ea --- /dev/null +++ b/lib/rack/tracker/tiktok_pixel/template/tiktok_pixel.erb @@ -0,0 +1,8 @@ + diff --git a/lib/rack/tracker/tiktok_pixel/tiktok_pixel.rb b/lib/rack/tracker/tiktok_pixel/tiktok_pixel.rb new file mode 100644 index 0000000..3adbb3f --- /dev/null +++ b/lib/rack/tracker/tiktok_pixel/tiktok_pixel.rb @@ -0,0 +1,5 @@ +class Rack::Tracker::TiktokPixel < Rack::Tracker::Handler + self.position = :body + self.allowed_tracker_options = [:id] + +end diff --git a/spec/handler/cordial_spec.rb b/spec/handler/cordial_spec.rb new file mode 100644 index 0000000..9667b62 --- /dev/null +++ b/spec/handler/cordial_spec.rb @@ -0,0 +1 @@ +# TODO: this diff --git a/spec/handler/facebook_pixel_spec.rb b/spec/handler/facebook_pixel_spec.rb index 9716529..397d235 100644 --- a/spec/handler/facebook_pixel_spec.rb +++ b/spec/handler/facebook_pixel_spec.rb @@ -3,13 +3,8 @@ def env { 'PIXEL_ID' => 'DYNAMIC_PIXEL_ID' } 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 'with static id' do - subject { described_class.new(env, id: 'PIXEL_ID').render } + subject { described_class.new(env, id: 'PIXEL_ID').render_head } it 'will push the tracking events to the queue' do expect(subject).to match(%r{fbq\('init', 'PIXEL_ID'\)}) @@ -21,7 +16,7 @@ def env end describe 'with dynamic id' do - subject { described_class.new(env, id: lambda { |env| env['PIXEL_ID'] }).render } + subject { described_class.new(env, id: lambda { |env| env['PIXEL_ID'] }).render_head } it 'will push the tracking events to the queue' do expect(subject).to match(%r{fbq\('init', 'DYNAMIC_PIXEL_ID'\)}) @@ -59,7 +54,7 @@ def env } } end - subject { described_class.new(env).render } + subject { described_class.new(env).render_head } it 'will push the tracking events to the queue' do expect(subject).to match(%r{"track", "Purchase", \{"value":"23","currency":"EUR"\}}) @@ -70,4 +65,27 @@ def env expect(subject).to match(%r{https://www.facebook.com/tr\?id=&ev=PageView&noscript=1}) end end + + describe '#inject' do + subject { handler_object.inject(example_response) } + let(:handler_object) { described_class.new(env, container: 'somebody') } + + before do + allow(handler_object).to receive(:render_head).and_return('') + allow(handler_object).to receive(:render_body).and_return('') + end + + context 'with one line html response' do + let(:example_response) { "" } + + it 'will have render_head content in head tag' do + expect(subject).to match(%r{.*.*}) + end + + it 'will have render_body content in body tag' do + expect(subject).to match(%r{.*.*}) + end + + end + end end diff --git a/spec/handler/impact_spec.rb b/spec/handler/impact_spec.rb new file mode 100644 index 0000000..9667b62 --- /dev/null +++ b/spec/handler/impact_spec.rb @@ -0,0 +1 @@ +# TODO: this diff --git a/spec/handler/tiktok_pixel_spec.rb b/spec/handler/tiktok_pixel_spec.rb new file mode 100644 index 0000000..46b015a --- /dev/null +++ b/spec/handler/tiktok_pixel_spec.rb @@ -0,0 +1,26 @@ +RSpec.describe Rack::Tracker::TiktokPixel do + def env + { 'PIXEL_ID' => 'DYNAMIC_PIXEL_ID' } + 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 'with static id' do + subject { described_class.new(env, id: 'PIXEL_ID').render } + + it 'will push the tracking events to the queue' do + expect(subject).to match(%r{ttq\.load\('PIXEL_ID'\)}) + end + end + + describe 'with dynamic id' do + subject { described_class.new(env, id: lambda { |env| env['PIXEL_ID'] }).render } + + it 'will push the tracking events to the queue' do + expect(subject).to match(%r{ttq\.load\('DYNAMIC_PIXEL_ID'\)}) + end + end +end diff --git a/spec/integration/cordial_integration_spec.rb b/spec/integration/cordial_integration_spec.rb new file mode 100644 index 0000000..9667b62 --- /dev/null +++ b/spec/integration/cordial_integration_spec.rb @@ -0,0 +1 @@ +# TODO: this diff --git a/spec/integration/google_tag_manager_integration_spec.rb b/spec/integration/google_tag_manager_integration_spec.rb index 9d2b5aa..a25c35c 100644 --- a/spec/integration/google_tag_manager_integration_spec.rb +++ b/spec/integration/google_tag_manager_integration_spec.rb @@ -23,14 +23,13 @@ expect(page.find("body")).to have_xpath '//body/noscript/iframe[@src="https://www.googletagmanager.com/ns.html?id=GTM-ABCDEF"]' end - it "embeds turbolinks and turbo observers if requested" do + it "embeds turbo observers if requested" do visit '/' - expect(page.find("head")).to_not have_content "turbolinks:load" + expect(page.find("head")).to_not have_content "turbo:load" setup_app(action: :google_tag_manager) do |tracker| - tracker.handler :google_tag_manager, { container: 'GTM-ABCDEF', turbolinks: true } + tracker.handler :google_tag_manager, { container: 'GTM-ABCDEF', turbo: true } end visit '/' - expect(page.find("head")).to have_content "turbolinks:load" expect(page.find("head")).to have_content "turbo:load" end end diff --git a/spec/integration/impact_integration_spec.rb b/spec/integration/impact_integration_spec.rb new file mode 100644 index 0000000..9667b62 --- /dev/null +++ b/spec/integration/impact_integration_spec.rb @@ -0,0 +1 @@ +# TODO: this diff --git a/spec/integration/tiktok_pixel_integration_spec.rb b/spec/integration/tiktok_pixel_integration_spec.rb new file mode 100644 index 0000000..97f0d5c --- /dev/null +++ b/spec/integration/tiktok_pixel_integration_spec.rb @@ -0,0 +1,16 @@ +require 'support/capybara_app_helper' + +RSpec.describe "Tiktok Pixel Integration" do + before do + setup_app(action: :tiktok_pixel) do |tracker| + tracker.handler :tiktok_pixel, { id: 'PIXEL_ID' } + end + visit '/' + end + + subject { page } + + it "embeds the script tag from the controller action" do + expect(page).to have_content("ttq.load('PIXEL_ID');") + end +end diff --git a/spec/support/metal_controller.rb b/spec/support/metal_controller.rb index f22447e..336e749 100644 --- a/spec/support/metal_controller.rb +++ b/spec/support/metal_controller.rb @@ -132,4 +132,20 @@ def drift def heap render "metal/index" end + + def tiktok_pixel + render "metal/index" + end + + def impact + render "metal/index" + end + + def cordial + render "metal/index" + end + + def braze + render "metal/index" + end end