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

Add cookieless A/A test #1951

Merged
merged 1 commit into from
Jan 15, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
116 changes: 116 additions & 0 deletions app/assets/javascripts/modules/cookieless-tracker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
window.GOVUK = window.GOVUK || {}
window.GOVUK.Modules = window.GOVUK.Modules || {};

(function (Modules) {
var CookielessTracker = function (trackingId, fieldsObject) {
var trackerName = fieldsObject.name + '.'

function configureProfile () {
// https://developers.google.com/analytics/devguides/collection/analyticsjs/command-queue-reference#create
sendToGa('create', trackingId, fieldsObject)
}

function anonymizeIp () {
// https://developers.google.com/analytics/devguides/collection/analyticsjs/advanced#anonymizeip
sendToGa(trackerName + 'set', 'anonymizeIp', true)
}

function disableAdFeatures () {
// https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#allowAdFeatures
sendToGa(trackerName + 'set', 'allowAdFeatures', false)
}

function stripTitlePII () {
sendToGa(trackerName + 'set', 'title', '')
}

function stripLocationPII () {
sendToGa(trackerName + 'set', 'location', '')
}

function load () {
/* eslint-disable */
(function (i, s, o, g, r, a, m) { i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments) }, i[r].l = 1 * new Date(); a = s.createElement(o),
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga')
/* eslint-enable */
}

// Support legacy cookieDomain param
if (typeof fieldsObject === 'string') {
fieldsObject = { cookieDomain: fieldsObject }
}

load()
configureProfile()
anonymizeIp()
disableAdFeatures()
stripTitlePII()
stripLocationPII()
}

CookielessTracker.load = function () {
/* eslint-disable */
(function (i, s, o, g, r, a, m) { i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments) }, i[r].l = 1 * new Date(); a = s.createElement(o),
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga')
/* eslint-enable */
}

// https://developers.google.com/analytics/devguides/collection/analyticsjs/events
CookielessTracker.prototype.trackEvent = function (category, action, options) {
options = options || {}
var value
var trackerName = ''
var evt = {
hitType: 'event',
eventCategory: category,
eventAction: action
}

// Label is optional
if (typeof options.label === 'string') {
evt.eventLabel = options.label
delete options.label
}

// Value is optional, but when used must be an
// integer, otherwise the event will be invalid
// and not logged
if (options.value || options.value === 0) {
value = parseInt(options.value, 10)
if (typeof value === 'number' && !isNaN(value)) {
options.eventValue = value
}
delete options.value
}

// trackerName is optional
if (typeof options.trackerName === 'string') {
trackerName = options.trackerName + '.'
delete options.trackerName
}

// Prevents an event from affecting bounce rate
// https://developers.google.com/analytics/devguides/collection/analyticsjs/events#implementation
if (options.nonInteraction) {
options.nonInteraction = 1
}

if (typeof options === 'object') {
$.extend(evt, options)
}

sendToGa(trackerName + 'send', evt)
}

function sendToGa () {
if (typeof window.ga === 'function') {
window.ga.apply(window, arguments)
}
}

Modules.CookielessTracker = CookielessTracker
})(window.GOVUK.Modules)
32 changes: 32 additions & 0 deletions app/assets/javascripts/modules/track-variant.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
window.GOVUK.Modules = window.GOVUK.Modules || {};

(function (Modules) {
'use strict'

Modules.TrackVariant = function () {
this.start = function ($element) {
var element = $element[0]

if (window.GOVUK.cookie('cookies_preferences_set') !== 'true') {
var variant = element.getAttribute('content')

if (variant === undefined) {
return
}

var cookielessTracker = new GOVUK.Modules.CookielessTracker('UA-26179049-29', {
name: 'CookielessTracker',
storage: 'none',
clientId: '0'
})

cookielessTracker.trackEvent('cookieless', 'hit', {
trackerName: 'CookielessTracker',
label: variant,
javaEnabled: false,
language: ''
})
}
}
}
})(window.GOVUK.Modules)
2 changes: 2 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ class ApplicationController < ActionController::Base
# For APIs, you may want to use :null_session instead.
protect_from_forgery except: :service_sign_in_options

