railslove / rack-tracker

Tracking made easy: Don’t fool around with adding tracking and analytics partials to your app and concentrate on the things that matter.
https://www.railslove.com/open-source
MIT License
647 stars 121 forks source link

Update is coming ? Google Analytics 4 GA4 Killed my groove #159

Open v2lrf opened 3 years ago

v2lrf commented 3 years ago

An update is coming to deal with Google Analytics 4 (GA4), or should I wonder why I need to use old google pixel from old projects (GA3) to make rack-tracker work properly?

At this point, the issue is not really clear. It's clear for instance that the javascript render injected inside the header does not fit for GA4 and makes some problems for the real-time console.

My investigation starts, but the only thing I can see is that with GA3 all is running perfectly... and with GA4 well there is something new behind that stuff...

Here the thing, GA4 ask some different patterns and I'm just wondering if it's not a good idea to care on old GA pixel and keep them alive for now :)

Actually, I don't know yet if anyone would be interested in it or fork it to upgrade my own? Let me know if updating the gem is something open ;-)

If some of you encountered that issue let us know and if there is a better way to fix it fine

care

my setup

#gemfile
ruby "3.0.0"
gem "rails", "~> 6.1.3"
gem "rack-tracker" ,"~>1.12.1"
#...

I'll update that issue asap I got some info

bumi commented 3 years ago

not sure if I can follow. but PRs are very welcome! :)

v2lrf commented 3 years ago

not sure if I can follow. but PRs are very welcome! :)

ok thanks I ll see what I can do or propose

bumi commented 3 years ago

ok, keep me posted and let me know if you need help with the code

puneetpandey commented 1 year ago

Following up on this one.. any fix for GA4 released so far? @v2lrf

puneetpandey commented 1 year ago

I am somehow able to monkey patch the library and modified the code to meet with our requirements. Here's what I did:

  1. Created a new file under config/initializers/rack/tracker/handler.rb with:
class Rack::Tracker::Handler
  def render
    if handler_name == "google_analytics"
      Tilt.new(File.join(File.dirname(__FILE__), "#{handler_name}/template/#{handler_name}.erb")).render(self)
    end
  end
end
  1. Created a new file under config/initializers/rack/tracker/google_analytics/template/google_analytics.erb with:
<% if tracker %>
  <!-- Global site tag (gtag.js) - Google AdWords - Google Analytics 4 -->
  <script async src="https://www.googletagmanager.com/gtag/js?id=<%= tracker %>"></script>
  <script>
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());

    gtag('config', "<%= tracker %>");
  </script>
  <!-- End Global site tag (gtag.js) - Google AdWords -->

  <script type="text/javascript">
    <% events.each do |var| %>
      <% if var.category == "sign_up" %>
        gtag('event', "<%= var.category %>", {
          method: "<%= var.action %>"
        });
      <% else %>
        gtag('event', "<%= var.category %>", {
          action: "<%= var.action %>"
        });
      <% end %>
    <% end %>
  </script>
<% end %>
  1. Inside config/application.rb:
config.middleware.use(Rack::Tracker) do
  handler :google_analytics, { tracker: Rails.application.credentials.google_analytics_4_id, explicit_pageview: false }
end

Call to tracker remains the same, in my case sample call to sign_up is:

tracker do |t|
  t.google_analytics :send, { type: 'event', category: 'sign_up', action: 'Email', label: request_label }
end

I will see if I can cleanup the google_analytics.rb so that changes in erb would be simple to integrate without any if-else condition. But this is the working sample so far I have with GA4

bumi commented 1 year ago

do you want to put this in a PR?

puneetpandey commented 1 year ago

@bumi File: ../rack/tracker/google_analytics/google_analytics.rb need some changes before I raise a PR because all those Ecommerce classes and stuff are no longer required in GA4 or what I can do is to add new google_analytics_4 class and helper to call that function like: t.google_analytics_4 this way both the integrations remain active. Thoughts?

DonSchado commented 1 year ago

yes some type of versioning sounds reasonable :)

ArtemBon commented 1 year ago

@puneetpandey Hi! Have you worked on the PR?

puneetpandey commented 1 year ago

@ArtemBon The work is still in progress and I am yet to write some test cases around it. I am unable to take time out for this, so I appreciate any help you and/or others can provide

puneetpandey commented 1 year ago

Update: I am somehow unable to work on the new changeset but I have come up with another solution for this.

This gem is very flexible and allows me to create my own middleware tracker. So I created one like this:

inside lib folder I created google_analytics4_handler.rb

class GoogleAnalytics4Handler < Rack::Tracker::Handler
  self.allowed_tracker_options = [:cookie_domain, :user_id]

  def initialize(env, options = {})
    super(env, options)
  end

  class Send < OpenStruct
    def initialize(attrs = {})
      attrs.reverse_merge!(type: 'event')
      super
    end

    def write
      ['send', event].to_json.gsub(/\[|\]/, '')
    end

    def event
      { hitType: self.type }.merge(attributes.stringify_values).compact
    end

    def attributes
      Hash[to_h.slice(:category, :action, :label, :value).map { |k,v| [self.type.to_s + k.to_s.capitalize, v] }]
    end
  end

  class Parameter < OpenStruct
    include Rack::Tracker::JavaScriptHelper
    def write
      ['set', self.to_h.to_a].flatten.map { |v| %Q{'#{j(v)}'} }.join ', '
    end
  end

  def tracker
    options[:tracker].respond_to?(:call) ? options[:tracker].call(env) : options[:tracker]
  end

  def pageview_url_script
    options[:pageview_url_script] || 'window.location.pathname + window.location.search'
  end

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

  private

  def tracker_option_key(key)
    key.to_s.camelize(:lower).to_sym
  end

  def tracker_option_value(value)
    value.to_s
  end
end

and created ga4_handler.erb inside lib/templates:

<!-- Global site tag (gtag.js) - Google Analytics 4 -->
<!--
<script async src="https://www.googletagmanager.com/gtag/js?id=<%#= tracker %>"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', "<%#= tracker %>");
</script>
-->
<!-- End Global site tag (gtag.js) - Google Analytics 4 -->
<% if tracker %>
  <script type="text/javascript">
    if(typeof gtag !== "undefined" && typeof gtag === "function") {
      <% events.each do |var| %>
        gtag('event', "<%= var.category %>", {
          method: "<%= var.action %>"
        });
      <% end %>
    } else {
      <% unless Rails.env.development? %>
        console.log("undefined gtag");
      <% end %>
    }
  </script>
<% end %>

I am initialising this in application.rb like this:

config.middleware.use(Rack::Tracker) do
  handler GoogleAnalytics4Handler, { tracker: Rails.application.credentials.ad_google_analytics_4_id }
end

and a call to this is like:

tracker do |t|
  t.google_analytics4_handler :send, { type: 'event', category: 'login', action: "Login Link" , label: nil }
end

P.S.: The reason I commented GA4 base tag is because I have already defined / included this base tag in my layout file. and this was resulting in duplicate GA4 tags as per Google Tag Assistant Legacy extension.