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