Commit b4fecd74 authored by Jeremy Jackson's avatar Jeremy Jackson Committed by Paul Slaughter

Updates current tracking code to use Tracking.js

This migrates away from the stats library and starts to implement a new
Tracking interface.
parent e1f8093d
import stats from 'ee/stats';
import Tracking from '~/tracking';
export default {
methods: {
trackUninstallButtonClick: application => {
stats.trackEvent('k8s_cluster', 'uninstall', {
Tracking.event('k8s_cluster', 'uninstall', {
label: application,
});
},
......
import Stats from 'ee/stats';
import Tracking from '~/tracking';
import { mergeUrlParams } from '~/lib/utils/url_utility';
const TRACKING_CATEGORY = 'navbar_top';
const NAVSOURCE_KEY = 'nav_source';
const NAVSOURCE_VALUE = 'navbar';
/**
* intercepts clicks on navbar links
* and adds the 'nav_source=navbar' query parameter
*/
const appendLinkParam = e => {
const target = e.currentTarget;
// get closest link in case the target is a wrapping DOM node
const link = target.tagName === 'A' ? target : target.closest('a');
if (link && link.href && link.href.indexOf(`${NAVSOURCE_KEY}=${NAVSOURCE_VALUE}`) === -1) {
const url = mergeUrlParams({ [NAVSOURCE_KEY]: NAVSOURCE_VALUE }, link.href);
link.setAttribute('href', url);
}
};
export default function trackNavbarEvents() {
const container = '.navbar-gitlab';
const category = 'navbar_top';
if (!Tracking.enabled()) return;
const navbar = document.querySelector('.navbar-gitlab');
if (!navbar) return;
new Tracking(TRACKING_CATEGORY).bind(navbar);
// track search inputs within frequent-items component
navbar.querySelectorAll(`.frequent-items-dropdown-container input`).forEach(el => {
el.addEventListener('click', e => {
const parentDropdown = e.currentTarget.closest('li.dropdown');
/**
* intercepts clicks on navbar links
* and adds the 'nav_source=navbar' query parameter
*/
const appendLinkParam = e => {
const NAVSOURCE_KEY = 'nav_source';
const NAVSOURCE_VALUE = 'navbar';
const target = e.target || e.srcElement;
// get closest link in case the target is a wrapping DOM node
const link = target.tagName === 'A' ? target : target.closest('a');
if (link && link.href && link.href.indexOf(`${NAVSOURCE_KEY}=${NAVSOURCE_VALUE}`) === -1) {
const url = mergeUrlParams({ [NAVSOURCE_KEY]: NAVSOURCE_VALUE }, link.href);
link.setAttribute('href', url);
}
};
Stats.bindTrackableContainer(container, category);
if (Stats.snowplowEnabled) {
// track search inputs within frequent-items component
document
.querySelectorAll(`${container} .frequent-items-dropdown-container input`)
.forEach(element => {
element.addEventListener('click', e => {
const target = e.currentTarget;
const parentDropdown = target.closest('li.dropdown');
const label = `${parentDropdown.getAttribute('data-track-label')}_search`;
Stats.trackEvent(category, 'activate_form_input', { label, property: '', value: '' });
});
Tracking.event(TRACKING_CATEGORY, 'activate_form_input', {
label: `${parentDropdown.getAttribute('data-track-label')}_search`,
property: '',
value: '',
});
});
});
if (navbar) {
navbar.addEventListener('click', appendLinkParam);
navbar.addEventListener('contextmenu', appendLinkParam);
}
if (navbar) {
navbar.addEventListener('click', appendLinkParam);
navbar.addEventListener('contextmenu', appendLinkParam);
}
}
import Stats from 'ee/stats';
import Tracking from '~/tracking';
export default () => {
document.querySelector('.main-notes-list').addEventListener('click', event => {
......@@ -7,7 +7,7 @@ export default () => {
);
if (isReplyButtonClick) {
Stats.trackEvent(document.body.dataset.page, 'click_button', {
Tracking.event(document.body.dataset.page, 'click_button', {
label: 'reply_comment_button',
property: '',
value: '',
......@@ -15,5 +15,5 @@ export default () => {
}
});
Stats.bindTrackableContainer('.js-main-target-form');
new Tracking().bind();
};
......@@ -2,7 +2,7 @@
import _ from 'underscore';
import { mapState, mapActions, mapGetters } from 'vuex';
import { redirectTo } from '~/lib/utils/url_utility';
import Stats from 'ee/stats';
import Tracking from '~/tracking';
import OnboardingHelper from './onboarding_helper.vue';
import actionPopoverUtils from './../action_popover_utils';
import eventHub from '../event_hub';
......@@ -111,7 +111,7 @@ export default {
},
handleRestartStep() {
this.showExitTourContent(false);
Stats.trackEvent(TRACKING_CATEGORY, 'click_link', {
Tracking.event(TRACKING_CATEGORY, 'click_link', {
label: this.getTrackingLabel(),
property: 'restart_this_step',
});
......@@ -122,7 +122,7 @@ export default {
const { selector } = this.actionPopover;
const popoverEl = selector ? document.querySelector(selector) : null;
if (popoverEl) {
Stats.trackEvent(TRACKING_CATEGORY, 'click_link', {
Tracking.event(TRACKING_CATEGORY, 'click_link', {
label: this.getTrackingLabel(),
property: 'skip_this_step',
});
......@@ -177,7 +177,7 @@ export default {
return;
}
Stats.trackEvent(TRACKING_CATEGORY, 'click_button', {
Tracking.event(TRACKING_CATEGORY, 'click_button', {
label: this.getTrackingLabel(),
property: 'got_it',
});
......@@ -185,7 +185,7 @@ export default {
this.showActionPopover();
},
handleShowExitTourContent(showExitTour) {
Stats.trackEvent(TRACKING_CATEGORY, 'click_link', {
Tracking.event(TRACKING_CATEGORY, 'click_link', {
label: this.getTrackingLabel(),
property: 'exit_learn_gitlab',
});
......
import '~/pages/projects/issues/index/index';
import Stats from 'ee/stats';
import Tracking from '~/tracking';
document.addEventListener('DOMContentLoaded', () => {
Stats.bindTrackableContainer('.issues-export-modal');
Stats.bindTrackableContainer('.issues-import-modal');
new Tracking().bind();
});
import '~/pages/projects/new/index';
import initCustomProjectTemplates from 'ee/projects/custom_project_templates';
import bindTrackEvents from 'ee/projects/track_project_new';
import Tracking from '~/tracking';
import { bindOnboardingEvents } from 'ee/onboarding/new_project';
document.addEventListener('DOMContentLoaded', () => {
initCustomProjectTemplates();
bindTrackEvents('.js-toggle-container');
new Tracking().bind();
bindOnboardingEvents(document.getElementById('new_project'));
});
import Stats from 'ee/stats';
import Tracking from '~/tracking';
export default () => {
Stats.bindTrackableContainer('#signin-container');
const container = document.getElementById('#signin-container');
new Tracking().bind(container);
};
import Stats from 'ee/stats';
const bindTrackEvents = container => {
Stats.bindTrackableContainer(container);
};
export default bindTrackEvents;
import Stats from 'ee/stats';
import Tracking from '~/tracking';
import * as types from './mutation_types';
export const setFilter = ({ commit }, payload) => {
commit(types.SET_FILTER, payload);
Stats.trackEvent(document.body.dataset.page, 'set_filter', {
Tracking.event(document.body.dataset.page, 'set_filter', {
label: payload.filterId,
value: payload.optionId,
});
......
import $ from 'jquery';
const snowplowEnabled = () => typeof window.snowplow === 'function';
const trackEvent = (
category,
eventName,
additionalData = { label: '', property: '', value: '' },
) => {
if (!snowplowEnabled()) {
return;
}
if (!category || !eventName) {
return;
}
const { label, property, value } = additionalData;
try {
window.snowplow('trackStructEvent', category, eventName, label, property, value);
} catch (e) {
// do nothing
}
};
const isSelect2 = element => element.classList.contains('select2');
const isBsDropdown = element => {
const hasDropdownClass = element.classList.contains('dropdown');
const dropdownToggle = element.querySelector('[data-toggle="dropdown"]');
return hasDropdownClass && dropdownToggle !== null;
};
const bindTrackableContainer = (container = '', category = document.body.dataset.page) => {
if (!snowplowEnabled()) {
return;
}
const clickHandler = e => {
const target = e.currentTarget;
const label = target.getAttribute('data-track-label');
const property = target.getAttribute('data-track-property') || '';
const eventName = target.getAttribute('data-track-event');
let value = target.value || '';
// overrides value for checkboxes
if (target.type === 'checkbox') {
value = target.checked;
}
// overrides value if data-track_value is set
if (
typeof target.getAttribute('data-track-value') !== 'undefined' &&
target.getAttribute('data-track-value') !== null
) {
value = target.getAttribute('data-track-value');
}
trackEvent(category, eventName, { label, property, value });
};
const trackableElements = document.querySelectorAll(`${container} [data-track-label]`);
trackableElements.forEach(element => {
if (!isSelect2(element) && !isBsDropdown(element)) {
element.addEventListener('click', e => clickHandler(e));
}
});
// jquery required for select2 events
// see: https://github.com/select2/select2/issues/4686#issuecomment-264747428
$(`${container} .select2[data-track-label]`).on('click', e => clickHandler(e));
const dropdownHandler = (e, open = true) => {
const target = e.currentTarget;
const property = target.getAttribute('data-track-property') || '';
const eventName = target.getAttribute('data-track-event');
const value = target.value || '';
const label = target.getAttribute('data-track-label') + (open ? '_open' : '_close');
trackEvent(category, eventName, { label, property, value });
};
// bootstrap dropdowns
$(`${container} [data-track-label][data-track-event="click_dropdown"]`).on(
'show.bs.dropdown',
e => dropdownHandler(e),
);
$(`${container} [data-track-label][data-track-event="click_dropdown"]`).on(
'hide.bs.dropdown',
e => dropdownHandler(e, false),
);
};
export default {
snowplowEnabled,
trackEvent,
bindTrackableContainer,
};
<script>
import { GlButton } from '@gitlab/ui';
import Stats from 'ee/stats';
import Tracking from '~/tracking';
import { s__ } from '~/locale';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
......@@ -31,11 +31,11 @@ export default {
},
methods: {
addCommentAndDismiss() {
Stats.trackEvent(document.body.dataset.page, 'click_add_comment_and_dismiss');
Tracking.event(document.body.dataset.page, 'click_add_comment_and_dismiss');
this.$emit('addCommentAndDismiss');
},
addDismissalComment() {
Stats.trackEvent(document.body.dataset.page, 'click_add_comment');
Tracking.event(document.body.dataset.page, 'click_add_comment');
this.$emit('addDismissalComment');
},
handleSubmit() {
......
import trackUninstallButtonClick from 'ee/clusters/mixins/track_uninstall_button_click';
import stats from 'ee/stats';
import Tracking from '~/tracking';
jest.mock('ee/stats');
jest.mock('~/tracking');
describe('trackUninstallButtonClickMixin', () => {
describe('trackUninstallButtonClick', () => {
it('sends snowplow event indicating which application will be uninstalled', () => {
it('tracks an event indicating which application will be uninstalled', () => {
const application = 'ingress';
trackUninstallButtonClick.methods.trackUninstallButtonClick(application);
expect(stats.trackEvent).toHaveBeenCalledWith('k8s_cluster', 'uninstall', {
expect(Tracking.event).toHaveBeenCalledWith('k8s_cluster', 'uninstall', {
label: application,
});
});
......
import Vue from 'vue';
import Stats from 'ee_else_ce/stats';
import Tracking from '~/tracking';
import { shallowMount } from '@vue/test-utils';
import initNoteStats from 'ee_else_ce/event_tracking/notes';
jest.mock('~/tracking');
describe('initNoteStats', () => {
let wrapper;
const createComponent = template => {
......@@ -14,13 +16,8 @@ describe('initNoteStats', () => {
return shallowMount(component, { attachToDocument: true });
};
jest.mock('ee_else_ce/stats');
Stats.trackEvent = jest.fn();
Stats.bindTrackableContainer = jest.fn();
afterEach(() => {
Stats.trackEvent.mockClear();
Stats.bindTrackableContainer.mockClear();
jest.clearAllMocks();
wrapper.destroy();
});
......@@ -33,12 +30,12 @@ describe('initNoteStats', () => {
});
it('calls bindTrackableContainer', () => {
expect(Stats.bindTrackableContainer).toHaveBeenCalledTimes(1);
expect(Tracking.prototype.bind).toHaveBeenCalledTimes(1);
});
it('calls trackEvent', () => {
wrapper.find('.main-notes-list').trigger('click');
expect(Stats.trackEvent).toHaveBeenCalledTimes(1);
expect(Tracking.event).toHaveBeenCalledTimes(1);
});
});
......@@ -47,7 +44,7 @@ describe('initNoteStats', () => {
wrapper = createComponent("<div><button class='main-notes-list'></button></div>");
initNoteStats();
wrapper.find('.main-notes-list').trigger('click');
expect(Stats.trackEvent).not.toHaveBeenCalled();
expect(Tracking.event).not.toHaveBeenCalled();
});
});
});
import { mount } from '@vue/test-utils';
import Stats from 'ee/stats';
import Tracking from '~/tracking';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import component from 'ee/vue_shared/security_reports/components/dismissal_comment_modal_footer.vue';
jest.mock('ee/stats');
jest.mock('~/tracking');
describe('DismissalCommentModalFooter', () => {
let origPage;
let wrapper;
afterEach(() => {
document.body.dataset.page = origPage;
jest.clearAllMocks();
wrapper.destroy();
});
beforeEach(() => {
origPage = document.body.dataset.page;
document.body.dataset.page = '_track_category_';
});
describe('with an non-dismissed vulnerability', () => {
beforeEach(() => {
wrapper = mount(component, { sync: false });
......@@ -21,8 +33,8 @@ describe('DismissalCommentModalFooter', () => {
wrapper.find(LoadingButton).trigger('click');
expect(wrapper.emitted().addCommentAndDismiss).toBeTruthy();
expect(Stats.trackEvent).toHaveBeenCalledWith(
document.body.dataset.page,
expect(Tracking.event).toHaveBeenCalledWith(
'_track_category_',
'click_add_comment_and_dismiss',
);
});
......@@ -49,10 +61,7 @@ describe('DismissalCommentModalFooter', () => {
wrapper.find(LoadingButton).trigger('click');
expect(wrapper.emitted().addDismissalComment).toBeTruthy();
expect(Stats.trackEvent).toHaveBeenCalledWith(
document.body.dataset.page,
'click_add_comment',
);
expect(Tracking.event).toHaveBeenCalledWith('_track_category_', 'click_add_comment');
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment