From 06933418fb974fb0716e73ec15c7b9b975783494 Mon Sep 17 00:00:00 2001 From: jejacks0n Date: Mon, 26 Aug 2019 10:24:40 -0600 Subject: Renames Tracking to EventTracking to match ruby This renames the Tracking class to EventTracking because it was requested to be renamed from two of the backend engineers. This will also require an EE merge request. --- app/assets/javascripts/event_tracking.js | 74 ++++++++++ .../javascripts/notes/components/comment_form.vue | 2 +- app/assets/javascripts/tracking.js | 73 ---------- spec/frontend/event_tracking_spec.js | 149 +++++++++++++++++++++ spec/frontend/tracking_spec.js | 143 -------------------- 5 files changed, 224 insertions(+), 217 deletions(-) create mode 100644 app/assets/javascripts/event_tracking.js delete mode 100644 app/assets/javascripts/tracking.js create mode 100644 spec/frontend/event_tracking_spec.js delete mode 100644 spec/frontend/tracking_spec.js diff --git a/app/assets/javascripts/event_tracking.js b/app/assets/javascripts/event_tracking.js new file mode 100644 index 00000000000..0290e09ad40 --- /dev/null +++ b/app/assets/javascripts/event_tracking.js @@ -0,0 +1,74 @@ +import $ from 'jquery'; + +const extractData = (el, opts = {}) => { + const { trackAction, trackLabel = '', trackProperty = '', trackContext } = el.dataset; + let trackValue = el.dataset.trackValue || el.value || ''; + if (el.type === 'checkbox' && !el.checked) trackValue = false; + return [ + trackAction + (opts.suffix || ''), + { + label: trackLabel, + property: trackProperty, + value: trackValue, + context: trackContext, + }, + ]; +}; + +export default class EventTracking { + static trackable() { + return !['1', 'yes'].includes( + window.doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack, + ); + } + + static enabled() { + return typeof window.snowplow === 'function' && this.trackable(); + } + + static track(category = document.body.dataset.page, action = 'generic', data = {}) { + if (!this.enabled()) return false; + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings + if (!category) throw new Error('Tracking: no category provided for tracking.'); + + return window.snowplow( + 'trackStructEvent', + category, + action, + Object.assign({}, { label: '', property: '', value: '', context: undefined }, data), + ); + } + + constructor(category = document.body.dataset.page) { + this.category = category; + } + + bind(container = document) { + if (!this.constructor.enabled()) return; + container.querySelectorAll(`[data-track-action]`).forEach(el => { + if (this.customHandlingFor(el)) return; + // jquery is required for select2, so we use it always + // see: https://github.com/select2/select2/issues/4686 + $(el).on('click', this.eventHandler(this.category)); + }); + } + + customHandlingFor(el) { + const classes = el.classList; + + // bootstrap dropdowns + if (classes.contains('dropdown')) { + $(el).on('show.bs.dropdown', this.eventHandler(this.category, { suffix: '_show' })); + $(el).on('hide.bs.dropdown', this.eventHandler(this.category, { suffix: '_hide' })); + return true; + } + + return false; + } + + eventHandler(category = null, opts = {}) { + return e => { + this.constructor.track(category || this.category, ...extractData(e.currentTarget, opts)); + }; + } +} diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index fda494fec07..97fe984dc31 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -384,7 +384,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown" qa-comment-button" type="submit" :data-track-label="trackingLabel" - data-track-event="click_button" + data-track-action="click_button" @click.prevent="handleSave()" > {{ commentButtonTitle }} diff --git a/app/assets/javascripts/tracking.js b/app/assets/javascripts/tracking.js deleted file mode 100644 index a852f937eec..00000000000 --- a/app/assets/javascripts/tracking.js +++ /dev/null @@ -1,73 +0,0 @@ -import $ from 'jquery'; - -const extractData = (el, opts = {}) => { - const { trackEvent, trackLabel = '', trackProperty = '' } = el.dataset; - let trackValue = el.dataset.trackValue || el.value || ''; - if (el.type === 'checkbox' && !el.checked) trackValue = false; - return [ - trackEvent + (opts.suffix || ''), - { - label: trackLabel, - property: trackProperty, - value: trackValue, - }, - ]; -}; - -export default class Tracking { - static trackable() { - return !['1', 'yes'].includes( - window.doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack, - ); - } - - static enabled() { - return typeof window.snowplow === 'function' && this.trackable(); - } - - static event(category = document.body.dataset.page, event = 'generic', data = {}) { - if (!this.enabled()) return false; - // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings - if (!category) throw new Error('Tracking: no category provided for tracking.'); - - return window.snowplow( - 'trackStructEvent', - category, - event, - Object.assign({}, { label: '', property: '', value: '' }, data), - ); - } - - constructor(category = document.body.dataset.page) { - this.category = category; - } - - bind(container = document) { - if (!this.constructor.enabled()) return; - container.querySelectorAll(`[data-track-event]`).forEach(el => { - if (this.customHandlingFor(el)) return; - // jquery is required for select2, so we use it always - // see: https://github.com/select2/select2/issues/4686 - $(el).on('click', this.eventHandler(this.category)); - }); - } - - customHandlingFor(el) { - const classes = el.classList; - - // bootstrap dropdowns - if (classes.contains('dropdown')) { - $(el).on('show.bs.dropdown', this.eventHandler(this.category, { suffix: '_show' })); - $(el).on('hide.bs.dropdown', this.eventHandler(this.category, { suffix: '_hide' })); - return true; - } - - return false; - } - - eventHandler(category = null, opts = {}) { - return e => { - this.constructor.event(category || this.category, ...extractData(e.currentTarget, opts)); - }; - } -} diff --git a/spec/frontend/event_tracking_spec.js b/spec/frontend/event_tracking_spec.js new file mode 100644 index 00000000000..f3b8530dfa9 --- /dev/null +++ b/spec/frontend/event_tracking_spec.js @@ -0,0 +1,149 @@ +import $ from 'jquery'; +import { setHTMLFixture } from './helpers/fixtures'; + +import EventTracking from '~/event_tracking'; + +describe('EventTracking', () => { + let snowplowSpy; + + beforeEach(() => { + window.snowplow = window.snowplow || (() => {}); + snowplowSpy = jest.spyOn(window, 'snowplow'); + }); + + describe('.track', () => { + afterEach(() => { + window.doNotTrack = undefined; + navigator.doNotTrack = undefined; + navigator.msDoNotTrack = undefined; + }); + + it('tracks to snowplow (our current tracking system)', () => { + EventTracking.track('_category_', '_action_', { label: '_label_' }); + + expect(snowplowSpy).toHaveBeenCalledWith('trackStructEvent', '_category_', '_action_', { + label: '_label_', + property: '', + value: '', + }); + }); + + it('skips tracking if snowplow is unavailable', () => { + window.snowplow = false; + EventTracking.track('_category_', '_action_'); + + expect(snowplowSpy).not.toHaveBeenCalled(); + }); + + it('skips tracking if the user does not want to be tracked (general spec)', () => { + window.doNotTrack = '1'; + EventTracking.track('_category_', '_action_'); + + expect(snowplowSpy).not.toHaveBeenCalled(); + }); + + it('skips tracking if the user does not want to be tracked (firefox legacy)', () => { + navigator.doNotTrack = 'yes'; + EventTracking.track('_category_', '_action_'); + + expect(snowplowSpy).not.toHaveBeenCalled(); + }); + + it('skips tracking if the user does not want to be tracked (IE legacy)', () => { + navigator.msDoNotTrack = '1'; + EventTracking.track('_category_', '_action_'); + + expect(snowplowSpy).not.toHaveBeenCalled(); + }); + }); + + describe('tracking interface events', () => { + let eventSpy; + + beforeEach(() => { + eventSpy = jest.spyOn(EventTracking, 'track'); + setHTMLFixture(` + + + + + +
+ `); + new EventTracking('_category_').bind(); + }); + + it('binds to clicks on elements matching [data-track-action]', () => { + $('[data-track-action="click_input1"]').click(); + + expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input1', { + label: '_label_', + value: '_value_', + property: '', + context: undefined, + }); + }); + + it('allows value override with the data-track-value attribute', () => { + $('[data-track-action="click_input2"]').click(); + + expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input2', { + label: '', + value: '_value_override_', + property: '', + context: undefined, + }); + }); + + it('allows providing context for the tracking call', () => { + $('[data-track-action="click_input3"]').click(); + + expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input3', { + label: '', + property: '', + value: '', + context: "{foo: 'bar'}", + }); + }); + + it('handles checkbox values correctly', () => { + const $checkbox = $('[data-track-action="toggle_checkbox"]'); + + $checkbox.click(); // unchecking + + expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_checkbox', { + label: '', + property: '', + value: false, + }); + + $checkbox.click(); // checking + + expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_checkbox', { + label: '', + property: '', + value: '_value_', + }); + }); + + it('handles bootstrap dropdowns', () => { + const $dropdown = $('[data-track-action="toggle_dropdown"]'); + + $dropdown.trigger('show.bs.dropdown'); // showing + + expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_show', { + label: '', + property: '', + value: '', + }); + + $dropdown.trigger('hide.bs.dropdown'); // hiding + + expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_hide', { + label: '', + property: '', + value: '', + }); + }); + }); +}); diff --git a/spec/frontend/tracking_spec.js b/spec/frontend/tracking_spec.js deleted file mode 100644 index 0e862c683d3..00000000000 --- a/spec/frontend/tracking_spec.js +++ /dev/null @@ -1,143 +0,0 @@ -import $ from 'jquery'; -import { setHTMLFixture } from './helpers/fixtures'; - -import Tracking from '~/tracking'; - -describe('Tracking', () => { - beforeEach(() => { - window.snowplow = window.snowplow || (() => {}); - }); - - describe('.event', () => { - let snowplowSpy = null; - - beforeEach(() => { - snowplowSpy = jest.spyOn(window, 'snowplow'); - }); - - afterEach(() => { - window.doNotTrack = undefined; - navigator.doNotTrack = undefined; - navigator.msDoNotTrack = undefined; - }); - - it('tracks to snowplow (our current tracking system)', () => { - Tracking.event('_category_', '_eventName_', { label: '_label_' }); - - expect(snowplowSpy).toHaveBeenCalledWith('trackStructEvent', '_category_', '_eventName_', { - label: '_label_', - property: '', - value: '', - }); - }); - - it('skips tracking if snowplow is unavailable', () => { - window.snowplow = false; - Tracking.event('_category_', '_eventName_'); - - expect(snowplowSpy).not.toHaveBeenCalled(); - }); - - it('skips tracking if the user does not want to be tracked (general spec)', () => { - window.doNotTrack = '1'; - Tracking.event('_category_', '_eventName_'); - - expect(snowplowSpy).not.toHaveBeenCalled(); - }); - - it('skips tracking if the user does not want to be tracked (firefox legacy)', () => { - navigator.doNotTrack = 'yes'; - Tracking.event('_category_', '_eventName_'); - - expect(snowplowSpy).not.toHaveBeenCalled(); - }); - - it('skips tracking if the user does not want to be tracked (IE legacy)', () => { - navigator.msDoNotTrack = '1'; - Tracking.event('_category_', '_eventName_'); - - expect(snowplowSpy).not.toHaveBeenCalled(); - }); - }); - - describe('tracking interface events', () => { - let eventSpy = null; - let subject = null; - - beforeEach(() => { - eventSpy = jest.spyOn(Tracking, 'event'); - subject = new Tracking('_category_'); - setHTMLFixture(` - - - - -
- `); - }); - - it('binds to clicks on elements matching [data-track-event]', () => { - subject.bind(document); - $('[data-track-event="click_input1"]').click(); - - expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input1', { - label: '_label_', - value: '_value_', - property: '', - }); - }); - - it('allows value override with the data-track-value attribute', () => { - subject.bind(document); - $('[data-track-event="click_input2"]').click(); - - expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input2', { - label: '', - value: '_value_override_', - property: '', - }); - }); - - it('handles checkbox values correctly', () => { - subject.bind(document); - const $checkbox = $('[data-track-event="toggle_checkbox"]'); - - $checkbox.click(); // unchecking - - expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_checkbox', { - label: '', - property: '', - value: false, - }); - - $checkbox.click(); // checking - - expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_checkbox', { - label: '', - property: '', - value: '_value_', - }); - }); - - it('handles bootstrap dropdowns', () => { - new Tracking('_category_').bind(document); - const $dropdown = $('[data-track-event="toggle_dropdown"]'); - - $dropdown.trigger('show.bs.dropdown'); // showing - - expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_show', { - label: '', - property: '', - value: '', - }); - - $dropdown.trigger('hide.bs.dropdown'); // hiding - - expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_hide', { - label: '', - property: '', - value: '', - }); - }); - }); -}); -- cgit v1.2.1