include CookielessTestable

if ENV["BASIC_AUTH_USERNAME"]
http_basic_authenticate_with(
name: ENV.fetch("BASIC_AUTH_USERNAME"),
Expand Down
31 changes: 31 additions & 0 deletions app/controllers/concerns/cookieless_testable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module CookielessTestable
extend ActiveSupport::Concern

CUSTOM_DIMENSION = 49

def self.included(base)
base.helper_method(
:cookieless_variant,
)
base.after_action :set_test_response_header
end

def cookieless_variant
@cookieless_variant ||= cookieless_test.requested_variant(request.headers)
end

private

def cookieless_test
@cookieless_test ||= GovukAbTesting::AbTest.new(
"CookielessAATest",
dimension: CUSTOM_DIMENSION,
allowed_variants: %w[A B Z],
control_variant: "Z",
)
end

def set_test_response_header
cookieless_variant.configure_response(response)
end
end
5 changes: 5 additions & 0 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
<% if @content_item.description %>
<meta name="description" content="<%= strip_tags(@content_item.description) %>" />
<% end %>

<%= cookieless_variant.analytics_meta_tag.html_safe %>
<% unless cookieless_variant.variant?('Z') %>
<meta name="Cookieless-Variant" content="<%= cookieless_variant.variant_name %>" data-module="track-variant">
<% end %>

<%= yield :extra_head_content %>
</head>
Expand Down
61 changes: 61 additions & 0 deletions spec/javascripts/track-variant-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
describe('Test variant tracker', function () {
'use strict'

var tracker,
element,
FakeCookielessTracker,
gaSpy

beforeEach(function () {
GOVUK.cookie('cookies_preferences_set', null)
gaSpy = jasmine.createSpyObj('initGa', ['send'])

FakeCookielessTracker = function (trackingId, fieldsObject) {}
FakeCookielessTracker.prototype.trackEvent = function (category, action, options) {
gaSpy.send(category, action, options)
}

GOVUK.Modules.CookielessTracker = FakeCookielessTracker

tracker = new GOVUK.Modules.TrackVariant()
})

afterEach(function () {
GOVUK.Modules.CookielessTracker = null
})

it('tracks A variant', function () {
element = $('<meta name="Cookieless-Variant" content="A" data-module="track-variant">')

tracker.start(element)

expect(gaSpy.send).toHaveBeenCalledWith('cookieless', 'hit', {
trackerName: 'CookielessTracker',
label: 'A',
javaEnabled: false,
language: ''
})
})

it('tracks B variant', function () {
element = $('<meta name="Cookieless-Variant" content="B" data-module="track-variant">')

tracker.start(element)

expect(gaSpy.send).toHaveBeenCalledWith('cookieless', 'hit', {
trackerName: 'CookielessTracker',
label: 'B',
javaEnabled: false,
language: ''
})
})

it('does not track variant if cookie is set', function () {
GOVUK.cookie('cookies_preferences_set', true)
element = $('<meta name="Cookieless-Variant" content="Z" data-module="track-variant">')

tracker.start(element)

expect(gaSpy.send).not.toHaveBeenCalled()
})
})
20 changes: 20 additions & 0 deletions test/controllers/content_items_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,26 @@ class ContentItemsControllerTest < ActionController::TestCase
assert_select ".gem-c-contextual-footer", false
end

%w[A B].each do |test_variant|
test "record cookieless hit when in variant #{test_variant}" do
with_variant CookielessAATest: test_variant.to_s do
content_item = content_store_has_schema_example("case_study", "case_study")

get :show, params: { path: path_for(content_item) }
assert_select "meta[data-module=track-variant][content=#{test_variant}]"
end
end
end

test "not record cookieless hit when in variant Z" do
with_variant CookielessAATest: "Z" do
content_item = content_store_has_schema_example("case_study", "case_study")

get :show, params: { path: path_for(content_item) }
assert_select "meta[data-module=track-variant]", false
end
end

def path_for(content_item, locale = nil)
base_path = content_item["base_path"].sub(/^\//, "")
base_path.gsub!(/\.#{locale}$/, "") if locale
Expand Down