diff options
Diffstat (limited to 'app/assets/javascripts/tracking.js')
-rw-r--r-- | app/assets/javascripts/tracking.js | 103 |
1 files changed, 56 insertions, 47 deletions
diff --git a/app/assets/javascripts/tracking.js b/app/assets/javascripts/tracking.js index 1b4ca1d5741..7c0097fbe37 100644 --- a/app/assets/javascripts/tracking.js +++ b/app/assets/javascripts/tracking.js @@ -1,4 +1,4 @@ -import $ from 'jquery'; +import _ from 'underscore'; const DEFAULT_SNOWPLOW_OPTIONS = { namespace: 'gl', @@ -14,18 +14,31 @@ const DEFAULT_SNOWPLOW_OPTIONS = { linkClickTracking: false, }; -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, - }, - ]; +const eventHandler = (e, func, opts = {}) => { + const el = e.target.closest('[data-track-event]'); + const action = el && el.dataset.trackEvent; + if (!action) return; + + let value = el.dataset.trackValue || el.value || undefined; + if (el.type === 'checkbox' && !el.checked) value = false; + + const data = { + label: el.dataset.trackLabel, + property: el.dataset.trackProperty, + value, + context: el.dataset.trackContext, + }; + + func(opts.category, action + (opts.suffix || ''), _.omit(data, _.isUndefined)); +}; + +const eventHandlers = (category, func) => { + const handler = opts => e => eventHandler(e, func, { ...{ category }, ...opts }); + const handlers = []; + handlers.push({ name: 'click', func: handler() }); + handlers.push({ name: 'show.bs.dropdown', func: handler({ suffix: '_show' }) }); + handlers.push({ name: 'hide.bs.dropdown', func: handler({ suffix: '_hide' }) }); + return handlers; }; export default class Tracking { @@ -39,49 +52,43 @@ export default class Tracking { return typeof window.snowplow === 'function' && this.trackable(); } - static event(category = document.body.dataset.page, event = 'generic', data = {}) { + static event(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, - event, - Object.assign({}, { label: '', property: '', value: '' }, data), - ); + const { label, property, value, context } = data; + const contexts = context ? [context] : undefined; + return window.snowplow('trackStructEvent', category, action, label, property, value, contexts); } - 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)); - }); - } + static bindDocument(category = document.body.dataset.page, documentOverride = null) { + const el = documentOverride || document; + if (!this.enabled() || el.trackingBound) return []; - customHandlingFor(el) { - const classes = el.classList; + el.trackingBound = true; - // 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; + const handlers = eventHandlers(category, (...args) => this.event(...args)); + handlers.forEach(event => el.addEventListener(event.name, event.func)); + return handlers; } - eventHandler(category = null, opts = {}) { - return e => { - this.constructor.event(category || this.category, ...extractData(e.currentTarget, opts)); + static mixin(opts) { + return { + data() { + return { + tracking: { + // eslint-disable-next-line no-underscore-dangle + category: this.$options.name || this.$options._componentTag, + }, + }; + }, + methods: { + track(action, data) { + const category = opts.category || data.category || this.tracking.category; + Tracking.event(category || 'unspecified', action, { ...opts, ...this.tracking, ...data }); + }, + }, }; } } @@ -89,7 +96,7 @@ export default class Tracking { export function initUserTracking() { if (!Tracking.enabled()) return; - const opts = Object.assign({}, DEFAULT_SNOWPLOW_OPTIONS, window.snowplowOptions); + const opts = { ...DEFAULT_SNOWPLOW_OPTIONS, ...window.snowplowOptions }; window.snowplow('newTracker', opts.namespace, opts.hostname, opts); window.snowplow('enableActivityTracking', 30, 30); @@ -97,4 +104,6 @@ export function initUserTracking() { if (opts.formTracking) window.snowplow('enableFormTracking'); if (opts.linkClickTracking) window.snowplow('enableLinkClickTracking'); + + Tracking.bindDocument(); } |