Changes snowplow to use cookies for sessions

This also restructures how and where the configuration for
Snowplow lives.
This commit is contained in:
Jeremy Jackson 2019-08-28 06:52:14 +00:00 committed by Jan Provaznik
parent 07c67200ff
commit 1509768335
8 changed files with 206 additions and 107 deletions

View File

@ -35,6 +35,7 @@ import initPerformanceBar from './performance_bar';
import initSearchAutocomplete from './search_autocomplete';
import GlFieldErrors from './gl_field_errors';
import initUserPopovers from './user_popovers';
import { initUserTracking } from './tracking';
import { __ } from './locale';
import 'ee_else_ce/main_ee';
@ -94,6 +95,7 @@ function deferredInitialisation() {
initLogoAnimation();
initUsagePingConsent();
initUserPopovers();
initUserTracking();
if (document.querySelector('.search')) initSearchAutocomplete();

View File

@ -1,5 +1,23 @@
import $ from 'jquery';
const DEFAULT_SNOWPLOW_OPTIONS = {
namespace: 'gl',
hostname: window.location.hostname,
cookieDomain: window.location.hostname,
appId: '',
userFingerprint: false,
respectDoNotTrack: true,
forceSecureTracker: true,
eventMethod: 'post',
contexts: { webPage: true },
// Page tracking tracks a single event when the page loads.
pageTrackingEnabled: false,
// Activity tracking tracks when a user is still interacting with the page.
// Events like scrolling and mouse movements are used to determine if the
// user has the tab active and is still actively engaging.
activityTrackingEnabled: false,
};
const extractData = (el, opts = {}) => {
const { trackEvent, trackLabel = '', trackProperty = '' } = el.dataset;
let trackValue = el.dataset.trackValue || el.value || '';
@ -71,3 +89,13 @@ export default class Tracking {
};
}
}
export function initUserTracking() {
if (!Tracking.enabled()) return;
const opts = Object.assign({}, DEFAULT_SNOWPLOW_OPTIONS, window.snowplowOptions);
window.snowplow('newTracker', opts.namespace, opts.hostname, opts);
if (opts.activityTrackingEnabled) window.snowplow('enableActivityTracking', 30, 30);
if (opts.pageTrackingEnabled) window.snowplow('trackPageView'); // must be after enableActivityTracking
}

View File

@ -7,23 +7,4 @@
};p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","#{asset_url('snowplow/sp.js')}","snowplow"));
window.snowplow('newTracker', '#{Gitlab::SnowplowTracker::NAMESPACE}', '#{Gitlab::CurrentSettings.snowplow_collector_hostname}', {
appId: '#{Gitlab::CurrentSettings.snowplow_site_id}',
cookieDomain: '#{Gitlab::CurrentSettings.snowplow_cookie_domain}',
userFingerprint: false,
respectDoNotTrack: true,
forceSecureTracker: true,
post: true,
contexts: { webPage: true },
stateStorageStrategy: "localStorage"
});
window.snowplow('enableActivityTracking', 30, 30);
window.snowplow('trackPageView');
- return unless Feature.enabled?(:additional_snowplow_tracking, @group)
= javascript_tag nonce: true do
:plain
window.snowplow('enableFormTracking');
window.snowplow('enableLinkClickTracking');
window.snowplowOptions = #{Gitlab::Tracking.snowplow_options(@group).to_json}

View File

@ -1,35 +0,0 @@
# frozen_string_literal: true
require 'snowplow-tracker'
module Gitlab
module SnowplowTracker
NAMESPACE = 'cf'
class << self
def track_event(category, action, label: nil, property: nil, value: nil, context: nil)
tracker&.track_struct_event(category, action, label, property, value, context, Time.now.to_i)
end
private
def tracker
return unless enabled?
@tracker ||= ::SnowplowTracker::Tracker.new(emitter, subject, NAMESPACE, Gitlab::CurrentSettings.snowplow_site_id)
end
def subject
::SnowplowTracker::Subject.new
end
def emitter
::SnowplowTracker::Emitter.new(Gitlab::CurrentSettings.snowplow_collector_hostname)
end
def enabled?
Gitlab::CurrentSettings.snowplow_enabled?
end
end
end
end

44
lib/gitlab/tracking.rb Normal file
View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
require 'snowplow-tracker'
module Gitlab
module Tracking
SNOWPLOW_NAMESPACE = 'gl'
class << self
def enabled?
Gitlab::CurrentSettings.snowplow_enabled?
end
def event(category, action, label: nil, property: nil, value: nil, context: nil)
return unless enabled?
snowplow.track_struct_event(category, action, label, property, value, context, Time.now.to_i)
end
def snowplow_options(group)
additional_features = Feature.enabled?(:additional_snowplow_tracking, group)
{
namespace: SNOWPLOW_NAMESPACE,
hostname: Gitlab::CurrentSettings.snowplow_collector_hostname,
cookie_domain: Gitlab::CurrentSettings.snowplow_cookie_domain,
app_id: Gitlab::CurrentSettings.snowplow_site_id,
page_tracking_enabled: additional_features,
activity_tracking_enabled: additional_features
}.transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
end
private
def snowplow
@snowplow ||= SnowplowTracker::Tracker.new(
SnowplowTracker::Emitter.new(Gitlab::CurrentSettings.snowplow_collector_hostname),
SnowplowTracker::Subject.new,
SNOWPLOW_NAMESPACE,
Gitlab::CurrentSettings.snowplow_site_id
)
end
end
end
end

