1--- 2stage: Growth 3group: Product Intelligence 4info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments 5--- 6 7# Implement Snowplow tracking 8 9This page describes how to: 10 11- Implement Snowplow frontend and backend tracking 12- Test Snowplow events 13 14## Snowplow JavaScript frontend tracking 15 16GitLab provides a `Tracking` interface that wraps the [Snowplow JavaScript tracker](https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/javascript-trackers/) 17to track custom events. 18 19For the recommended frontend tracking implementation, see [Usage recommendations](#usage-recommendations). 20 21Tracking implementations must have an `action` and a `category`. You can provide additional 22categories from the [structured event taxonomy](index.md#structured-event-taxonomy) with an `extra` object 23that accepts key-value pairs. 24 25| Field | Type | Default value | Description | 26|:-----------|:-------|:---------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 27| `category` | string | `document.body.dataset.page` | Page or subsection of a page in which events are captured. | 28| `action` | string | generic | Action the user is taking. Clicks must be `click` and activations must be `activate`. For example, focusing a form field is `activate_form_input`, and clicking a button is `click_button`. | 29| `data` | object | `{}` | Additional data such as `label`, `property`, `value`, `context` as described in [Structured event taxonomy](index.md#structured-event-taxonomy), and `extra` (key-value pairs object). | 30 31### Usage recommendations 32 33- Use [data attributes](#implement-data-attribute-tracking) on HTML elements that emit `click`, `show.bs.dropdown`, or `hide.bs.dropdown` events. 34- Use the [Vue mixin](#implement-vue-component-tracking) for tracking custom events, or if the supported events for data attributes are not propagating. 35- Use the [tracking class](#implement-raw-javascript-tracking) when tracking raw JavaScript files. 36 37### Implement data attribute tracking 38 39To implement tracking for HAML or Vue templates, add a [`data-track` attribute](#data-track-attributes) to the element. 40 41The following example shows `data-track-*` attributes assigned to a button: 42 43```haml 44%button.btn{ data: { track: { action: "click_button", label: "template_preview", property: "my-template" } } } 45``` 46 47```html 48<button class="btn" 49 data-track-action="click_button" 50 data-track-label="template_preview" 51 data-track-property="my-template" 52 data-track-extra='{ "template_variant": "primary" }' 53/> 54``` 55 56#### `data-track` attributes 57 58| Attribute | Required | Description | 59|:----------------------|:---------|:------------| 60| `data-track-action` | true | Action the user is taking. Clicks must be prepended with `click` and activations must be prepended with `activate`. For example, focusing a form field is `activate_form_input` and clicking a button is `click_button`. Replaces `data-track-event`, which was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/290962) in GitLab 13.11. | 61| `data-track-label` | false | The specific element or object to act on. This can be: the label of the element, for example, a tab labeled 'Create from template' for `create_from_template`; a unique identifier if no text is available, for example, `groups_dropdown_close` for closing the Groups dropdown in the top bar; or the name or title attribute of a record being created. | 62| `data-track-property` | false | Any additional property of the element, or object being acted on. | 63| `data-track-value` | false | Describes a numeric value (decimal) directly related to the event. This could be the value of an input. For example, `10` when clicking `internal` visibility. If omitted, this is the element's `value` property or `undefined`. For checkboxes, the default value is the element's checked attribute or `0` when unchecked. | 64| `data-track-extra` | false | A key-value pair object passed as a valid JSON string. This attribute is added to the `extra` property in our [`gitlab_standard`](schemas.md#gitlab_standard) schema. | 65| `data-track-context` | false | The `context` as described in our [Structured event taxonomy](index.md#structured-event-taxonomy). | 66 67#### Event listeners 68 69Event listeners bind at the document level to handle click events in elements with data attributes. 70This allows them to be handled when the DOM re-renders or changes. Document-level binding reduces 71the likelihood that click events stop propagating up the DOM tree. 72 73If click events stop propagating, you must implement listeners and [Vue component tracking](#implement-vue-component-tracking) or [raw JavaScript tracking](#implement-raw-javascript-tracking). 74 75#### Helper methods 76 77Use the following Ruby helper: 78 79```ruby 80tracking_attrs(label, action, property) # { data: { track_label... } } 81 82%button{ **tracking_attrs('main_navigation', 'click_button', 'navigation') } 83``` 84 85If you use the GitLab helper method [`nav_link`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/helpers/tab_helper.rb#L76), you must wrap `html_options` under the `html_options` keyword argument. If you 86use the `ActionView` helper method [`link_to`](https://api.rubyonrails.org/v5.2.3/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to), you don't need to wrap `html_options`. 87 88```ruby 89# Bad 90= nav_link(controller: ['dashboard/groups', 'explore/groups'], data: { track_label: "explore_groups", 91track_action: "click_button" }) 92 93# Good 94= nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { data: { track_label: 95"explore_groups", track_action: "click_button" } }) 96 97# Good (other helpers) 98= link_to explore_groups_path, title: _("Explore"), data: { track_label: "explore_groups", track_action: 99"click_button" } 100``` 101 102### Implement Vue component tracking 103 104For custom event tracking, use a Vue `mixin` in components. Vue `mixin` exposes the `Tracking.event` 105static method and the `track` method. You can specify tracking options in `data` or `computed`. 106These options override any defaults and allow the values to be dynamic from props or based on state. 107 108Several default options are passed when an event is tracked from the component: 109 110- `category`: If you don't specify, by default `document.body.dataset.page` is used. 111- `label` 112- `property` 113- `value` 114 115To implement Vue component tracking: 116 1171. Import the `Tracking` library and request a `mixin`: 118 119 ```javascript 120 import Tracking from '~/tracking'; 121 const trackingMixin = Tracking.mixin; 122 ``` 123 1241. Provide categories to track the event from the component. For example, to track all events in a 125component with a label, use the `label` category: 126 127 ```javascript 128 import Tracking from '~/tracking'; 129 const trackingMixin = Tracking.mixin({ label: 'right_sidebar' }); 130 ``` 131 1321. In the component, declare the Vue `mixin`: 133 134 ```javascript 135 export default { 136 mixins: [trackingMixin], 137 // ...[component implementation]... 138 data() { 139 return { 140 expanded: false, 141 tracking: { 142 label: 'left_sidebar', 143 }, 144 }; 145 }, 146 }; 147 ``` 148 1491. To receive event data as a tracking object or computed property: 150 - Declare it in the `data` function. Use a `tracking` object when default event properties are dynamic or provided at runtime: 151 152 ```javascript 153 export default { 154 name: 'RightSidebar', 155 mixins: [Tracking.mixin()], 156 data() { 157 return { 158 tracking: { 159 label: 'right_sidebar', 160 // category: '', 161 // property: '', 162 // value: '', 163 // experiment: '', 164 // extra: {}, 165 }, 166 }; 167 }, 168 }; 169 ``` 170 171 - Declare it in the event data in the `track` function. This object merges with any previously provided options: 172 173 ```javascript 174 this.track('click_button', { 175 label: 'right_sidebar', 176 }); 177 ``` 178 1791. Optional. Use the `track` method in a template: 180 181 ```html 182 <template> 183 <div> 184 <button data-testid="toggle" @click="toggle">Toggle</button> 185 186 <div v-if="expanded"> 187 <p>Hello world!</p> 188 <button @click="track('click_action')">Track another event</button> 189 </div> 190 </div> 191 </template> 192 ``` 193 194The following example shows an implementation of Vue component tracking: 195 196```javascript 197export default { 198 name: 'RightSidebar', 199 mixins: [Tracking.mixin({ label: 'right_sidebar' })], 200 data() { 201 return { 202 expanded: false, 203 }; 204 }, 205 methods: { 206 toggle() { 207 this.expanded = !this.expanded; 208 // Additional data will be merged, like `value` below 209 this.track('click_toggle', { value: Number(this.expanded) }); 210 } 211 } 212}; 213``` 214 215#### Testing example 216 217```javascript 218import { mockTracking } from 'helpers/tracking_helper'; 219// mockTracking(category, documentOverride, spyMethod) 220 221describe('RightSidebar.vue', () => { 222 let trackingSpy; 223 let wrapper; 224 225 beforeEach(() => { 226 trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); 227 }); 228 229 const findToggle = () => wrapper.find('[data-testid="toggle"]'); 230 231 it('tracks turning off toggle', () => { 232 findToggle().trigger('click'); 233 234 expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_toggle', { 235 label: 'right_sidebar', 236 value: 0, 237 }); 238 }); 239}); 240``` 241 242### Implement raw JavaScript tracking 243 244To call custom event tracking and instrumentation directly from the JavaScript file, call the `Tracking.event` static function. 245 246The following example demonstrates tracking a click on a button by manually calling `Tracking.event`. 247 248```javascript 249import Tracking from '~/tracking'; 250 251const button = document.getElementById('create_from_template_button'); 252 253button.addEventListener('click', () => { 254 Tracking.event('dashboard:projects:index', 'click_button', { 255 label: 'create_from_template', 256 property: 'template_preview', 257 extra: { 258 templateVariant: 'primary', 259 valid: 1, 260 }, 261 }); 262}); 263``` 264 265#### Testing example 266 267```javascript 268import Tracking from '~/tracking'; 269 270describe('MyTracking', () => { 271 let wrapper; 272 273 beforeEach(() => { 274 jest.spyOn(Tracking, 'event'); 275 }); 276 277 const findButton = () => wrapper.find('[data-testid="create_from_template"]'); 278 279 it('tracks event', () => { 280 findButton().trigger('click'); 281 282 expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', { 283 label: 'create_from_template', 284 property: 'template_preview', 285 extra: { 286 templateVariant: 'primary', 287 valid: true, 288 }, 289 }); 290 }); 291}); 292``` 293 294### Form tracking 295 296To enable Snowplow automatic [form tracking](https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/javascript-trackers/javascript-tracker/javascript-tracker-v2/tracking-specific-events/#form-tracking): 297 2981. Call `Tracking.enableFormTracking` when the DOM is ready. 2991. Provide a `config` object that includes at least one of the following elements: 300 - `forms` determines the forms to track. Identified by the CSS class name. 301 - `fields` determines the fields inside the tracked forms to track. Identified by the field `name`. 3021. Optional. Provide a list of contexts as the second argument. The [`gitlab_standard`](schemas.md#gitlab_standard) schema is excluded from these events. 303 304```javascript 305Tracking.enableFormTracking({ 306 forms: { allow: ['sign-in-form', 'password-recovery-form'] }, 307 fields: { allow: ['terms_and_conditions', 'newsletter_agreement'] }, 308}); 309``` 310 311#### Testing example 312 313```javascript 314import Tracking from '~/tracking'; 315 316describe('MyFormTracking', () => { 317 let formTrackingSpy; 318 319 beforeEach(() => { 320 formTrackingSpy = jest 321 .spyOn(Tracking, 'enableFormTracking') 322 .mockImplementation(() => null); 323 }); 324 325 it('initialized with the correct configuration', () => { 326 expect(formTrackingSpy).toHaveBeenCalledWith({ 327 forms: { allow: ['sign-in-form', 'password-recovery-form'] }, 328 fields: { allow: ['terms_and_conditions', 'newsletter_agreement'] }, 329 }); 330 }); 331}); 332``` 333 334## Implement Ruby backend tracking 335 336`Gitlab::Tracking` is an interface that wraps the [Snowplow Ruby Tracker](https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/ruby-tracker/) for tracking custom events. 337Backend tracking provides: 338 339- User behavior tracking 340- Instrumentation to monitor and visualize performance over time in a section or aspect of code. 341 342To add custom event tracking and instrumentation, call the `GitLab::Tracking.event` class method. 343For example: 344 345```ruby 346class Projects::CreateService < BaseService 347 def execute 348 project = Project.create(params) 349 350 Gitlab::Tracking.event('Projects::CreateService', 'create_project', label: project.errors.full_messages.to_sentence, 351 property: project.valid?.to_s, project: project, user: current_user, namespace: namespace) 352 end 353end 354``` 355 356Use the following arguments: 357 358| Argument | Type | Default value | Description | 359|------------|---------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------| 360| `category` | String | | Area or aspect of the application. For example, `HealthCheckController` or `Lfs::FileTransformer`. | 361| `action` | String | | The action being taken. For example, a controller action such as `create`, or an Active Record callback. | 362| `label` | String | nil | The specific element or object to act on. This can be one of the following: the label of the element, for example, a tab labeled 'Create from template' for `create_from_template`; a unique identifier if no text is available, for example, `groups_dropdown_close` for closing the Groups dropdown in the top bar; or the name or title attribute of a record being created. | 363| `property` | String | nil | Any additional property of the element, or object being acted on. | 364| `value` | Numeric | nil | Describes a numeric value (decimal) directly related to the event. This could be the value of an input. For example, `10` when clicking `internal` visibility. | 365| `context` | Array\[SelfDescribingJSON\] | nil | An array of custom contexts to send with this event. Most events should not have any custom contexts. | 366| `project` | Project | nil | The project associated with the event. | 367| `user` | User | nil | The user associated with the event. | 368| `namespace` | Namespace | nil | The namespace associated with the event. | 369| `extra` | Hash | `{}` | Additional keyword arguments are collected into a hash and sent with the event. | 370 371### Unit testing 372 373To test backend Snowplow events, use the `expect_snowplow_event` helper. For more information, see 374[testing best practices](../testing_guide/best_practices.md#test-snowplow-events). 375 376### Performance 377 378We use the [AsyncEmitter](https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/ruby-tracker/emitters/#the-asyncemitter-class) when tracking events, which allows for instrumentation calls to be run in a background thread. This is still an active area of development. 379 380## Develop and test Snowplow 381 382To develop and test a Snowplow event, there are several tools to test frontend and backend events: 383 384| Testing Tool | Frontend Tracking | Backend Tracking | Local Development Environment | Production Environment | Production Environment | 385|----------------------------------------------|--------------------|---------------------|-------------------------------|------------------------|------------------------| 386| Snowplow Analytics Debugger Chrome Extension | Yes | No | Yes | Yes | Yes | 387| Snowplow Inspector Chrome Extension | Yes | No | Yes | Yes | Yes | 388| Snowplow Micro | Yes | Yes | Yes | No | No | 389 390### Test frontend events 391 392Before you test frontend events in development, you must: 393 3941. [Enable Snowplow tracking in the Admin Area](index.md#enable-snowplow-tracking). 3951. Turn off ad blockers that could prevent Snowplow JavaScript from loading in your environment. 3961. Turn off "Do Not Track" (DNT) in your browser. 397 398All URLs are pseudonymized. The entity identifier [replaces](https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/javascript-trackers/javascript-tracker/javascript-tracker-v2/tracker-setup/other-parameters-2/#Setting_a_custom_page_URL_and_referrer_URL) personally identifiable 399information (PII). PII includes usernames, group, and project names. 400 401#### Snowplow Analytics Debugger Chrome Extension 402 403[Snowplow Analytics Debugger](https://www.iglooanalytics.com/blog/snowplow-analytics-debugger-chrome-extension.html) is a browser extension for testing frontend events. It works in production, staging, and local development environments. 404 4051. Install the [Snowplow Analytics Debugger](https://chrome.google.com/webstore/detail/snowplow-analytics-debugg/jbnlcgeengmijcghameodeaenefieedm) Chrome browser extension. 4061. Open Chrome DevTools to the Snowplow Analytics Debugger tab. 407 408#### Snowplow Inspector Chrome Extension 409 410Snowplow Inspector Chrome Extension is a browser extension for testing frontend events. This works in production, staging, and local development environments. 411 4121. Install [Snowplow Inspector](https://chrome.google.com/webstore/detail/snowplow-inspector/maplkdomeamdlngconidoefjpogkmljm?hl=en). 4131. To open the extension, select the Snowplow Inspector icon beside the address bar. 4141. Click around on a webpage with Snowplow to see JavaScript events firing in the inspector window. 415 416### Test backend events 417 418#### Snowplow Micro 419 420[Snowplow Micro](https://snowplowanalytics.com/blog/2019/07/17/introducing-snowplow-micro/) is a 421Docker-based solution for testing backend and frontend in a local development environment. Snowplow Micro 422records the same events as the full Snowplow pipeline. To query events, use the Snowplow Micro API. 423 424It can be set up automatically using [GitLab Development Kit (GDK)](https://gitlab.com/gitlab-org/gitlab-development-kit). 425See the [how-to docs](https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/main/doc/howto/snowplow_micro.md) for more details. 426 427Optionally, you can set it up manually, using the following instructions. 428 429#### Set up Snowplow Micro manually 430 431To install and run Snowplow Micro, complete these steps to modify the 432[GitLab Development Kit (GDK)](https://gitlab.com/gitlab-org/gitlab-development-kit): 433 4341. Ensure [Docker is installed](https://docs.docker.com/get-docker/) and running. 435 4361. To install Snowplow Micro, clone the settings in 437[this project](https://gitlab.com/gitlab-org/snowplow-micro-configuration). 438 4391. Navigate to the directory with the cloned project, 440 and start the appropriate Docker container: 441 442 ```shell 443 ./snowplow-micro.sh 444 ``` 445 4461. Set the environment variable to tell the GDK to use Snowplow Micro in development. This overrides two `application_settings` options: 447 - `snowplow_enabled` setting will instead return `true` from `Gitlab::Tracking.enabled?` 448 - `snowplow_collector_hostname` setting will instead always return `localhost:9090` (or whatever is set for `SNOWPLOW_MICRO_URI`) from `Gitlab::Tracking.collector_hostname`. 449 450 ```shell 451 export SNOWPLOW_MICRO_ENABLE=1 452 ``` 453 454 Optionally, you can set the URI for you Snowplow Micro instance as well (the default value is `http://localhost:9090`): 455 456 ```shell 457 export SNOWPLOW_MICRO_URI=https://127.0.0.1:8080 458 ``` 459 4601. Restart GDK: 461 462 ```shell 463 gdk restart 464 ``` 465 4661. Send a test Snowplow event from the Rails console: 467 468 ```ruby 469 Gitlab::Tracking.event('category', 'action') 470 ``` 471 4721. Navigate to `localhost:9090/micro/good` to see the event. 473 474#### Useful links 475 476- [Snowplow Micro repository](https://github.com/snowplow-incubator/snowplow-micro) 477- [Installation guide recording](https://www.youtube.com/watch?v=OX46fo_A0Ag) 478 479### Troubleshoot 480 481To control content security policy warnings when using an external host, modify `config/gitlab.yml` 482to allow or disallow them. To allow them, add the relevant host for `connect_src`. For example, for 483`https://snowplow.trx.gitlab.net`: 484 485```yaml 486development: 487 <<: *base 488 gitlab: 489 content_security_policy: 490 enabled: true 491 directives: 492 connect_src: "'self' http://localhost:* http://127.0.0.1:* ws://localhost:* wss://localhost:* ws://127.0.0.1:* https://snowplow.trx.gitlab.net/" 493``` 494