From 3c0186a7176176eb996c73bc87a559d08246e8b4 Mon Sep 17 00:00:00 2001 From: Niko Belokolodov <nbelokolodov@gitlab.com> Date: Thu, 13 Mar 2025 06:28:14 +1300 Subject: [PATCH] Trigger Sentry when tracking called with incorrect event name --- .../tracking/dispatch_snowplow_event.js | 3 ++ app/assets/javascripts/tracking/utils.js | 7 ++++ spec/frontend/tracking/utils_spec.js | 32 +++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/app/assets/javascripts/tracking/dispatch_snowplow_event.js b/app/assets/javascripts/tracking/dispatch_snowplow_event.js index 91512292eb68f..4ae88268c4be2 100644 --- a/app/assets/javascripts/tracking/dispatch_snowplow_event.js +++ b/app/assets/javascripts/tracking/dispatch_snowplow_event.js @@ -1,5 +1,6 @@ import * as Sentry from '~/sentry/sentry_browser_wrapper'; import getStandardContext from './get_standard_context'; +import { validateEvent } from './utils'; export function dispatchSnowplowEvent( category = document.body.dataset.page, @@ -11,6 +12,8 @@ export function dispatchSnowplowEvent( throw new Error('Tracking: no category provided for tracking.'); } + validateEvent(action); + const { label, property, extra = {} } = data; let { value } = data; diff --git a/app/assets/javascripts/tracking/utils.js b/app/assets/javascripts/tracking/utils.js index 6493147672ec2..bfb767260f351 100644 --- a/app/assets/javascripts/tracking/utils.js +++ b/app/assets/javascripts/tracking/utils.js @@ -1,6 +1,7 @@ import { omitBy, isUndefined } from 'lodash'; import { TRACKING_CONTEXT_SCHEMA } from '~/experimentation/constants'; import { getExperimentData } from '~/experimentation/utils'; +import * as Sentry from '~/sentry/sentry_browser_wrapper'; import { ACTION_ATTR_SELECTOR, LOAD_ACTION_ATTR_SELECTOR, @@ -189,6 +190,12 @@ export const validateAdditionalProperties = (additionalProperties) => { }); }; +export const validateEvent = (event) => { + if (event && /\s/.test(event)) { + Sentry.captureException(new Error(`Event name should not contain whitespace: ${event}`)); + } +}; + function filterProperties(additionalProperties, predicate) { return Object.keys(additionalProperties).reduce((acc, key) => { if (predicate(key)) { diff --git a/spec/frontend/tracking/utils_spec.js b/spec/frontend/tracking/utils_spec.js index 91e0f958ef518..59dcab27afdd1 100644 --- a/spec/frontend/tracking/utils_spec.js +++ b/spec/frontend/tracking/utils_spec.js @@ -9,7 +9,9 @@ import { validateAdditionalProperties, getCustomAdditionalProperties, getBaseAdditionalProperties, + validateEvent, } from '~/tracking/utils'; +import * as Sentry from '~/sentry/sentry_browser_wrapper'; import { TRACKING_CONTEXT_SCHEMA } from '~/experimentation/constants'; import { REFERRER_TTL, URLS_CACHE_STORAGE_KEY } from '~/tracking/constants'; import { TEST_HOST } from 'helpers/test_constants'; @@ -220,6 +222,36 @@ describe('~/tracking/utils', () => { }); }); + describe('validateEvent', () => { + let sentrySpy; + + beforeEach(() => { + sentrySpy = jest.spyOn(Sentry, 'captureException'); + }); + + afterEach(() => { + sentrySpy.mockRestore(); + }); + + it('calls Sentry for event names with whitespace', () => { + validateEvent('event name'); + + expect(sentrySpy).toHaveBeenCalled(); + }); + + it('does not call Sentry for event names eqaual to nil', () => { + validateEvent(null); + + expect(sentrySpy).not.toHaveBeenCalled(); + }); + + it('does not call Sentry for event names without whitespace', () => { + validateEvent('event-name'); + + expect(sentrySpy).not.toHaveBeenCalled(); + }); + }); + describe('getCustomAdditionalProperties', () => { it('returns only custom properties', () => { const additionalProperties = { -- GitLab