View File

@ -1,20 +1,56 @@
import $ from 'jquery';
import { setHTMLFixture } from './helpers/fixtures';
import Tracking from '~/tracking';
import Tracking, { initUserTracking } from '~/tracking';
describe('Tracking', () => {
let snowplowSpy;
beforeEach(() => {
window.snowplow = window.snowplow || (() => {});
window.snowplowOptions = {
namespace: '_namespace_',
hostname: 'app.gitfoo.com',
cookieDomain: '.gitfoo.com',
};
snowplowSpy = jest.spyOn(window, 'snowplow');
});
describe('initUserTracking', () => {
it('calls through to get a new tracker with the expected options', () => {
initUserTracking();
expect(snowplowSpy).toHaveBeenCalledWith('newTracker', '_namespace_', 'app.gitfoo.com', {
namespace: '_namespace_',
hostname: 'app.gitfoo.com',
cookieDomain: '.gitfoo.com',
appId: '',
userFingerprint: false,
respectDoNotTrack: true,
forceSecureTracker: true,
eventMethod: 'post',
contexts: { webPage: true },
activityTrackingEnabled: false,
pageTrackingEnabled: false,
});
});
it('should activate features based on what has been enabled', () => {
initUserTracking();
expect(snowplowSpy).not.toHaveBeenCalledWith('enableActivityTracking', 30, 30);
expect(snowplowSpy).not.toHaveBeenCalledWith('trackPageView');
window.snowplowOptions = Object.assign({}, window.snowplowOptions, {
activityTrackingEnabled: true,
pageTrackingEnabled: true,
});
initUserTracking();
expect(snowplowSpy).toHaveBeenCalledWith('enableActivityTracking', 30, 30);
expect(snowplowSpy).toHaveBeenCalledWith('trackPageView');
});
});
describe('.event', () => {
let snowplowSpy = null;
beforeEach(() => {
snowplowSpy = jest.spyOn(window, 'snowplow');
});
afterEach(() => {
window.doNotTrack = undefined;
navigator.doNotTrack = undefined;

View File

@ -1,45 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::SnowplowTracker do
let(:timestamp) { Time.utc(2017, 3, 22) }
around do |example|
Timecop.freeze(timestamp) { example.run }
end
subject { described_class.track_event('epics', 'action', property: 'what', value: 'doit') }
context '.track_event' do
context 'when Snowplow tracker is disabled' do
it 'does not track the event' do
expect(SnowplowTracker::Tracker).not_to receive(:new)
subject
end
end
context 'when Snowplow tracker is enabled' do
before do
stub_application_setting(snowplow_enabled: true)
stub_application_setting(snowplow_site_id: 'awesome gitlab')
stub_application_setting(snowplow_collector_hostname: 'url.com')
end
it 'tracks the event' do
tracker = double
expect(::SnowplowTracker::Tracker).to receive(:new)
.with(
an_instance_of(::SnowplowTracker::Emitter),
an_instance_of(::SnowplowTracker::Subject),
'cf', 'awesome gitlab'
).and_return(tracker)
expect(tracker).to receive(:track_struct_event)
.with('epics', 'action', nil, 'what', 'doit', nil, timestamp.to_i)
subject
end
end
end
end

View File

@ -0,0 +1,88 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Tracking do
let(:timestamp) { Time.utc(2017, 3, 22) }
before do
stub_application_setting(snowplow_enabled: true)
stub_application_setting(snowplow_collector_hostname: 'gitfoo.com')
stub_application_setting(snowplow_cookie_domain: '.gitfoo.com')
stub_application_setting(snowplow_site_id: '_abc123_')
end
describe '.snowplow_options' do
subject(&method(:described_class))
it 'returns useful client options' do
expect(subject.snowplow_options(nil)).to eq(
namespace: 'gl',
hostname: 'gitfoo.com',
cookieDomain: '.gitfoo.com',
appId: '_abc123_',
pageTrackingEnabled: true,
activityTrackingEnabled: true
)
end
it 'enables features using feature flags' do
stub_feature_flags(additional_snowplow_tracking: true)
allow(Feature).to receive(:enabled?).with(
:additional_snowplow_tracking,
'_group_'
).and_return(false)
expect(subject.snowplow_options('_group_')).to include(
pageTrackingEnabled: false,
activityTrackingEnabled: false
)
end
end
describe '.event' do
subject(&method(:described_class))
around do |example|
Timecop.freeze(timestamp) { example.run }
end
it 'can track events' do
tracker = double
expect(SnowplowTracker::Emitter).to receive(:new).with(
'gitfoo.com'
).and_return('_emitter_')
expect(SnowplowTracker::Tracker).to receive(:new).with(
'_emitter_',
an_instance_of(SnowplowTracker::Subject),
'gl',
'_abc123_'
).and_return(tracker)
expect(tracker).to receive(:track_struct_event).with(
'category',
'action',
'_label_',
'_property_',
'_value_',
'_context_',
timestamp.to_i
)
subject.event('category', 'action',
label: '_label_',
property: '_property_',
value: '_value_',
context: '_context_'
)
end
it 'does not track when not enabled' do
stub_application_setting(snowplow_enabled: false)
expect(SnowplowTracker::Tracker).not_to receive(:new)
subject.event('epics', 'action', property: 'what', value: 'doit')
end
end
end