summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/tracking
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-05-19 07:33:21 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-05-19 07:33:21 +0000
commit36a59d088eca61b834191dacea009677a96c052f (patch)
treee4f33972dab5d8ef79e3944a9f403035fceea43f /app/assets/javascripts/tracking
parenta1761f15ec2cae7c7f7bbda39a75494add0dfd6f (diff)
downloadgitlab-ce-36a59d088eca61b834191dacea009677a96c052f.tar.gz
Add latest changes from gitlab-org/gitlab@15-0-stable-eev15.0.0-rc42
Diffstat (limited to 'app/assets/javascripts/tracking')
-rw-r--r--app/assets/javascripts/tracking/dispatch_snowplow_event.js9
-rw-r--r--app/assets/javascripts/tracking/tracker.js267
-rw-r--r--app/assets/javascripts/tracking/tracking.js275
3 files changed, 283 insertions, 268 deletions
diff --git a/app/assets/javascripts/tracking/dispatch_snowplow_event.js b/app/assets/javascripts/tracking/dispatch_snowplow_event.js
index 7e596f5f36f..5daeaf1d85b 100644
--- a/app/assets/javascripts/tracking/dispatch_snowplow_event.js
+++ b/app/assets/javascripts/tracking/dispatch_snowplow_event.js
@@ -1,3 +1,4 @@
+import * as Sentry from '@sentry/browser';
import getStandardContext from './get_standard_context';
export function dispatchSnowplowEvent(
@@ -24,5 +25,11 @@ export function dispatchSnowplowEvent(
value = Number(value);
}
- return window.snowplow('trackStructEvent', category, action, label, property, value, contexts);
+ try {
+ window.snowplow('trackStructEvent', category, action, label, property, value, contexts);
+ return true;
+ } catch (error) {
+ Sentry.captureException(error);
+ return false;
+ }
}
diff --git a/app/assets/javascripts/tracking/tracker.js b/app/assets/javascripts/tracking/tracker.js
new file mode 100644
index 00000000000..9ad86e76b6e
--- /dev/null
+++ b/app/assets/javascripts/tracking/tracker.js
@@ -0,0 +1,267 @@
+import { LOAD_ACTION_ATTR_SELECTOR } from './constants';
+import { dispatchSnowplowEvent } from './dispatch_snowplow_event';
+import getStandardContext from './get_standard_context';
+import {
+ getEventHandlers,
+ createEventPayload,
+ renameKey,
+ getReferrersCache,
+ addReferrersCacheEntry,
+} from './utils';
+
+export const Tracker = {
+ nonInitializedQueue: [],
+ initialized: false,
+ definitionsLoaded: false,
+ definitionsManifest: {},
+ definitionsEventsQueue: [],
+ definitions: [],
+ ALLOWED_URL_HASHES: ['#diff', '#note'],
+ /**
+ * (Legacy) Determines if tracking is enabled at the user level.
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/DNT.
+ *
+ * @returns {Boolean}
+ */
+ trackable() {
+ return !['1', 'yes'].includes(
+ window.doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack,
+ );
+ },
+
+ /**
+ * Determines if Snowplow is available/enabled.
+ *
+ * @returns {Boolean}
+ */
+ enabled() {
+ return typeof window.snowplow === 'function' && Tracker.trackable();
+ },
+
+ /**
+ * Dispatches a structured event per our taxonomy:
+ * https://docs.gitlab.com/ee/development/snowplow/index.html#structured-event-taxonomy.
+ *
+ * If the library is not initialized and events are trying to be
+ * dispatched (data-attributes, load-events), they will be added
+ * to a queue to be flushed afterwards.
+ *
+ * If there is an error when using the library, it will return ´false´
+ * and ´true´ otherwise.
+ *
+ * @param {...any} eventData defined event taxonomy
+ * @returns {Boolean}
+ */
+ event(...eventData) {
+ if (!Tracker.enabled()) {
+ return false;
+ }
+
+ if (!Tracker.initialized) {
+ Tracker.nonInitializedQueue.push(eventData);
+ return false;
+ }
+
+ return dispatchSnowplowEvent(...eventData);
+ },
+
+ /**
+ * Preloads event definitions.
+ *
+ * @returns {undefined}
+ */
+ loadDefinitions() {
+ // TODO: fetch definitions from the server and flush the queue
+ // See https://gitlab.com/gitlab-org/gitlab/-/issues/358256
+ Tracker.definitionsLoaded = true;
+
+ while (Tracker.definitionsEventsQueue.length) {
+ Tracker.dispatchFromDefinition(...Tracker.definitionsEventsQueue.shift());
+ }
+ },
+
+ /**
+ * Dispatches a structured event with data from its event definition.
+ *
+ * @param {String} basename
+ * @param {Object} eventData
+ * @returns {Boolean}
+ */
+ definition(basename, eventData = {}) {
+ if (!Tracker.enabled()) {
+ return false;
+ }
+
+ if (!(basename in Tracker.definitionsManifest)) {
+ throw new Error(`Missing Snowplow event definition "${basename}"`);
+ }
+
+ return Tracker.dispatchFromDefinition(basename, eventData);
+ },
+
+ /**
+ * Builds an event with data from a valid definition and sends it to
+ * Snowplow. If the definitions are not loaded, it pushes the data to a queue.
+ *
+ * @param {String} basename
+ * @param {Object} eventData
+ * @returns {Boolean}
+ */
+ dispatchFromDefinition(basename, eventData) {
+ if (!Tracker.definitionsLoaded) {
+ Tracker.definitionsEventsQueue.push([basename, eventData]);
+
+ return false;
+ }
+
+ const eventDefinition = Tracker.definitions.find((definition) => definition.key === basename);
+
+ return Tracker.event(
+ eventData.category ?? eventDefinition.category,
+ eventData.action ?? eventDefinition.action,
+ eventData,
+ );
+ },
+
+ /**
+ * Dispatches any event emitted before initialization.
+ *
+ * @returns {undefined}
+ */
+ flushPendingEvents() {
+ Tracker.initialized = true;
+
+ while (Tracker.nonInitializedQueue.length) {
+ dispatchSnowplowEvent(...Tracker.nonInitializedQueue.shift());
+ }
+ },
+
+ /**
+ * Attaches event handlers for data-attributes powered events.
+ *
+ * @param {String} category - the default category for all events
+ * @param {HTMLElement} parent - element containing data-attributes
+ * @returns {Array}
+ */
+ bindDocument(category = document.body.dataset.page, parent = document) {
+ if (!Tracker.enabled() || parent.trackingBound) {
+ return [];
+ }
+
+ // eslint-disable-next-line no-param-reassign
+ parent.trackingBound = true;
+
+ const handlers = getEventHandlers(category, (...args) => Tracker.event(...args));
+ handlers.forEach((event) => parent.addEventListener(event.name, event.func));
+
+ return handlers;
+ },
+
+ /**
+ * Attaches event handlers for load-events (on render).
+ *
+ * @param {String} category - the default category for all events
+ * @param {HTMLElement} parent - element containing event targets
+ * @returns {Array}
+ */
+ trackLoadEvents(category = document.body.dataset.page, parent = document) {
+ if (!Tracker.enabled()) {
+ return [];
+ }
+
+ const loadEvents = parent.querySelectorAll(LOAD_ACTION_ATTR_SELECTOR);
+
+ loadEvents.forEach((element) => {
+ const { action, data } = createEventPayload(element);
+ Tracker.event(category, action, data);
+ });
+
+ return loadEvents;
+ },
+
+ /**
+ * Enable Snowplow automatic form tracking.
+ * The config param requires at least one array of either forms
+ * class names, or field name attributes.
+ * https://docs.gitlab.com/ee/development/snowplow/index.html#form-tracking.
+ *
+ * @param {Object} config
+ * @param {Array} contexts
+ * @returns {undefined}
+ */
+ enableFormTracking(config, contexts = []) {
+ if (!Tracker.enabled()) {
+ return;
+ }
+
+ if (!Array.isArray(config?.forms?.allow) && !Array.isArray(config?.fields?.allow)) {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ throw new Error('Unable to enable form event tracking without allow rules.');
+ }
+
+ // Ignore default/standard schema
+ const standardContext = getStandardContext();
+ const userProvidedContexts = contexts.filter(
+ (context) => context.schema !== standardContext.schema,
+ );
+
+ const mappedConfig = {};
+ if (config.forms) {
+ mappedConfig.forms = renameKey(config.forms, 'allow', 'whitelist');
+ }
+
+ if (config.fields) {
+ mappedConfig.fields = renameKey(config.fields, 'allow', 'whitelist');
+ }
+
+ const enabler = () => window.snowplow('enableFormTracking', mappedConfig, userProvidedContexts);
+
+ if (document.readyState === 'complete') {
+ enabler();
+ } else {
+ document.addEventListener('readystatechange', () => {
+ if (document.readyState === 'complete') {
+ enabler();
+ }
+ });
+ }
+ },
+
+ /**
+ * Replaces the URL and referrer for the default web context
+ * if the replacements are available.
+ *
+ * @returns {undefined}
+ */
+ setAnonymousUrls() {
+ const { snowplowPseudonymizedPageUrl: pageUrl } = window.gl;
+
+ if (!pageUrl) {
+ return;
+ }
+
+ const referrers = getReferrersCache();
+ const pageLinks = Object.seal({
+ url: pageUrl,
+ referrer: '',
+ originalUrl: window.location.href,
+ });
+
+ const appendHash = Tracker.ALLOWED_URL_HASHES.some((prefix) =>
+ window.location.hash.startsWith(prefix),
+ );
+ const customUrl = `${pageUrl}${appendHash ? window.location.hash : ''}`;
+ window.snowplow('setCustomUrl', customUrl);
+
+ if (document.referrer) {
+ const node = referrers.find((links) => links.originalUrl === document.referrer);
+
+ if (node) {
+ pageLinks.referrer = node.url;
+ window.snowplow('setReferrerUrl', pageLinks.referrer);
+ }
+ }
+
+ addReferrersCacheEntry(referrers, pageLinks);
+ },
+};
diff --git a/app/assets/javascripts/tracking/tracking.js b/app/assets/javascripts/tracking/tracking.js
index f299c57b33f..923aea433f1 100644
--- a/app/assets/javascripts/tracking/tracking.js
+++ b/app/assets/javascripts/tracking/tracking.js
@@ -1,268 +1,7 @@
-import { LOAD_ACTION_ATTR_SELECTOR } from './constants';
-import { dispatchSnowplowEvent } from './dispatch_snowplow_event';
-import getStandardContext from './get_standard_context';
-import {
- getEventHandlers,
- createEventPayload,
- renameKey,
- addExperimentContext,
- getReferrersCache,
- addReferrersCacheEntry,
-} from './utils';
-
-const ALLOWED_URL_HASHES = ['#diff', '#note'];
-
-export default class Tracking {
- static nonInitializedQueue = [];
- static initialized = false;
- static definitionsLoaded = false;
- static definitionsManifest = {};
- static definitionsEventsQueue = [];
- static definitions = [];
-
- /**
- * (Legacy) Determines if tracking is enabled at the user level.
- * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/DNT.
- *
- * @returns {Boolean}
- */
- static trackable() {
- return !['1', 'yes'].includes(
- window.doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack,
- );
- }
-
- /**
- * Determines if Snowplow is available/enabled.
- *
- * @returns {Boolean}
- */
- static enabled() {
- return typeof window.snowplow === 'function' && this.trackable();
- }
-
- /**
- * Dispatches a structured event per our taxonomy:
- * https://docs.gitlab.com/ee/development/snowplow/index.html#structured-event-taxonomy.
- *
- * If the library is not initialized and events are trying to be
- * dispatched (data-attributes, load-events), they will be added
- * to a queue to be flushed afterwards.
- *
- * @param {...any} eventData defined event taxonomy
- * @returns {undefined|Boolean}
- */
- static event(...eventData) {
- if (!this.enabled()) {
- return false;
- }
-
- if (!this.initialized) {
- this.nonInitializedQueue.push(eventData);
- return false;
- }
-
- return dispatchSnowplowEvent(...eventData);
- }
-
- /**
- * Preloads event definitions.
- *
- * @returns {undefined}
- */
- static loadDefinitions() {
- // TODO: fetch definitions from the server and flush the queue
- // See https://gitlab.com/gitlab-org/gitlab/-/issues/358256
- this.definitionsLoaded = true;
-
- while (this.definitionsEventsQueue.length) {
- this.dispatchFromDefinition(...this.definitionsEventsQueue.shift());
- }
- }
-
- /**
- * Dispatches a structured event with data from its event definition.
- *
- * @param {String} basename
- * @param {Object} eventData
- * @returns {undefined|Boolean}
- */
- static definition(basename, eventData = {}) {
- if (!this.enabled()) {
- return false;
- }
-
- if (!(basename in this.definitionsManifest)) {
- throw new Error(`Missing Snowplow event definition "${basename}"`);
- }
-
- return this.dispatchFromDefinition(basename, eventData);
- }
-
- /**
- * Builds an event with data from a valid definition and sends it to
- * Snowplow. If the definitions are not loaded, it pushes the data to a queue.
- *
- * @param {String} basename
- * @param {Object} eventData
- * @returns {undefined|Boolean}
- */
- static dispatchFromDefinition(basename, eventData) {
- if (!this.definitionsLoaded) {
- this.definitionsEventsQueue.push([basename, eventData]);
-
- return false;
- }
-
- const eventDefinition = this.definitions.find((definition) => definition.key === basename);
-
- return this.event(
- eventData.category ?? eventDefinition.category,
- eventData.action ?? eventDefinition.action,
- eventData,
- );
- }
-
- /**
- * Dispatches any event emitted before initialization.
- *
- * @returns {undefined}
- */
- static flushPendingEvents() {
- this.initialized = true;
-
- while (this.nonInitializedQueue.length) {
- dispatchSnowplowEvent(...this.nonInitializedQueue.shift());
- }
- }
-
- /**
- * Attaches event handlers for data-attributes powered events.
- *
- * @param {String} category - the default category for all events
- * @param {HTMLElement} parent - element containing data-attributes
- * @returns {Array}
- */
- static bindDocument(category = document.body.dataset.page, parent = document) {
- if (!this.enabled() || parent.trackingBound) {
- return [];
- }
-
- // eslint-disable-next-line no-param-reassign
- parent.trackingBound = true;
-
- const handlers = getEventHandlers(category, (...args) => this.event(...args));
- handlers.forEach((event) => parent.addEventListener(event.name, event.func));
-
- return handlers;
- }
-
- /**
- * Attaches event handlers for load-events (on render).
- *
- * @param {String} category - the default category for all events
- * @param {HTMLElement} parent - element containing event targets
- * @returns {Array}
- */
- static trackLoadEvents(category = document.body.dataset.page, parent = document) {
- if (!this.enabled()) {
- return [];
- }
-
- const loadEvents = parent.querySelectorAll(LOAD_ACTION_ATTR_SELECTOR);
-
- loadEvents.forEach((element) => {
- const { action, data } = createEventPayload(element);
- this.event(category, action, data);
- });
-
- return loadEvents;
- }
-
- /**
- * Enable Snowplow automatic form tracking.
- * The config param requires at least one array of either forms
- * class names, or field name attributes.
- * https://docs.gitlab.com/ee/development/snowplow/index.html#form-tracking.
- *
- * @param {Object} config
- * @param {Array} contexts
- * @returns {undefined}
- */
- static enableFormTracking(config, contexts = []) {
- if (!this.enabled()) {
- return;
- }
-
- if (!Array.isArray(config?.forms?.allow) && !Array.isArray(config?.fields?.allow)) {
- // eslint-disable-next-line @gitlab/require-i18n-strings
- throw new Error('Unable to enable form event tracking without allow rules.');
- }
-
- // Ignore default/standard schema
- const standardContext = getStandardContext();
- const userProvidedContexts = contexts.filter(
- (context) => context.schema !== standardContext.schema,
- );
-
- const mappedConfig = {};
- if (config.forms) {
- mappedConfig.forms = renameKey(config.forms, 'allow', 'whitelist');
- }
-
- if (config.fields) {
- mappedConfig.fields = renameKey(config.fields, 'allow', 'whitelist');
- }
-
- const enabler = () => window.snowplow('enableFormTracking', mappedConfig, userProvidedContexts);
-
- if (document.readyState === 'complete') {
- enabler();
- } else {
- document.addEventListener('readystatechange', () => {
- if (document.readyState === 'complete') {
- enabler();
- }
- });
- }
- }
-
- /**
- * Replaces the URL and referrer for the default web context
- * if the replacements are available.
- *
- * @returns {undefined}
- */
- static setAnonymousUrls() {
- const { snowplowPseudonymizedPageUrl: pageUrl } = window.gl;
-
- if (!pageUrl) {
- return;
- }
-
- const referrers = getReferrersCache();
- const pageLinks = Object.seal({
- url: pageUrl,
- referrer: '',
- originalUrl: window.location.href,
- });
-
- const appendHash = ALLOWED_URL_HASHES.some((prefix) => window.location.hash.startsWith(prefix));
- const customUrl = `${pageUrl}${appendHash ? window.location.hash : ''}`;
- window.snowplow('setCustomUrl', customUrl);
-
- if (document.referrer) {
- const node = referrers.find((links) => links.originalUrl === document.referrer);
-
- if (node) {
- pageLinks.referrer = node.url;
- window.snowplow('setReferrerUrl', pageLinks.referrer);
- }
- }
-
- addReferrersCacheEntry(referrers, pageLinks);
- }
+import { Tracker } from 'jh_else_ce/tracking/tracker';
+import { addExperimentContext } from './utils';
+const Tracking = Object.assign(Tracker, {
/**
* Returns an implementation of this class in the form of
* a Vue mixin.
@@ -270,7 +9,7 @@ export default class Tracking {
* @param {Object} opts - default options for all events
* @returns {Object}
*/
- static mixin(opts = {}) {
+ mixin(opts = {}) {
return {
computed: {
trackingCategory() {
@@ -294,5 +33,7 @@ export default class Tracking {
},
},
};
- }
-}
+ },
+});
+
+export default Tracking;