Skip to content

Commit

Permalink
Track external link visits
Browse files Browse the repository at this point in the history
  • Loading branch information
fbacall committed Jun 20, 2023
1 parent da5b1ea commit ca1b7ce
Show file tree
Hide file tree
Showing 16 changed files with 283 additions and 28 deletions.
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ gem 'reverse_markdown'
# eventbrite api
gem 'eventbrite_sdk'

gem 'ahoy_matey'


source 'https://rails-assets.org' do
gem 'rails-assets-clipboard', '~> 1.5.12'
gem 'rails-assets-devbridge-autocomplete', '~> 1.4.9'
Expand Down
7 changes: 7 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ GEM
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
aes_key_wrap (1.1.0)
ahoy_matey (4.2.1)
activesupport (>= 5.2)
device_detector
safely_block (>= 0.2.1)
amazing_print (1.4.0)
ast (2.4.2)
attr_required (1.0.1)
Expand Down Expand Up @@ -176,6 +180,7 @@ GEM
crass (1.0.6)
date (3.3.3)
debug_inspector (1.1.0)
device_detector (1.1.0)
devise (4.8.0)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
Expand Down Expand Up @@ -611,6 +616,7 @@ GEM
parser (>= 3.0.1.1)
ruby-progressbar (1.11.0)
ruby2_keywords (0.0.5)
safely_block (0.4.0)
sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
Expand Down Expand Up @@ -771,6 +777,7 @@ PLATFORMS
DEPENDENCIES
active_model_serializers (~> 0.10.13)
activerecord-session_store
ahoy_matey
auto_strip_attributes (~> 2.0)
better_errors
binding_of_caller
Expand Down
21 changes: 3 additions & 18 deletions app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
//= require devbridge-autocomplete
//= require clipboard
//= require ardc_vocab_widget_v2
//= require ahoy
//= require_tree ./templates
//= require_tree .
//= require_self
Expand Down Expand Up @@ -210,6 +211,8 @@ document.addEventListener("turbolinks:load", function() {

Biotools.init();

Tracking.init();

$('.tess-expandable').each(function () {
var limit = this.dataset.heightLimit || 300;

Expand Down Expand Up @@ -306,21 +309,3 @@ $(document).on('click', '[href="#activity_log"]', function () {

return false;
});

/**
* Function that registers a click on an outbound link in Analytics.
* This function takes a valid URL string as an argument, and uses that URL string
* as the event label. Setting the transport method to 'beacon' lets the hit be sent
* using 'navigator.sendBeacon' in browser that support it.
*/
var getOutboundLink = function(url) {
if (!window.captureClicks) {
return;
}
gtag('event', 'click', {
'event_category': 'outbound',
'event_label': url,
'transport_type': 'beacon',
'event_callback': function() {} // Not needed
});
}
34 changes: 34 additions & 0 deletions app/assets/javascripts/tracking.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
ahoy.configure({
trackVisits: false // Disable automatic client-side visit tracking
});

var Tracking = {
init: function () {
$('[data-trackable]').click(Tracking.track);
},

/**
* Function that registers a click on an outbound link in Analytics.
* This function takes a valid URL string as an argument, and uses that URL string
* as the event label. Setting the transport method to 'beacon' lets the hit be sent
* using 'navigator.sendBeacon' in browser that support it.
*/
track: function () {
if (this.dataset.trackableType && this.dataset.trackableId) {
ahoy.track('Visited Link', {
url: this.href,
trackable_type: this.dataset.trackableType,
trackable_id: parseInt(this.dataset.trackableId)
});
}
if (window.gtag) {
gtag('event', 'click', {
'event_category': 'outbound',
'event_label': url,
'transport_type': 'beacon',
'event_callback': function() {} // Not needed
});
}
return true;
}
}
18 changes: 16 additions & 2 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -461,8 +461,22 @@ def star_button(resource)
end

def external_link_button(text, url, options = {})
options.reverse_merge!({ rel: 'noopener', target: '_blank', class: 'btn btn-primary' })
link_to((text + ' <i class="icon icon-md arrow-top-right-white-icon"></i>').html_safe, url, options)
options.reverse_merge!({ class: 'btn btn-primary' })
text = (text + ' <i class="icon icon-md arrow-top-right-white-icon"></i>').html_safe
external_link(text, url, options)
end

def external_link(text, url, options = {})
track = options.delete(:track)
if track && cookie_consent.allow_tracking?
options.reverse_merge!('data-trackable' => true)
if track.is_a?(ApplicationRecord)
options.reverse_merge!('data-trackable-type' => track.class.name)
options.reverse_merge!('data-trackable-id' => track.id)
end
end
options.reverse_merge!({ rel: 'noopener', target: '_blank' })
link_to(text, url, options)
end

def edit_button(resource, url: nil, text: nil)
Expand Down
8 changes: 8 additions & 0 deletions app/models/ahoy/event.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class Ahoy::Event < ApplicationRecord
include Ahoy::QueryMethods

self.table_name = "ahoy_events"

belongs_to :visit
belongs_to :user, optional: true
end
6 changes: 6 additions & 0 deletions app/models/ahoy/visit.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class Ahoy::Visit < ApplicationRecord
self.table_name = "ahoy_visits"

has_many :events, class_name: "Ahoy::Event"
belongs_to :user, optional: true
end
2 changes: 1 addition & 1 deletion app/views/events/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

<div class="content-actions">
<div class="content-viewer-actions">
<%= external_link_button 'View event', @event.url, onclick: "getOutboundLink('#{@event.url}'); return true;" %>
<%= external_link_button 'View event', @event.url, track: @event %>
<%= render partial: 'events/add_to_calendar_button', locals: { event: @event } if @event.start && @event.end %>
<%= render partial: 'common/identifiers_dot_org_button', locals: { resource: @event } if TeSS::Config.identifiers_prefix %>
<%= star_button(@event) if user_signed_in? %>
Expand Down
1 change: 0 additions & 1 deletion app/views/layouts/_google_analytics.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@
gtag('js', new Date());

gtag('config', '<%= Rails.application.secrets.google_analytics_code %>');
window.captureClicks = true;
</script>
2 changes: 1 addition & 1 deletion app/views/materials/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

<div class="content-actions">
<div class="content-viewer-actions">
<%= external_link_button 'View material', @material.url, onclick: "getOutboundLink('#{@material.url}'); return true;" %>
<%= external_link_button 'View material', @material.url, track: @material %>
<%= render partial: 'common/identifiers_dot_org_button', locals: { resource: @material } if TeSS::Config.identifiers_prefix %>
<%= star_button(@material) if user_signed_in? %>
</div>
Expand Down
6 changes: 2 additions & 4 deletions app/views/trainers/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@
<!-- Field: website -->
<% if @trainer.website.present? %>
<div class="url-wrap">
<%= link_to @trainer.website, @trainer.website, class: 'h5', target: '_blank', rel: 'noopener',
onclick: "getOutboundLink('#{@trainer.website}'); return true;" %>
<%= external_link @trainer.website, @trainer.website, class: 'h5', target: '_blank', rel: 'noopener', track: true %>
</div>
<% end %>

<!-- Field: orcid -->
<% if @trainer.orcid.present? %>
<div class="url-wrap">
<%= link_to @trainer.orcid, @trainer.orcid, class: 'h5', target: '_blank', rel: 'noopener',
onclick: "getOutboundLink('#{@trainer.orcid}'); return true;" %>
<%= external_link @trainer.orcid, @trainer.orcid, class: 'h5', target: '_blank', rel: 'noopener', track: true %>
</div>
<% end %>

Expand Down
17 changes: 17 additions & 0 deletions config/initializers/ahoy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Ahoy::Store < Ahoy::DatabaseStore
end

# set to true for JavaScript tracking
Ahoy.api = true

# set to true for geocoding (and add the geocoder gem to your Gemfile)
# we recommend configuring local geocoding as well
# see https://github.com/ankane/ahoy#geocoding
Ahoy.geocode = false

# GDPR compliance
Ahoy.mask_ips = true
Ahoy.cookies = false

# Disable automatic server-side visit tracking
Ahoy.server_side_visits = :when_needed
61 changes: 61 additions & 0 deletions db/migrate/20230617092354_create_ahoy_visits_and_events.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
class CreateAhoyVisitsAndEvents < ActiveRecord::Migration[6.1]
def change
create_table :ahoy_visits do |t|
t.string :visit_token
t.string :visitor_token

# the rest are recommended but optional
# simply remove any you don't want

# user
t.references :user

# standard
t.string :ip
t.text :user_agent
t.text :referrer
t.string :referring_domain
t.text :landing_page

# technology
t.string :browser
t.string :os
t.string :device_type

# location
t.string :country
t.string :region
t.string :city
t.float :latitude
t.float :longitude

# utm parameters
t.string :utm_source
t.string :utm_medium
t.string :utm_term
t.string :utm_content
t.string :utm_campaign

# native apps
t.string :app_version
t.string :os_version
t.string :platform

t.datetime :started_at
end

add_index :ahoy_visits, :visit_token, unique: true

create_table :ahoy_events do |t|
t.references :visit
t.references :user

t.string :name
t.jsonb :properties
t.datetime :time
end

add_index :ahoy_events, [:name, :time]
add_index :ahoy_events, :properties, using: :gin, opclass: :jsonb_path_ops
end
end
44 changes: 43 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2023_03_01_111905) do
ActiveRecord::Schema.define(version: 2023_06_17_092354) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand All @@ -32,6 +32,48 @@
t.index ["trackable_id", "trackable_type"], name: "index_activities_on_trackable_id_and_trackable_type"
end

create_table "ahoy_events", force: :cascade do |t|
t.bigint "visit_id"
t.bigint "user_id"
t.string "name"
t.jsonb "properties"
t.datetime "time"
t.index ["name", "time"], name: "index_ahoy_events_on_name_and_time"
t.index ["properties"], name: "index_ahoy_events_on_properties", opclass: :jsonb_path_ops, using: :gin
t.index ["user_id"], name: "index_ahoy_events_on_user_id"
t.index ["visit_id"], name: "index_ahoy_events_on_visit_id"
end

create_table "ahoy_visits", force: :cascade do |t|
t.string "visit_token"
t.string "visitor_token"
t.bigint "user_id"
t.string "ip"
t.text "user_agent"
t.text "referrer"
t.string "referring_domain"
t.text "landing_page"
t.string "browser"
t.string "os"
t.string "device_type"
t.string "country"
t.string "region"
t.string "city"
t.float "latitude"
t.float "longitude"
t.string "utm_source"
t.string "utm_medium"
t.string "utm_term"
t.string "utm_content"
t.string "utm_campaign"
t.string "app_version"
t.string "os_version"
t.string "platform"
t.datetime "started_at"
t.index ["user_id"], name: "index_ahoy_visits_on_user_id"
t.index ["visit_token"], name: "index_ahoy_visits_on_visit_token", unique: true
end

create_table "autocomplete_suggestions", force: :cascade do |t|
t.string "field"
t.string "value"
Expand Down
Loading

0 comments on commit ca1b7ce

Please sign in to comment.