diff --git a/.travis.yml b/.travis.yml index b754765..3c8e02f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,18 @@ rvm: - 2.5.3 - 2.6.1 - jruby-9.1.10.0 + - jruby-9.2.7.0 gemfile: - Gemfile - Gemfile.rails-3.2 - Gemfile.rails-4.2 - Gemfile.rails-5.2 + - Gemfile.rails-6.0 +matrix: + exclude: + - rvm: 2.3.8 + gemfile: Gemfile.rails-6.0 + - rvm: 2.4.5 + gemfile: Gemfile.rails-6.0 + - rvm: jruby-9.1.10.0 + gemfile: Gemfile.rails-6.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3603277..4398e32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +# 1.13.0 + +* [ENHANCEMENT] hotwired/turbo support #160 (thx @wrozka) +* [BUGFIX] Use leftmost match for gtm tag injection #156 (thx @yutoji) + +# 1.12.1 + +* [ENHANCEMENT] Use local variables to prevent instance state #151 (thx @bumi) +* [ENHANCEMENT] Make middleware thread safe #150 (thx @kspe) + +# 1.12.0 + +* [ENHANCEMENT] Add support for Heap #147 (thx @mohanzhang) + +# 1.11.2 + + * [ENHANCEMENT] Allows disabling the Google Analytics pageview send. Defaults to true #131 (thx @ChrisCoffey) + +# 1.11.1 + + * [BUGFIX] Uncaught ReferenceError Fix: wrap Drift account ID in quotes #140 (thx @sassela) + +# 1.11.0 + + * [ENHANCEMENT] Add support for Drift #139 (thx @sassela) + +# 1.10.0 + + * [ENHANCEMENT] Hubspot integration #136 (thx @ChrisCoffey) + +# 1.9.0 + + * [ENHANCEMENT] Integration for Bing tracking #131 (thx @pcraston) + * [ENHANCEMENT] Possibility to integrate Google Optimize ID into the allowed tracker options #127 (thx @nachoabad) + * [ENHANCEMENT] Support for google global events #126 (thx @atd) + # 1.8.0 * [ENHANCEMENT] Google Global Site Tag: basic integration with support for pageviews to Google global tag #123 (thx @atd) diff --git a/Gemfile.rails-6.0 b/Gemfile.rails-6.0 new file mode 100644 index 0000000..ee3fb05 --- /dev/null +++ b/Gemfile.rails-6.0 @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gemspec + +gem 'activesupport', '~> 6.0.0' +gem 'actionpack', '~> 6.0.0' diff --git a/README.md b/README.md index 499098a..e1b7927 100644 --- a/README.md +++ b/README.md @@ -18,18 +18,9 @@ rack middleware that can be hooked up to multiple services and exposing them in fashion. It comes in two parts, the first one is the actual middleware that you can add to the middleware stack the second part are the service-handlers that you're going to use in your application. It's easy to add your own [custom handlers](#custom-handlers), -but to get you started we're shipping support for the following services out of the box: - -* [Google Global Site Tag](#google-global) -* [Google Analytics](#google-analytics) -* [Google Adwords Conversion](#google-adwords-conversion) -* [Google Tag Manager](#google-tag-manager) -* [Facebook](#facebook) -* [Visual Website Optimizer (VWO)](#visual-website-optimizer-vwo) -* [GoSquared](#gosquared) -* [Criteo](#criteo) -* [Zanox](#zanox) -* [Hotjar](#hotjar) +but to get you started we're shipping support for the services [mentioned below](#services) +out of the box: + ## Respecting the Do Not Track (DNT) HTTP header @@ -112,6 +103,8 @@ request.env['tracker'] = { } ``` +## Services + ### Google Global Site Tag (gtag.js) * `:anonymize_ip` - sets the tracker to remove the last octet from all IP addresses, see https://developers.google.com/analytics/devguides/collection/gtagjs/ip-anonymization for details. @@ -147,6 +140,7 @@ end * `:enhanced_ecommerce` - Enables [Enhanced Ecommerce Tracking](https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce) * `:optimize` - pass [Google Optimize container ID](https://support.google.com/360suite/optimize/answer/6262084#example-combined-snippet) as value (e.g. `optimize: 'GTM-1234'`). * `:pageview_url_script` - a String containing a custom js script evaluating to the url that shoudl be given to the pageview event. Default to `window.location.pathname + window.location.search`. +* `:explicit_pageview` - A boolean that controls whether to send the `pageview` event on pageload. This defaults to true. #### Events @@ -576,6 +570,62 @@ config.middleware.use(Rack::Tracker) do end ``` +### Bing + +[Bing](https://bingads.microsoft.com/) + +To add the tracking snippet: + +``` +config.middleware.use(Rack::Tracker) do + handler :bing, { tracker: '12345678' } +end +``` + +To send conversion events: +``` +tracker do |t| + t.bing :conversion, { + type: 'event', + category: 'Users', + action: 'Login', + label: 'Standard', + value: 10 + } +end +``` + +### Hubspot + +[Hubspot](https://www.hubspot.com/) + +``` +config.middleware.use(Rack::Tracker) do + handler :hubspot, { site_id: '1234' } +end +``` + +### Drift + +[Drift](https://www.drift.com/) + +``` +config.middleware.use(Rack::Tracker) do + handler :drift, account_id: 'DRIFT_ID' +end +``` + +### Heap + +[Heap](https://heap.io/). Heap has Projects (e.g. "Main") which have multiple +Environments (e.g. "Production" or "Development"). `env_id` is therefore the numerical ID +that represents the Environment. See Settings -> Projects -> Environments in your dashboard. + +``` +config.middleware.use(Rack::Tracker) do + handler :heap, env_id: 'HEAP_ID' +end +``` ### Custom Handlers diff --git a/lib/rack/tracker.rb b/lib/rack/tracker.rb index 1a7c899..4e10d87 100644 --- a/lib/rack/tracker.rb +++ b/lib/rack/tracker.rb @@ -23,6 +23,10 @@ require "rack/tracker/criteo/criteo" require "rack/tracker/zanox/zanox" require "rack/tracker/hotjar/hotjar" +require "rack/tracker/bing/bing" +require "rack/tracker/hubspot/hubspot" +require "rack/tracker/drift/drift" +require "rack/tracker/heap/heap" module Rack class Tracker @@ -34,10 +38,14 @@ def initialize(app, &block) end def call(env) - @status, @headers, @body = @app.call(env) - return [@status, @headers, @body] unless html? - response = Rack::Response.new([], @status, @headers) + dup._call(env) + end + + def _call(env) + status, headers, body = @app.call(env) + return [status, headers, body] unless headers['Content-Type'] =~ /html/ + response = Rack::Response.new([], status, headers) env[EVENT_TRACKING_KEY] ||= {} if session = env["rack.session"] @@ -48,16 +56,14 @@ def call(env) session[EVENT_TRACKING_KEY] = env[EVENT_TRACKING_KEY] end - @body.each { |fragment| response.write inject(env, fragment) } - @body.close if @body.respond_to?(:close) + body.each { |fragment| response.write inject(env, fragment) } + body.close if body.respond_to?(:close) response.finish end private - def html?; @headers['Content-Type'] =~ /html/; end - def inject(env, response) duplicated_response = response.dup @handlers.each(env) do |handler| diff --git a/lib/rack/tracker/bing/bing.rb b/lib/rack/tracker/bing/bing.rb new file mode 100644 index 0000000..f7e71c7 --- /dev/null +++ b/lib/rack/tracker/bing/bing.rb @@ -0,0 +1,12 @@ +class Rack::Tracker::Bing < Rack::Tracker::Handler + + class Conversion < OpenStruct + end + + self.position = :body + + def tracker + options[:tracker].respond_to?(:call) ? options[:tracker].call(env) : options[:tracker] + end + +end diff --git a/lib/rack/tracker/bing/template/bing.erb b/lib/rack/tracker/bing/template/bing.erb new file mode 100644 index 0000000..9c0d342 --- /dev/null +++ b/lib/rack/tracker/bing/template/bing.erb @@ -0,0 +1,22 @@ +<% if events.any? %> + + +<% end %> + + + \ No newline at end of file diff --git a/lib/rack/tracker/drift/drift.rb b/lib/rack/tracker/drift/drift.rb new file mode 100644 index 0000000..fb7fe34 --- /dev/null +++ b/lib/rack/tracker/drift/drift.rb @@ -0,0 +1,2 @@ +class Rack::Tracker::Drift < Rack::Tracker::Handler +end diff --git a/lib/rack/tracker/drift/template/drift.erb b/lib/rack/tracker/drift/template/drift.erb new file mode 100644 index 0000000..e046404 --- /dev/null +++ b/lib/rack/tracker/drift/template/drift.erb @@ -0,0 +1,26 @@ + diff --git a/lib/rack/tracker/google_analytics/google_analytics.rb b/lib/rack/tracker/google_analytics/google_analytics.rb index a18d1e0..f33663b 100644 --- a/lib/rack/tracker/google_analytics/google_analytics.rb +++ b/lib/rack/tracker/google_analytics/google_analytics.rb @@ -2,6 +2,11 @@ class Rack::Tracker::GoogleAnalytics < Rack::Tracker::Handler self.allowed_tracker_options = [:cookie_domain, :user_id] + def initialize(env, options = {}) + options[:explicit_pageview] = true if !options.has_key?(:explicit_pageview) + super(env, options) + end + class Send < OpenStruct def initialize(attrs = {}) attrs.reverse_merge!(type: 'event') diff --git a/lib/rack/tracker/google_analytics/template/google_analytics.erb b/lib/rack/tracker/google_analytics/template/google_analytics.erb index 000ff7c..d6d9d7b 100644 --- a/lib/rack/tracker/google_analytics/template/google_analytics.erb +++ b/lib/rack/tracker/google_analytics/template/google_analytics.erb @@ -1,5 +1,5 @@ - +<% end %> diff --git a/lib/rack/tracker/google_global/google_global.rb b/lib/rack/tracker/google_global/google_global.rb index 764ee02..5ccb817 100644 --- a/lib/rack/tracker/google_global/google_global.rb +++ b/lib/rack/tracker/google_global/google_global.rb @@ -9,14 +9,33 @@ def params end end + class Event < OpenStruct + PREFIXED_PARAMS = %i[category label] + SKIP_PARAMS = %i[action] + + def params + Hash[to_h.except(*SKIP_PARAMS).map { |key, value| [param_key(key), value] }] + end + + private + + def param_key(key) + PREFIXED_PARAMS.include?(key) ? "event_#{key}" : key.to_s + end + end + def pages - events # TODO: Filter pages after Event is implemented + select_handler_events(Page) + end + + alias handler_events events + + def events + select_handler_events(Event) end def trackers - options[:trackers].map { |tracker| - tracker[:id].respond_to?(:call) ? tracker.merge(id: tracker[:id].call(env)) : tracker - }.reject { |tracker| tracker[:id].nil? } + @_trackers ||= build_trackers end def set_options @@ -25,8 +44,37 @@ def set_options private + def build_trackers + options[:trackers].map(&method(:call_tracker)).reject(&method(:invalid_tracker?)) + end + + def call_tracker(tracker) + if tracker[:id].respond_to?(:call) + tracker.merge(id: tracker[:id].call(env)) + else + tracker + end + end + + def invalid_tracker?(tracker) + if tracker[:id].to_s.strip == '' + $stdout.puts <<~WARN + WARNING: One of the trackers specified for Rack::Tracker handler 'google_global' is empty. + Trackers: #{options[:trackers]} + WARN + + true + else + false + end + end + def build_set_options value = options[:set] value.respond_to?(:call) ? value.call(env) : value end + + def select_handler_events(klass) + handler_events.select { |event| event.is_a?(klass) } + end end diff --git a/lib/rack/tracker/google_global/template/google_global.erb b/lib/rack/tracker/google_global/template/google_global.erb index ea656a3..9103419 100644 --- a/lib/rack/tracker/google_global/template/google_global.erb +++ b/lib/rack/tracker/google_global/template/google_global.erb @@ -1,4 +1,4 @@ -<% if trackers %> +<% if trackers.any? %> <% end %> 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 2db9d2b..1a05489 100644 --- a/lib/rack/tracker/google_tag_manager/google_tag_manager.rb +++ b/lib/rack/tracker/google_tag_manager/google_tag_manager.rb @@ -10,7 +10,7 @@ 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{