Commit b94dac6a authored by Russell Dickenson's avatar Russell Dickenson

Merge branch 'jeromezng-revamp-telemetry-docs' into 'master'

Revamp telemetry docs

See merge request gitlab-org/gitlab!32352
parents f03fae20 3de51f73
......@@ -187,10 +187,11 @@ Complementary reads:
## Telemetry guides
- [Introduction](../telemetry/index.md)
- [Snowplow tracking guide](../telemetry/snowplow.md)
- [Telemetry guide](telemetry/index.md)
- [Usage Ping guide](telemetry/usage_ping.md)
- [Snowplow guide](telemetry/snowplow.md)
## Experiment Guide
## Experiment guide
- [Introduction](experiment_guide/index.md)
......
---
redirect_to: '../../telemetry/index.md'
redirect_to: '../telemetry/index.md'
---
This document was moved to [another location](../../telemetry/index.md).
This document was moved to [another location](../telemetry/index.md).
---
redirect_to: '../../telemetry/index.md'
redirect_to: '../telemetry/index.md'
---
This document was moved to [another location](../../telemetry/index.md).
This document was moved to [another location](../telemetry/index.md).
---
redirect_to: '../../telemetry/index.md'
redirect_to: '../telemetry/index.md'
---
This document was moved to [another location](../../telemetry/index.md).
This document was moved to [another location](../telemetry/index.md).
......@@ -54,7 +54,7 @@ The author then adds a comment to this piece of code and adds a link to the issu
end
```
- Track necessary events. See the [telemetry guide](../../telemetry/index.md) for details.
- Track necessary events. See the [telemetry guide](../telemetry/index.md) for details.
- After the merge request is merged, use [`chatops`](../../ci/chatops/README.md) in the
[appropriate channel](../feature_flags/controls.md#communicate-the-change) to start the experiment for 10% of the users.
The feature flag should have the name of the experiment with the `_experiment_percentage` suffix appended.
......
---
redirect_to: '../../telemetry/index.md'
redirect_to: '../telemetry/index.md'
---
This document was moved to [another location](../../telemetry/index.md).
This document was moved to [another location](../telemetry/index.md).
......@@ -5,7 +5,7 @@ blocks of Ruby code. Method instrumentation is the primary form of
instrumentation with block-based instrumentation only being used when we want to
drill down to specific regions of code within a method.
Please refer to [Telemetry](../telemetry/index.md) if you are tracking product usage patterns.
Please refer to [Telemetry](telemetry/index.md) if you are tracking product usage patterns.
## Instrumenting Methods
......
# Telemetry Guide
At GitLab, we collect telemetry for the purpose of helping us build a better GitLab. Data about how GitLab is used is collected to better understand what parts of GitLab needs improvement and what features to build next. Telemetry also helps our team better understand the reasons why people use GitLab and with this knowledge we are able to make better product decisions.
We also encourage users to enable tracking, and we embrace full transparency with our tracking approach so it can be easily understood and trusted. By enabling tracking, users can:
- Contribute back to the wider community.
- Help GitLab improve on the product.
This documentation consists of three guides providing an overview of Telemetry at GitLab.
Telemetry Guide:
1. [Our tracking tools](#our-tracking-tools)
1. [What data can be tracked](#what-data-can-be-tracked)
1. [Telemetry systems overview](#telemetry-systems-overview)
[Usage Ping Guide](usage_ping.md)
1. [What is Usage Ping](usage_ping.md#what-is-usage-ping)
1. [Usage Ping payload](usage_ping.md#usage-ping-payload)
1. [Disabling Usage Ping](usage_ping.md#disabling-usage-ping)
1. [Usage Ping request flow](usage_ping.md#usage-ping-request-flow)
1. [How Usage Ping works](usage_ping.md#how-usage-ping-works)
1. [Implementing Usage Ping](usage_ping.md#implementing-usage-ping)
1. [Developing and testing usage ping](usage_ping.md#developing-and-testing-usage-ping)
[Snowplow Guide](snowplow.md)
1. [What is Snowplow](snowplow.md#what-is-snowplow)
1. [Snowplow schema](snowplow.md#snowplow-schema)
1. [Enabling Snowplow](snowplow.md#enabling-snowplow)
1. [Snowplow request flow](snowplow.md#snowplow-request-flow)
1. [Implementing Snowplow JS (Frontend) tracking](snowplow.md#implementing-snowplow-js-frontend-tracking)
1. [Implementing Snowplow Ruby (Backend) tracking](snowplow.md#implementing-snowplow-ruby-backend-tracking)
1. [Developing and testing Snowplow](snowplow.md#developing-and-testing-snowplow)
More useful links:
- [Telemetry Direction](https://about.gitlab.com/direction/telemetry/)
- [Data Analysis Process](https://about.gitlab.com/handbook/business-ops/data-team/#-data-analysis-process)
- [Data for Product Managers](https://about.gitlab.com/handbook/business-ops/data-team/data-for-product-managers/)
- [Data Infrastructure](https://about.gitlab.com/handbook/business-ops/data-team/data-infrastructure/)
## Our tracking tools
In this section we will explain the six different technologies we use to gather product usage data.
**Snowplow JS (Frontend)**
Snowplow is an enterprise-grade marketing and product analytics platform which helps track the way users engage with our website and application. [Snowplow JS](https://github.com/snowplow/snowplow/wiki/javascript-tracker) is a frontend tracker for client-side events.
**Snowplow Ruby (Backend)**
Snowplow is an enterprise-grade marketing and product analytics platform which helps track the way users engage with our website and application. [Snowplow Ruby](https://github.com/snowplow/snowplow/wiki/ruby-tracker) is a backend tracker for server-side events.
**Usage Ping**
Usage Ping is a method for GitLab Inc to collect usage data on a GitLab instance. Usage Ping is primarily composed of row counts for different tables in the instance’s database. By comparing these counts month over month (or week over week), we can get a rough sense for how an instance is using the different features within the product. This high-level data is used to help our product, support, and sales teams.
Read more about how this works in the [Usage Ping guide](usage_ping.md)
**Database import**
Database imports are full imports of data into GitLab's data warehouse. For GitLab.com, the PostgreSQL database is loaded into Snowflake data warehouse every 6 hours. For more details, see the [data team handbook](https://about.gitlab.com/handbook/business-ops/data-team/#extract-and-load).
**Log system**
System logs are the application logs generated from running the GitLab Rails application. For more details, see the [log system](../../administration/logs.md) and [logging infrastructure](https://gitlab.com/gitlab-com/runbooks/tree/master/logging/doc#logging-infrastructure-overview).
## What data can be tracked
Our different tracking tools allows us to track different types of events. The event types and examples of what data can be tracked are outlined below.
| Event Type | Snowplow JS (Frontend) | Snowplow Ruby (Backend) | Usage Ping | Database import | Log system |
| ------ | ------ | ------ | ------ | ------ | ------ |
| Database counts | ❌ | ❌ | ✅ | ✅ | ❌ |
| Pageview events | ✅ | ✅ | ❌ | ❌ | ❌ |
| UI events | ✅ | ❌ | ❌ | ❌ | ❌ |
| CRUD and API events | ❌ | ✅ | ❌ | ❌ | ❌ |
| Event funnels | ✅ | ✅ | ❌ | ❌ | ❌ |
| PostgreSQL Data | ❌ | ❌ | ❌ | ✅ | ❌ |
| Logs | ❌ | ❌ | ❌ | ❌ | ✅ |
| External services | ❌ | ❌ | ❌ | ❌ | ❌ |
**Database counts**
- How many Projects have been created by unique users
- How many users logged in the past 28 day
Database counts are row counts for different tables in an instance’s database. These are SQL count queries which have been filtered, grouped, or aggregated which provide high level usage data. The full list of available tables can be found in [structure.sql](https://gitlab.com/gitlab-org/gitlab/-/blob/master/db/structure.sql)
**Pageview events**
- How many sessions visited the /dashboard/groups page
**UI Events**
- How many sessions clicked on a button or link
- How many sessions closed a modal
UI events are any interface-driven actions from the browser including click data.
**CRUD or API events**
- How many Git pushes were made
- How many GraphQL queries were made
- How many requests were made to a Rails action or controller.
These are backend events that include the creation, read, update, deletion of records and other events that might be triggered from layers that aren't necessarily only available in the interface.
**Event funnels**
- How many sessions performed action A, B, then C
- What is our conversion rate from step A to B?
**PostgreSQL data**
These are raw database records which can be explored using business intelligence tools like Sisense. The full list of available tables can be found in [structure.sql](https://gitlab.com/gitlab-org/gitlab/-/blob/master/db/structure.sql)
**Logs**
These are raw logs such as the [Production logs](../../administration/logs.md#production_jsonlog), [API logs](../../administration/logs.md#api_jsonlog), or [Sidekiq logs](../../administration/logs.md#sidekiqlog). See the [overview of Logging Infrastructure](https://gitlab.com/gitlab-com/runbooks/tree/master/logging/doc#logging-infrastructure-overview) for more details.
**External services**
These are external services a GitLab instance interacts with such as an [external storage provider](../../administration/static_objects_external_storage.md) or an [external container registry](../../administration/packages/container_registry.md#use-an-external-container-registry-with-gitlab-as-an-auth-endpoint). These services must be able to send data back into a GitLab instance for data to be tracked.
## Telemetry systems overview
The systems overview is a simplified diagram showing the interactions between GitLab Inc and self-managed nstances.
![Telemetry_Overview](../img/telemetry_system_overview.png)
[Source file](https://app.diagrams.net/#G13DVpN-XnhWGz9tqReIj8pp1UE4ehk_EC)
### GitLab Inc
For Telemetry purposes, GitLab Inc has three major components:
1. [Data Infrastructure](https://about.gitlab.com/handbook/business-ops/data-team/data-infrastructure/): This contains everything managed by our data team including Sisense Dashboards for visualization, Snowflake for Data Warehousing, incoming data sources such as PostgreSQL Pipeline and S3 Bucket, and lastly our data collectors [GitLab.com's Snowplow Collector](https://about.gitlab.com/handbook/engineering/infrastructure/library/snowplow/) and GitLab's Versions Application.
1. GitLab.com: This is the production GitLab application which is made up of a Client and Server. On the Client or browser side, a Snowplow JS Tracker (Frontend) is used to track client-side events. On the Server or application side, a Snowplow Ruby Tracker (Backend) is used to track server-side events. The server also contains Usage Ping which leverages a PostgreSQL database and a Redis in-memory data store to report on usage data. Lastly, the server also contains System Logs which are generated from running the GitLab application.
1. [Monitoring infrastructure](https://about.gitlab.com/handbook/engineering/monitoring/): This is the infrastructure used to ensure GitLab.com is operating smoothly. System Logs are sent from GitLab.com to our monitoring infrastructure and collected by a FluentD collector. From FluentD, logs are either sent to long term Google Cloud Services cold storage via Stackdriver, or, they are sent to our Elastic Cluster via Cloud Pub/Sub which can be explored in real-time using Kibana
### Self-managed
For Telemetry purposes, self-managed instances have two major components:
1. Data infrastructure: Having a data infrastructure setup is optional on self-managed instances. If you'd like to collect Snowplow tracking events for your self-managed instance, you can setup your own self-managed Snowplow collector and configure your Snowplow events to point to your own collector.
1. GitLab: A self-managed GitLab instance contains all of the same components as GitLab.com mentioned above.
### Differences between GitLab Inc and Self-managed
As shown by the orange lines, on GitLab.com Snowplow JS, Snowplow Ruby, Usage Ping, and PostgreSQL database imports all flow into GitLab Inc's data fnfrastructure. However, on self-managed, only Usage Ping flows into GitLab Inc's data infrastructure.
As shown by the green lines, on GitLab.com system logs flow into GitLab Inc's monitoring infrastructure. On self-managed, there are no logs sent to GitLab Inc's monitoring infrastructure.
The differences between GitLab.com and self-managed are summarized below:
| Environment | Snowplow JS (Frontend) | Snowplow Ruby (Backend) | Usage Ping | Database import | Logs system |
| ------ | ------ | ------ | ------ | ------ | ------ |
| GitLab.com | ✅ | ✅ | ✅ | ✅ | ✅ |
| Self-Managed | ❌(1) | ❌(1) | ✅ | ❌ | ❌ |
Note (1): Snowplow JS and Snowplow Ruby are available on self-managed, however, the Snowplow Collector endpoint is set to a self-managed Snowplow Collector which GitLab Inc does not have access to.
# Snowplow Guide
This guide provides a details about how Snowplow works. It includes the following sections:
1. [What is Snowplow](#what-is-snowplow)
1. [Snowplow schema](#snowplow-schema)
1. [Enabling Snowplow](#enabling-snowplow)
1. [Snowplow request flow](#snowplow-request-flow)
1. [Implementing Snowplow JS (Frontend) tracking](#implementing-snowplow-js-frontend-tracking)
1. [Implementing Snowplow Ruby (Backend) tracking](#implementing-snowplow-ruby-backend-tracking)
1. [Developing and testing Snowplow](#developing-and-testing-snowplow)
For more information about Telemetry, see:
- [Telemetry Guide](index.md)
- [Usage Ping Guide](usage_ping.md)
More useful links:
- [Telemetry Direction](https://about.gitlab.com/direction/telemetry/)
- [Data Analysis Process](https://about.gitlab.com/handbook/business-ops/data-team/#-data-analysis-process)
- [Data for Product Managers](https://about.gitlab.com/handbook/business-ops/data-team/data-for-product-managers/)
- [Data Infrastructure](https://about.gitlab.com/handbook/business-ops/data-team/data-infrastructure/)
## What is Snowplow
Snowplow is an enterprise-grade marketing and product analytics platform which helps track the way users engage with our website and application.
From [Snowplow's documentation](https://github.com/snowplow/snowplow), Snowplow consists of six loosely-coupled sub-systems:
- **Trackers** fire Snowplow events. Currently Snowplow has 12 trackers, covering web, mobile, desktop, server and IoT
- **Collectors** receive Snowplow events from trackers. Currently we have three different event collectors, sinking events either to Amazon S3, Apache Kafka or Amazon Kinesis
- **Enrich** cleans up the raw Snowplow events, enriches them and puts them into storage. Currently we have a Hadoop-based enrichment process, and a Kinesis- or Kafka-based process
- **Storage** is where the Snowplow events live. Currently we store the Snowplow events in a flat file structure on S3, and in the Redshift and PostgreSQL databases
- **Data modeling** is where event-level data is joined with other data sets and aggregated into smaller data sets, and business logic is applied. This produces a clean set of tables which make it easier to perform analysis on the data. We have data models for Redshift and Looker
- **Analytics** are performed on the Snowplow events or on the aggregate tables.
![snowplow_flow](../img/snowplow_flow.png)
> ![snowplow_flow](../img/snowplow_flow.png)
## Snowplow schema
We currently have many definitions of Snowplow's schema. We have an active issue to [standardize this schema](https://gitlab.com/gitlab-org/gitlab/issues/207930) including the following definitions:
- Frontend and backend taxonomy as listed below
- [Feature instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy)
- [Self describing events](https://github.com/snowplow/snowplow/wiki/Custom-events#self-describing-events)
- [Iglu schema](https://gitlab.com/gitlab-org/iglu/)
- [Snowplow authored events](https://github.com/snowplow/snowplow/wiki/Snowplow-authored-events)
## Enabling Snowplow
Tracking can be enabled at:
- The instance level, which will enable tracking on both the frontend and backend layers.
- User level, though user tracking can be disabled on a per-user basis. GitLab tracking respects the [Do Not Track](https://www.eff.org/issues/do-not-track) standard, so any user who has enabled the Do Not Track option in their browser will also not be tracked from a user level.
We utilize Snowplow for the majority of our tracking strategy and it is enabled on GitLab.com. On a self-managed instance, Snowplow can be enabled by navigating to:
- **Admin Area > Settings > Integrations** in the UI.
- `admin/application_settings/integrations` in your browser.
The following configuration is required:
| Name | Value |
| ------------- | ------------------------- |
| Collector | `snowplow.trx.gitlab.net` |
| Site ID | `gitlab` |
| Cookie domain | `.gitlab.com` |
## Snowplow request flow
The following example shows a basic request/response flow between a Snowplow JS / Ruby Trackers on GitLab.com, [the GitLab.com Snowplow Collector](https://about.gitlab.com/handbook/engineering/infrastructure/library/snowplow/), GitLab's S3 Bucket, GitLab's Snowflake Data Warehouse, and Sisense.:
```mermaid
sequenceDiagram
participant Snowplow JS (Frontend)
participant Snowplow Ruby (Backend)
participant GitLab.com Snowplow Collector
participant S3 Bucket
participant Snowflake DW
participant Sisense Dashboards
Snowplow JS (Frontend) ->> GitLab.com Snowplow Collector: FE Tracking event
Snowplow Ruby (Backend) ->> GitLab.com Snowplow Collector: BE Tracking event
loop Process using Kinesis Stream
GitLab.com Snowplow Collector ->> GitLab.com Snowplow Collector: Log raw events
GitLab.com Snowplow Collector ->> GitLab.com Snowplow Collector: Enrich events
GitLab.com Snowplow Collector ->> GitLab.com Snowplow Collector: Write to disk
end
GitLab.com Snowplow Collector ->> S3 Bucket: Kinesis Firehose
S3 Bucket->>Snowflake DW: Import data
Snowflake DW->>Snowflake DW: Transform data using dbt
Snowflake DW->>Sisense Dashboards: Data available for querying
```
## Implementing Snowplow JS (Frontend) tracking
GitLab provides `Tracking`, an interface that wraps the [Snowplow JavaScript Tracker](https://github.com/snowplow/snowplow/wiki/javascript-tracker) for tracking custom events. There are a few ways to utilize tracking, but each generally requires at minimum, a `category` and an `action`. Additional data can be provided that adheres to our [Feature instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy).
| field | type | default value | description |
|:-----------|:-------|:---------------------------|:------------|
| `category` | string | document.body.dataset.page | Page or subsection of a page that events are being captured within. |
| `action` | string | 'generic' | Action the user is taking. Clicks should be `click` and activations should be `activate`, so for example, focusing a form field would be `activate_form_input`, and clicking a button would be `click_button`. |
| `data` | object | {} | Additional data such as `label`, `property`, `value`, and `context` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). |
### Tracking in HAML (or Vue Templates)
When working within HAML (or Vue templates) we can add `data-track-*` attributes to elements of interest. All elements that have a `data-track-event` attribute will automatically have event tracking bound on clicks.
Below is an example of `data-track-*` attributes assigned to a button:
```haml
%button.btn{ data: { track: { event: "click_button", label: "template_preview", property: "my-template" } } }
```
```html
<button class="btn"
data-track-event="click_button"
data-track-label="template_preview"
data-track-property="my-template"
/>
```
Event listeners are bound at the document level to handle click events on or within elements with these data attributes. This allows for them to be properly handled on rerendering and changes to the DOM, but it's important to know that because of the way these events are bound, click events shouldn't be stopped from propagating up the DOM tree. If for any reason click events are being stopped from propagating, you'll need to implement your own listeners and follow the instructions in [Tracking in raw JavaScript](#tracking-in-raw-javascript).
Below is a list of supported `data-track-*` attributes:
| attribute | required | description |
|:----------------------|:---------|:------------|
| `data-track-event` | 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 would be `activate_form_input` and clicking a button would be `click_button`. |
| `data-track-label` | false | The `label` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). |
| `data-track-property` | false | The `property` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). |
| `data-track-value` | false | The `value` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). If omitted, this will be the element's `value` property or an empty string. For checkboxes, the default value will be the element's checked attribute or `false` when unchecked. |
| `data-track-context` | false | The `context` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). |
### Tracking within Vue components
There's a tracking Vue mixin that can be used in components if more complex tracking is required. To use it, first import the `Tracking` library and request a mixin.
```javascript
import Tracking from '~/tracking';
const trackingMixin = Tracking.mixin({ label: 'right_sidebar' });
```
You can provide default options that will be passed along whenever an event is tracked from within your component. For instance, if all events within a component should be tracked with a given `label`, you can provide one at this time. Available defaults are `category`, `label`, `property`, and `value`. If no category is specified, `document.body.dataset.page` is used as the default.
You can then use the mixin normally in your component with the `mixin`, Vue declaration. The mixin also provides the ability to specify tracking options in `data` or `computed`. These will override any defaults and allows the values to be dynamic from props, or based on state.
```javascript
export default {
mixins: [trackingMixin],
// ...[component implementation]...
data() {
return {
expanded: false,
tracking: {
label: 'left_sidebar'
}
};
},
}
```
The mixin provides a `track` method that can be called within the template, or from component methods. An example of the whole implementation might look like the following.
```javascript
export default {
mixins: [Tracking.mixin({ label: 'right_sidebar' })],
data() {
return {
expanded: false,
};
},
methods: {
toggle() {
this.expanded = !this.expanded;
this.track('click_toggle', { value: this.expanded })
}
}
};
```
And if needed within the template, you can use the `track` method directly as well.
```html
<template>
<div>
<a class="toggle" @click.prevent="toggle">Toggle</a>
<div v-if="expanded">
<p>Hello world!</p>
<a @click.prevent="track('click_action')">Track an event</a>
</div>
</div>
</template>
```
### Tracking in raw JavaScript
Custom event tracking and instrumentation can be added by directly calling the `Tracking.event` static function. The following example demonstrates tracking a click on a button by calling `Tracking.event` manually.
```javascript
import Tracking from '~/tracking';
const button = document.getElementById('create_from_template_button');
button.addEventListener('click', () => {
Tracking.event('dashboard:projects:index', 'click_button', {
label: 'create_from_template',
property: 'template_preview',
value: 'rails',
});
})
```
### Tests and test helpers
In Jest particularly in vue tests, you can use the following:
```javascript
import { mockTracking } from 'helpers/tracking_helper';
describe('MyTracking', () => {
let spy;
beforeEach(() => {
spy = mockTracking('_category_', wrapper.element, jest.spyOn);
});
it('tracks an event when clicked on feedback', () => {
wrapper.find('.discover-feedback-icon').trigger('click');
expect(spy).toHaveBeenCalledWith('_category_', 'click_button', {
label: 'security-discover-feedback-cta',
property: '0',
});
});
});
```
In obsolete Karma tests it's used as below:
```javascript
import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper';
describe('my component', () => {
let trackingSpy;
beforeEach(() => {
trackingSpy = mockTracking('_category_', vm.$el, spyOn);
});
const triggerEvent = () => {
// action which should trigger a event
};
it('tracks an event when toggled', () => {
expect(trackingSpy).not.toHaveBeenCalled();
triggerEvent('a.toggle');
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_edit_button', {
label: 'right_sidebar',
property: 'confidentiality',
});
});
});
```
## Implementing Snowplow Ruby (Backend) tracking
GitLab provides `Gitlab::Tracking`, an interface that wraps the [Snowplow Ruby Tracker](https://github.com/snowplow/snowplow/wiki/ruby-tracker) for tracking custom events.
Custom event tracking and instrumentation can be added by directly calling the `GitLab::Tracking.event` class method, which accepts the following arguments:
| argument | type | default value | description |
|:-----------|:-------|:---------------------------|:------------|
| `category` | string | 'application' | Area or aspect of the application. This could be `HealthCheckController` or `Lfs::FileTransformer` for instance. |
| `action` | string | 'generic' | The action being taken, which can be anything from a controller action like `create` to something like an Active Record callback. |
| `data` | object | {} | Additional data such as `label`, `property`, `value`, and `context` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). These will be set as empty strings if you don't provide them. |
Tracking can be viewed as either tracking user behavior, or can be utilized for instrumentation to monitor and visual performance over time in an area or aspect of code.
For example:
```ruby
class Projects::CreateService < BaseService
def execute
project = Project.create(params)
Gitlab::Tracking.event('Projects::CreateService', 'create_project',
label: project.errors.full_messages.to_sentence,
value: project.valid?
)
end
end
```
### Performance
We use the [AsyncEmitter](https://github.com/snowplow/snowplow/wiki/Ruby-Tracker#52-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.
## Developing and testing Snowplow
There are several tools for developing and testing Snowplow Event
| Testing Tool | Frontend Tracking | Backend Tracking | Local Development Environment | Production Environment |
| ------ | ------ | ------ | ------ | ------ |
| Snowplow Analytics Debugger Chrome Extension | ✅ | ❌ | ✅ | ✅ |
| Snowplow Inspector Chrome Extension | ✅ | ❌ | ✅ | ✅ |
| Snowplow Micro | ✅ | ✅ | ✅ | ❌ |
| Snowplow Mini | ✅ | ✅ | ❌ | ✅ |
### Snowplow Analytics Debugger Chrome Extension
Snowplow Analytics Debugger is a browser extension for testing frontend events. This works on production, staging and local development environments.
1. Install [Snowplow Analytics Debugger](https://chrome.google.com/webstore/detail/snowplow-analytics-debugg/jbnlcgeengmijcghameodeaenefieedm) chrome browser extension
1. Open Chrome DevTools to the Snowplow Analytics Debugger tab
1. Learn more at [Igloo Analytics](https://www.iglooanalytics.com/blog/snowplow-analytics-debugger-chrome-extension.html)
### Snowplow Inspector Chrome Extension
Snowplow Inspector Chrome Extension is a browser extension for testing frontend events. This works on production, staging and local development environments.
1. Install [Snowplow Inspector](https://chrome.google.com/webstore/detail/snowplow-inspector/maplkdomeamdlngconidoefjpogkmljm?hl=en)
1. Open the chrome extension by pressing the Snowplow Inspector icon beside the address bar
1. Click around on a webpage with Snowplow and you should see JavaScript events firing in the inspector window.
### Snowplow Micro
Snowplow Micro is a very small version of a full Snowplow data collection pipeline: small enough that it can be launched by a test suite. Events can be recorded into Snowplow Micro just as they can a full Snowplow pipeline. Micro then exposes an API that can be queried.
Snowplow Micro is a docker-based solution for testing frontend and backend events in a local development environment. You need to modify GDK using the instructions below to set this up.
- Read [Introducing Snowplow Micro](https://snowplowanalytics.com/blog/2019/07/17/introducing-snowplow-micro/)
- Look at the [Snowplow Micro repo](https://github.com/snowplow-incubator/snowplow-micro)
- Watch our [installation guide recording](https://www.youtube.com/watch?v=OX46fo_A0Ag)
1. Install [Snowplow Micro](https://github.com/snowplow-incubator/snowplow-micro)
``` bash
docker run --mount type=bind,source=$(pwd)/example,destination=/config -p 9090:9090 snowplow/snowplow-micro:latest --collector-config /config/micro.conf --iglu /config/iglu.json
```
1. Install snowplow micro by cloning the settings in [this project](https://gitlab.com/a_akgun/snowplow-micro).
``` bash
git clone git@gitlab.com:a_akgun/snowplow-micro.git
./snowplow-micro.sh
```
1. Update port in SQL (needed to set 9090)
``` bash
gdk psql -d gitlabhq_development
update application_settings set snowplow_collector_hostname='localhost:9090', snowplow_enabled=true, snowplow_cookie_domain='.gitlab.com';
```
1. Update `app/assets/javascripts/tracking.js` to [remove this line](https://gitlab.com/snippets/1918635):
``` javascript
forceSecureTracker: true
```
1. Update `lib/gitlab/tracking.rb` to [add these lines](https://gitlab.com/snippets/1918635):
``` ruby
protocol: 'http',
port: 9090,
```
1. Update `lib/gitlab/tracking.rb` to [change async emitter from https to http](https://gitlab.com/snippets/1918635):
``` ruby
SnowplowTracker::AsyncEmitter.new(Gitlab::CurrentSettings.snowplow_collector_hostname, protocol: 'http'),
```
1. Enable Snowplow in the admin area, Settings::Integrations::Snowplow to point to:
`http://localhost:3000/admin/application_settings/integrations#js-snowplow-settings`
1. `gdk restart`
1. Send a test Snowplow event from the Rails console
``` ruby
Gitlab::Tracking.self_describing_event('iglu:com.gitlab/pageview_context/jsonschema/1-0-0', { page_type: MY_TYPE' }, context: nil )
```
### Snowplow Mini
[Snowplow Mini](https://github.com/snowplow/snowplow-mini) is an easily-deployable, single-instance version of Snowplow.
Snowplow Mini can be used for testing frontend and backend events on a production, staging and local development environment.
For GitLab.com, we are currently setting up a [QA and Testing environment](https://gitlab.com/gitlab-org/telemetry/-/issues/266) using Snowplow Mini.
# Usage Ping Guide
> - [Introduced][ee-557] in GitLab Enterprise Edition 8.10.
> - More statistics [were added][ee-735] in GitLab Enterprise Edition 8.12.
> - [Moved to GitLab Core][ce-23361] in 9.1.
> - More statistics [were added][ee-6602] in GitLab Ultimate 11.2.
This guide provides a details about how usage ping works. It includes the following sections:
1. [What is Usage Ping](#what-is-usage-ping)
1. [Usage Ping payload](#usage-ping-payload)
1. [Disabling Usage Ping](#disabling-usage-ping)
1. [Usage Ping request flow](#usage-ping-request-flow)
1. [How Usage Ping works](#how-usage-ping-works)
1. [Implementing Usage Ping](#implementing-usage-ping)
1. [Developing and testing usage ping](#developing-and-testing-usage-ping)
For more information about Telemetry, see:
- [Telemetry Guide](index.md)
- [Snowplow Guide](snowplow.md)
More useful links:
- [Telemetry Direction](https://about.gitlab.com/direction/telemetry/)
- [Data Analysis Process](https://about.gitlab.com/handbook/business-ops/data-team/#-data-analysis-process)
- [Data for Product Managers](https://about.gitlab.com/handbook/business-ops/data-team/data-for-product-managers/)
- [Data Infrastructure](https://about.gitlab.com/handbook/business-ops/data-team/data-infrastructure/)
## What is Usage Ping
- GitLab sends a weekly payload containing usage data to GitLab Inc. The usage ping uses high-level data to help our product, support, and sales teams. It does not send any project names, usernames, or any other specific data. The information from the usage ping is not anonymous, it is linked to the hostname of the instance. Sending usage ping is optional, and any instance can disable analytics.
- The usage data is primarily composed of row counts for different tables in the instance’s database. By comparing these counts month over month (or week over week), we can get a rough sense for how an instance is using the different features within the product.
- Usage ping is important to GitLab as we use it to calculate our and Stage Monthly Active Users (SMAU) which helps us measure the success of our stages and features.
- Once usage ping is enabled, GitLab will gather data from the other instances and will be able to show usage statistics of your instance to your users.
### Why Should We Enable Usage Ping?
- The main purpose of Usage Ping is to build a better GitLab. Data about how GitLab is used is collected to better understand feature/stage adoption and usage, which helps us understand how GitLab is adding value and helps our team better understand the reasons why people use GitLab and with this knowledge we are able to make better product decisions.
- As a benefit of having the usage ping active, GitLab lets you analyze the users’ activities over time of your GitLab installation.
- As a benefit of having the usage ping active, GitLab provides you with The DevOps Score,which gives you an overview of your entire instance’s adoption of Concurrent DevOps from planning to monitoring.
- You will get better, more proactive support. (assuming that our TAMs and support organization used the data to deliver more value)
- You will get insight and advice into how to get the most value out of your investment in GitLab. Wouldn't you want to know that a number of features or values are not being adopted in your organization?
- You get a report that illustrates how you compare against other similar organizations (anonymized), with specific advice and recommendations on how to improve your DevOps processes.
### Limitations
- Usage Ping does not track frontend events things like page views, link clicks, or user sessions and only focuses on aggregated backend events.
- Because of these limitations we recommend instrumenting your products with Snowplow for more detailed analytics on GitLab.com and use Usage Ping to track aggregated backend events on self-managed.
## Usage Ping payload
You can view the exact JSON payload sent to GitLab Inc. in the administration panel. To view the payload:
1. Navigate to the **Admin Area > Settings > Metrics and profiling**.
1. Expand the **Usage statistics** section.
1. Click the **Preview payload** button.
Here is an example of the payload structure
``` json
{
"uuid": "0000000-0000-0000-0000-000000000000",
"hostname": "example.com",
"version": "12.10.0-pre",
"installation_type": "omnibus-gitlab",
"active_user_count": 999,
"recorded_at": "2020-04-17T07:43:54.162+00:00",
"edition": "EEU",
"license_md5": "00000000000000000000000000000000",
"license_id": null,
"historical_max_users": 999,
"licensee": {
"Name": "ABC, Inc.",
"Email": "email@example.com",
"Company": "ABC, Inc."
},
"license_user_count": 999,
"license_starts_at": "2020-01-01",
"license_expires_at": "2021-01-01",
"license_plan": "ultimate",
"license_add_ons": {
},
"license_trial": false,
"counts": {
"assignee_lists": 999,
"boards": 999,
"ci_builds": 999,
...
},
"container_registry_enabled": true,
"dependency_proxy_enabled": false,
"gitlab_shared_runners_enabled": true,
"gravatar_enabled": true,
"influxdb_metrics_enabled": true,
"ldap_enabled": false,
"mattermost_enabled": false,
"omniauth_enabled": true,
"prometheus_metrics_enabled": false,
"reply_by_email_enabled": "incoming+%{key}@incoming.gitlab.com",
"signup_enabled": true,
"web_ide_clientside_preview_enabled": true,
"ingress_modsecurity_enabled": true,
"projects_with_expiration_policy_disabled": 999,
"projects_with_expiration_policy_enabled": 999,
...
"elasticsearch_enabled": true,
"license_trial_ends_on": null,
"geo_enabled": false,
"git": {
"version": {
"major": 2,
"minor": 26,
"patch": 1
}
},
"gitaly": {
"version": "12.10.0-rc1-93-g40980d40",
"servers": 56,
"filesystems": [
"EXT_2_3_4"
]
},
"gitlab_pages": {
"enabled": true,
"version": "1.17.0"
},
"database": {
"adapter": "postgresql",
"version": "9.6.15"
},
"app_server": {
"type": "console"
},
"avg_cycle_analytics": {
"issue": {
"average": 999,
"sd": 999,
"missing": 999
},
"plan": {
"average": null,
"sd": 999,
"missing": 999
},
"code": {
"average": null,
"sd": 999,
"missing": 999
},
"test": {
"average": null,
"sd": 999,
"missing": 999
},
"review": {
"average": null,
"sd": 999,
"missing": 999
},
"staging": {
"average": null,
"sd": 999,
"missing": 999
},
"production": {
"average": null,
"sd": 999,
"missing": 999
},
"total": 999
},
"usage_activity_by_stage": {
"configure": {
"project_clusters_enabled": 999,
...
},
"create": {
"merge_requests": 999,
...
},
"manage": {
"events": 999,
...
},
"monitor": {
"clusters": 999,
...
},
"package": {
"projects_with_packages": 999
},
"plan": {
"issues": 999,
...
},
"release": {
"deployments": 999,
...
},
"secure": {
"user_container_scanning_jobs": 999,
...
},
"verify": {
"ci_builds": 999,
...
}
},
"usage_activity_by_stage_monthly": {
"configure": {
"project_clusters_enabled": 999,
...
},
"create": {
"merge_requests": 999,
...
},
"manage": {
"events": 999,
...
},
"monitor": {
"clusters": 999,
...
},
"package": {
"projects_with_packages": 999
},
"plan": {
"issues": 999,
...
},
"release": {
"deployments": 999,
...
},
"secure": {
"user_container_scanning_jobs": 999,
...
},
"verify": {
"ci_builds": 999,
...
}
}
}
```
## Disabling usage ping
The usage ping is opt-out. If you want to deactivate this feature, go to the Settings page of your administration panel and uncheck the Usage Ping checkbox.
To disable the usage ping and prevent it from being configured in future through the administration panel, Omnibus installs can set the following in [`gitlab.rb`](https://docs.gitlab.com/omnibus/settings/configuration.html#configuration-options):
```ruby
gitlab_rails['usage_ping_enabled'] = false
```
And source installs can set the following in `gitlab.yml`:
```yaml
production: &base
# ...
gitlab:
# ...
usage_ping_enabled: false
```
## Usage Ping Request Flow
The following example shows a basic request/response flow between a GitLab Instance, the Versions Application, the License Application, Salesforce, GitLab's S3 Bucket, GitLab's Snowflake Data Warehouse, and Sisense.:
```mermaid
sequenceDiagram
participant GitLab Instance
participant Versions Application
participant Licenses Application
participant Salesforce
participant S3 Bucket
participant Snowflake DW
participant Sisense Dashboards
GitLab Instance->>Versions Application: Send Usage Ping
loop Process usage data
Versions Application->>Versions Application: Parse usage data
Versions Application->>Versions Application: Write to database
Versions Application->>Versions Application: Update license ping time
end
loop Process data for Salesforce
Versions Application-xLicenses Application: Request Zuora subscription id
Licenses Application-xVersions Application: Zuora subscription id
Versions Application-xSalesforce: Request Zuora account id by Zuora subscription id
Salesforce-xVersions Application: Zuora account id
Versions Application-xSalesforce: Usage data for the Zuora account
end
Versions Application->>S3 Bucket: Export Versions database
S3 Bucket->>Snowflake DW: Import data
Snowflake DW->>Snowflake DW: Transform data using dbt
Snowflake DW->>Sisense Dashboards: Data available for querying
Versions Application->>GitLab Instance: DevOps Score (Conversational Development Index)
```
## How Usage Ping works
1. The Usage Ping [cron job](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/workers/gitlab_usage_ping_worker.rb#L30) is set in Sidekiq to run weekly.
1. When the cron job runs, it calls [GitLab::UsageData.to_json](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/services/submit_usage_ping_service.rb#L22).
1. GitLab::UsageData.to_json [cascades down](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data.rb#L22) to ~400+ other counter method calls.
1. The response of all methods calls are [merged together](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data.rb#L14) into a single JSON payload in GitLab::UsageData.to_json.
1. The JSON payload is then [posted to the Versions application]( https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/services/submit_usage_ping_service.rb#L20).
## Implementing Usage Ping
Usage Ping consists of four types of counters which are all found in `usage_data.rb`:
- **Ordinary Batch Counters:** Simple count of a given ActiveRecord_Relation
- **Distinct Batch Counters:** Distinct count of a given ActiveRecord_Relation on given column
- **Alternative Counters:** Used for settings and configurations
- **Redis Counters:** Used for in-memory counts. This method is being deprecated due to data inaccuracies and will be replaced with a persistent method.
Note: Only use the provided counter methods. Each counter method contains a built in fail safe to isolate each counter to avoid breaking the entire Usage Ping.
### Why batch counting
For large tables, PostgreSQL can take a long time to count rows due to MVCC [(Multi-version Concurrency Control)](https://en.wikipedia.org/wiki/Multiversion_concurrency_control). Batch counting is a counting method where a single large query is broken into multiple smaller queries. For example, instead of a single query querying 1,000,000 records, with batch counting, you can execute 100 queries of 10,000 records each. Batch counting is useful for avoiding database timeouts as each batch query is significantly shorter than one single long running query.
For GitLab.com, there are extremely large tables with 15 second query timeouts, so, we use batch counting to avoid encountering timeouts. Here are the sizes of some GitLab.com tables:
| Table | Row counts in millions |
| ------ | ------ |
| merge_request_diff_commits | 2280 |
| ci_build_trace_sections | 1764 |
| merge_request_diff_files | 1082 |
| events | 514 |
There are two batch counting methods provided, `Ordinary Batch Counters` and `Distinct Batch Counters`. Batch counting requires indexes on columns to calculate max, min, and range queries. In some cases, a specialized index may need to be added on the columns involved in a counter.
### Ordinary Batch Counters
Handles `ActiveRecord::StatementInvalid` error
Simple count of a given ActiveRecord_Relation
Method: `count(relation, column = nil, batch: true, start: nil, finish: nil)`
Arguments:
- `relation` the ActiveRecord_Relation to perform the count
- `column` the column to perform the count on, by default is the primary key
- `batch`: default `true` in order to use batch counting
- `start`: custom start of the batch counting in order to avoid complex min calculations
- `end`: custom end of the batch counting in order to avoid complex min calculations
Examples:
```ruby
count(User.active)
count(::Clusters::Cluster.aws_installed.enabled, :cluster_id)
count(::Clusters::Cluster.aws_installed.enabled, :cluster_id, start: ::Clusters::Cluster.minimum(:id), finish: ::Clusters::Cluster.maximum(:id))
```
### Distinct Batch Counters
Handles `ActiveRecord::StatementInvalid` error
Distinct count of a given ActiveRecord_Relation on given column
Method: `distinct_count(relation, column = nil, batch: true, start: nil, finish: nil)`
Arguments:
- `relation` the ActiveRecord_Relation to perform the count
- `column` the column to perform the distinct count, by default is the primary key
- `batch`: default `true` in order to use batch counting
- `start`: custom start of the batch counting in order to avoid complex min calculations
- `end`: custom end of the batch counting in order to avoid complex min calculations
Examples:
```ruby
distinct_count(::Project, :creator_id)
distinct_count(::Note.with_suggestions.where(time_period), :author_id, start: ::User.minimum(:id), finish: ::User.maximum(:id))
distinct_count(::Clusters::Applications::CertManager.where(time_period).available.joins(:cluster), 'clusters.user_id')
```
### Redis Counters
Handles `::Redis::CommandError` and `Gitlab::UsageDataCounters::BaseCounter::UnknownEvent`
returns -1 when a block is sent or hash with all values -1 when a `counter(Gitlab::UsageDataCounters)` is sent
different behavior due to 2 different implementations of Redis counter
Method: `redis_usage_data(counter, &block)`
Arguments:
- `counter`: a counter from `Gitlab::UsageDataCounters`, that has `fallback_totals` method implemented
- or a `block`: wich is evaluated
Example of usage:
```ruby
redis_usage_data(Gitlab::UsageDataCounters::WikiPageCounter)
redis_usage_data { ::Gitlab::UsageCounters::PodLogs.usage_totals[:total] }
```
Note that Redis counters are in the [process of being deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/216330) and you should instead try to use Snowplow events instead. We're in the process of building [self-managed event tracking](https://gitlab.com/gitlab-org/telemetry/-/issues/373) and once this is available, we will convert all Redis counters into Snowplow events.
### Alternative Counters
Handles `StandardError` and fallbacks into -1 this way not all measures fail if we encounter one exception.
Mainly used for settings and configurations.
Method: `alt_usage_data(value = nil, fallback: -1, &block)`
Arguments:
- `value`: a simple static value in wich case the value is simply returned.
- or a `block`: wich is evaluated
- `fallback: -1`: the common value used for any metrics that are failing.
Example of usage:
```ruby
alt_usage_data { Gitlab::VERSION }
alt_usage_data { Gitlab::CurrentSettings.uuid }
alt_usage_data(999)
```
## Developing and testing Usage Ping
### 1. Use your Rails console to manually test counters
```ruby
# count
Gitlab::UsageData.count(User.active)
Gitlab::UsageData.count(::Clusters::Cluster.aws_installed.enabled, :cluster_id)
# count distinct
Gitlab::UsageData.distinct_count(::Project, :creator_id)
Gitlab::UsageData.distinct_count(::Note.with_suggestions.where(time_period), :author_id, start: ::User.minimum(:id), finish: ::User.maximum(:id))
```
### 2. Generate the SQL query
Your Rails console will give back the generated SQL queries.
Example:
```ruby
pry(main)> Gitlab::UsageData.count(User.active)
(0.4ms) SELECT "features"."key" FROM "features"
(0.7ms) SELECT MIN("users"."id") FROM "users" WHERE ("users"."state" IN ('active')) AND (ghost IS NOT TRUE) AND ("users"."user_type" IS NULL OR "users"."user_type" NOT IN (2, 1, 3))
(0.6ms) SELECT MAX("users"."id") FROM "users" WHERE ("users"."state" IN ('active')) AND (ghost IS NOT TRUE) AND ("users"."user_type" IS NULL OR "users"."user_type" NOT IN (2, 1, 3))
(0.5ms) SELECT COUNT("users"."id") FROM "users" WHERE ("users"."state" IN ('active')) AND (ghost IS NOT TRUE) AND ("users"."user_type" IS NULL OR "users"."user_type" NOT IN (2, 1, 3)) AND "users"."id" BETWEEN 0 AND 99999
```
### 3. Optimize queries with #database-lab
Paste the SQL query into `#database-lab` to see how the query performs at scale.
- #database-lab is a Slack channel which uses a production-sized environment to test your queries
- GitLab.com’s production database has a 15 second timeout.
- For each query we require an execution time of under 1 second due do cold caches which can 10x this time.
- Add a specialized index on columns involved to reduce your the execution time.
In order to have an understanding of the queries execution we add in the MR description the following information
For counters that have a `time_period` test and add information for both cases.
- with `time_period = {}` for all time period
- and `time_period = { created_at: 28.days.ago..Time.current }` for last 28 days period
Execution plan and query time before and after optimization
Using database-lab and [explain.depesz.com](https://explain.depesz.com/) see more details in [database review guide](../database_review.md#preparation-when-adding-or-modifying-queries)
Query generated for the index and time
Using database-lab
Migration output for up and down execution
Examples of query optimization work:
- [Example 1](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/26445)
- [Example 2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/26871)
### 4. Ask for a Telemetry Review
On GitLab.com, we have DangerBot setup to monitor Telemetry related files and DangerBot will recommend a Telemetry review. Simply `@gitlab-org/growth/telemetry/engineers` in your MR for a review.
# Event tracking
---
redirect_to: '../development/telemetry/index.md'
---
At GitLab, we encourage event tracking so we can iterate on and improve the project and user experience.
We do this by running experiments, and collecting analytics for features and feature variations. This is:
- So we generally know engagement.
- A way to approach A/B testing.
As developers, we should attempt to add tracking and instrumentation where possible. This enables the Product team to better understand:
- User engagement.
- Usage patterns.
- Other metrics that can potentially be improved on.
To maintain consistency and not adversely effect performance, we have some basic tracking functionality exposed at both the frontend and backend layers that can be utilized while building new features or updating existing features.
We also encourage users to enable tracking, and we embrace full transparency with our tracking approach so it can be easily understood and trusted. By enabling tracking, users can:
- Contribute back to the wider community.
- Help GitLab improve on the product.
## Implementing tracking
Event tracking can be implemented on either the frontend or the backend layers, and each can be approached slightly differently since they have slightly different concerns.
In GitLab, many actions can be initiated via the web interface, but they can also be initiated via an API client (an iOS applications is a good example of this), or via `git` directly. Crucially, this means that tracking should be considered holistically for the feature that's being instrumented.
The data team should be involved when defining analytics and can be consulted when coming up with ways of presenting data that's being tracked. This allows our event data to be considered carefully and presented in ways that may reveal details about user engagement that may not be fully understood or interactions where we can make improvements. You can [contact the data team](https://about.gitlab.com/handbook/business-ops/data-team/#contact-us) and consult with them when defining tracking strategies.
### Frontend
Generally speaking, the frontend can track user actions and events, like:
- Clicking links or buttons.
- Submitting forms.
- Other typically interface-driven actions.
### Backend
From the backend, the events that are tracked will likely consist of things like the creation or deletion of records and other events that might be triggered from layers that aren't necessarily only available in the interface.
Also, see [Instrumenting Ruby code](../development/instrumentation.md) if you are instrumenting application performance metrics for Ruby code.
## Enabling tracking
Tracking can be enabled at:
- The instance level, which will enable tracking on both the frontend and backend layers.
- User level, though user tracking can be disabled on a per-user basis. GitLab tracking respects the [Do Not Track](https://www.eff.org/issues/do-not-track) standard, so any user who has enabled the Do Not Track option in their browser will also not be tracked from a user level.
We utilize Snowplow for the majority of our tracking strategy, and it can be enabled by navigating to:
- **Admin Area > Settings > Integrations** in the UI.
- `admin/application_settings/integrations` in your browser.
The following configuration is required:
| Name | Value |
| ------------- | ------------------------- |
| Collector | `snowplow.trx.gitlab.net` |
| Site ID | `gitlab` |
| Cookie domain | `.gitlab.com` |
Once enabled, tracking events can be inspected locally by either:
- Looking at the network panel of the browser's development tools
- Using the [Snowplow Chrome Extension](https://chrome.google.com/webstore/detail/snowplow-inspector/maplkdomeamdlngconidoefjpogkmljm).
This document was moved to [another location](../development/telemetry/index.md).
# Snowplow tracking guide
---
redirect_to: '../development/telemetry/index.md'
---
## Frontend tracking
GitLab provides `Tracking`, an interface that wraps the [Snowplow JavaScript Tracker](https://github.com/snowplow/snowplow/wiki/javascript-tracker) for tracking custom events. There are a few ways to utilize tracking, but each generally requires at minimum, a `category` and an `action`. Additional data can be provided that adheres to our [Feature instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy).
| field | type | default value | description |
|:-----------|:-------|:---------------------------|:------------|
| `category` | string | document.body.dataset.page | Page or subsection of a page that events are being captured within. |
| `action` | string | 'generic' | Action the user is taking. Clicks should be `click` and activations should be `activate`, so for example, focusing a form field would be `activate_form_input`, and clicking a button would be `click_button`. |
| `data` | object | {} | Additional data such as `label`, `property`, `value`, and `context` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). |
### Tracking in HAML (or Vue Templates)
When working within HAML (or Vue templates) we can add `data-track-*` attributes to elements of interest. All elements that have a `data-track-event` attribute will automatically have event tracking bound on clicks.
Below is an example of `data-track-*` attributes assigned to a button:
```haml
%button.btn{ data: { track_event: "click_button", track_label: "template_preview", track_property: "my-template" } }
```
```html
<button class="btn"
data-track-event="click_button"
data-track-label="template_preview"
data-track-property="my-template"
/>
```
Event listeners are bound at the document level to handle click events on or within elements with these data attributes. This allows for them to be properly handled on rerendering and changes to the DOM, but it's important to know that because of the way these events are bound, click events shouldn't be stopped from propagating up the DOM tree. If for any reason click events are being stopped from propagating, you'll need to implement your own listeners and follow the instructions in [Tracking in raw JavaScript](#tracking-in-raw-javascript).
Below is a list of supported `data-track-*` attributes:
| attribute | required | description |
|:----------------------|:---------|:------------|
| `data-track-event` | 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 would be `activate_form_input` and clicking a button would be `click_button`. |
| `data-track-label` | false | The `label` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). |
| `data-track-property` | false | The `property` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). |
| `data-track-value` | false | The `value` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). If omitted, this will be the elements `value` property or an empty string. For checkboxes, the default value will be the element's checked attribute or `false` when unchecked. |
| `data-track-context` | false | The `context` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). |
### Tracking within Vue components
There's a tracking Vue mixin that can be used in components if more complex tracking is required. To use it, first import the `Tracking` library and request a mixin.
```javascript
import Tracking from '~/tracking';
const trackingMixin = Tracking.mixin({ label: 'right_sidebar' });
```
You can provide default options that will be passed along whenever an event is tracked from within your component. For instance, if all events within a component should be tracked with a given `label`, you can provide one at this time. Available defaults are `category`, `label`, `property`, and `value`. If no category is specified, `document.body.dataset.page` is used as the default.
You can then use the mixin normally in your component with the `mixin`, Vue declaration. The mixin also provides the ability to specify tracking options in `data` or `computed`. These will override any defaults and allows the values to be dynamic from props, or based on state.
```javascript
export default {
mixins: [trackingMixin],
// ...[component implementation]...
data() {
return {
expanded: false,
tracking: {
label: 'left_sidebar'
}
};
},
}
```
The mixin provides a `track` method that can be called within the template, or from component methods. An example of the whole implementation might look like the following.
```javascript
export default {
mixins: [Tracking.mixin({ label: 'right_sidebar' })],
data() {
return {
expanded: false,
};
},
methods: {
toggle() {
this.expanded = !this.expanded;
this.track('click_toggle', { value: this.expanded })
}
}
};
```
And if needed within the template, you can use the `track` method directly as well.
```html
<template>
<div>
<a class="toggle" @click.prevent="toggle">Toggle</a>
<div v-if="expanded">
<p>Hello world!</p>
<a @click.prevent="track('click_action')">Track an event</a>
</div>
</div>
</template>
```
### Tracking in raw JavaScript
Custom event tracking and instrumentation can be added by directly calling the `Tracking.event` static function. The following example demonstrates tracking a click on a button by calling `Tracking.event` manually.
```javascript
import Tracking from '~/tracking';
const button = document.getElementById('create_from_template_button');
button.addEventListener('click', () => {
Tracking.event('dashboard:projects:index', 'click_button', {
label: 'create_from_template',
property: 'template_preview',
value: 'rails',
});
})
```
### Tests and test helpers
In Jest particularly in vue tests, you can use the following:
```javascript
import { mockTracking } from 'helpers/tracking_helper';
describe('MyTracking', () => {
let spy;
beforeEach(() => {
spy = mockTracking('_category_', wrapper.element, jest.spyOn);
});
it('tracks an event when clicked on feedback', () => {
wrapper.find('.discover-feedback-icon').trigger('click');
expect(spy).toHaveBeenCalledWith('_category_', 'click_button', {
label: 'security-discover-feedback-cta',
property: '0',
});
});
});
```
In obsolete Karma tests it's used as below:
```javascript
import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper';
describe('my component', () => {
let trackingSpy;
beforeEach(() => {
trackingSpy = mockTracking('_category_', vm.$el, spyOn);
});
const triggerEvent = () => {
// action which should trigger a event
};
it('tracks an event when toggled', () => {
expect(trackingSpy).not.toHaveBeenCalled();
triggerEvent('a.toggle');
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_edit_button', {
label: 'right_sidebar',
property: 'confidentiality',
});
});
});
```
## Backend tracking
GitLab provides `Gitlab::Tracking`, an interface that wraps the [Snowplow Ruby Tracker](https://github.com/snowplow/snowplow/wiki/ruby-tracker) for tracking custom events.
### Tracking in Ruby
Custom event tracking and instrumentation can be added by directly calling the `GitLab::Tracking.event` class method, which accepts the following arguments:
| argument | type | default value | description |
|:-----------|:-------|:---------------------------|:------------|
| `category` | string | 'application' | Area or aspect of the application. This could be `HealthCheckController` or `Lfs::FileTransformer` for instance. |
| `action` | string | 'generic' | The action being taken, which can be anything from a controller action like `create` to something like an Active Record callback. |
| `data` | object | {} | Additional data such as `label`, `property`, `value`, and `context` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). These will be set as empty strings if you don't provide them. |
Tracking can be viewed as either tracking user behavior, or can be utilized for instrumentation to monitor and visual performance over time in an area or aspect of code.
For example:
```ruby
class Projects::CreateService < BaseService
def execute
project = Project.create(params)
Gitlab::Tracking.event('Projects::CreateService', 'create_project',
label: project.errors.full_messages.to_sentence,
value: project.valid?
)
end
end
```
### Performance
We use the [AsyncEmitter](https://github.com/snowplow/snowplow/wiki/Ruby-Tracker#52-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.
This document was moved to [another location](../development/telemetry/index.md).
......@@ -35,7 +35,7 @@ Access the default page for admin area settings by navigating to
| [PlantUML](../../../administration/integration/plantuml.md#gitlab) | Allow rendering of PlantUML diagrams in Asciidoc documents. |
| [Slack application](../../../user/project/integrations/gitlab_slack_application.md#configuration) **(FREE ONLY)** | Slack integration allows you to interact with GitLab via slash commands in a chat window. This option is only available on GitLab.com, though it may be [available for self-managed instances in the future](https://gitlab.com/gitlab-org/gitlab/-/issues/28164). |
| [Third party offers](third_party_offers.md) | Control the display of third party offers. |
| [Snowplow](../../../telemetry/index.md#enabling-tracking) | Configure the Snowplow integration. |
| [Snowplow](../../../development/telemetry/snowplow.md) | Configure the Snowplow integration. |
| [Google GKE](../../project/clusters/add_gke_clusters.md) | Google GKE integration allows you to provision GKE clusters from GitLab. |
| [Amazon EKS](../../project/clusters/add_eks_clusters.md) | Amazon EKS integration allows you to provision EKS clusters from GitLab. |
......
......@@ -58,75 +58,7 @@ sequenceDiagram
## Usage Ping **(CORE ONLY)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/557) in GitLab Enterprise Edition 8.10.
> - More statistics [were added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/735) in GitLab Enterprise Edition 8.12.
> - [Moved to GitLab Core](https://gitlab.com/gitlab-org/gitlab-foss/issues/23361) in 9.1.
> - More statistics [were added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6602) in GitLab Ultimate 11.2.
GitLab sends a weekly payload containing usage data to GitLab Inc. The usage
ping uses high-level data to help our product, support, and sales teams. It does
not send any project names, usernames, or any other specific data. The
information from the usage ping is not anonymous, it is linked to the hostname
of the instance.
You can view the exact JSON payload in the administration panel. To view the payload:
1. Navigate to the **Admin Area > Settings > Metrics and profiling**.
1. Expand the **Usage statistics** section.
1. Click the **Preview payload** button.
You can see how [the usage ping data maps to different stages of the product](https://gitlab.com/gitlab-data/analytics/blob/master/transform/snowflake-dbt/data/version_usage_stats_to_stage_mappings.csv).
Usage ping is important to GitLab as we use it to calculate our [Action Monthly Active Users (AMAU)](https://about.gitlab.com/handbook/product/metrics/#action-monthly-active-users-amau) which helps us measure the success of our features.
### Request flow example
The following example shows a basic request/response flow between the self-managed GitLab instance, GitLab Version Application,
GitLab License Application and Salesforce:
```mermaid
sequenceDiagram
participant GitLab instance
participant Version Application
participant License Application
participant Salesforce
GitLab instance->>Version Application: Usage Ping data
loop Process Usage Data
Version Application->>Version Application: Parse Usage Data
Version Application->>Version Application: Record Usage Data
Version Application->>Version Application: Update license ping time
end
Version Application-xLicense Application: Request Zuora subscription id
License Application-xVersion Application: Zuora subscription id
Version Application-xSalesforce: Request Zuora account id by Zuora subscription id
Salesforce-xVersion Application: Zuora account id
Version Application-xSalesforce: Usage data for the Zuora account
Version Application->>GitLab instance: Conversational Development Index
```
### Deactivate the usage ping
The usage ping is opt-out. If you want to deactivate this feature, go to
the Settings page of your administration panel and uncheck the Usage ping
checkbox.
To disable the usage ping and prevent it from being configured in future through
the administration panel, Omnibus installs can set the following in
[`gitlab.rb`](https://docs.gitlab.com/omnibus/settings/configuration.html#configuration-options):
```ruby
gitlab_rails['usage_ping_enabled'] = false
```
And source installs can set the following in `gitlab.yml`:
```yaml
production: &base
# ...
gitlab:
# ...
usage_ping_enabled: false
```
See [Usage Ping guide](../../../development/telemetry/usage_ping.md).
## Instance statistics visibility **(CORE ONLY)**
......@@ -148,308 +80,3 @@ questions that you know someone might ask.
Each scenario can be a third-level heading, e.g. `### Getting error message X`.
If you have none to add when creating a doc, leave this section in place
but commented out to help encourage others to add to it in the future. -->
## Usage Statistics Collected
| Statistic | Section | Stage | Description |
|---|---|---|---|
|uuid|||
|hostname|||
|version|||
|installation_type|||
|active_user_count|||
|recorded_at|||
|edition|||
|license_md5|||
|license_id|||
|historical_max_users|||
|Name|licensee||
|Email|licensee||
|Company|licensee||
|license_user_count|||
|license_starts_at|||
|license_expires_at|||
|license_plan|||
|license_trial|||
|assignee_lists|counts||
|boards|counts||
|ci_builds|counts||
|ci_internal_pipelines|counts||
|ci_external_pipelines|counts||
|ci_pipeline_config_auto_devops|counts||
|ci_pipeline_config_repository|counts||
|ci_runners|counts||
|ci_triggers|counts||
|ci_pipeline_schedules|counts||
|auto_devops_enabled|counts||
|auto_devops_disabled|counts||
|deploy_keys|counts||
|deployments|counts||
|dast_jobs|counts||
|successful_deployments|counts||
|failed_deployments|counts||
|environments|counts||
|clusters|counts||
|clusters_enabled|counts||
|project_clusters_enabled|counts||
|group_clusters_enabled|counts||
|instance_clusters_enabled|counts||
|clusters_disabled|counts||
|project_clusters_disabled|counts||
|group_clusters_disabled|counts||
|instance_clusters_disabled|counts||
|clusters_platforms_eks|counts||
|clusters_platforms_gke|counts||
|clusters_platforms_user|counts||
|clusters_applications_helm|counts||
|clusters_applications_ingress|counts||
|clusters_applications_cert_managers|counts||
|clusters_applications_crossplane|counts||
|clusters_applications_prometheus|counts||
|clusters_applications_runner|counts||
|clusters_applications_knative|counts||
|clusters_applications_elastic_stack|counts||
|clusters_management_project|counts||
|in_review_folder|counts||
|grafana_integrated_projects|counts||
|groups|counts||
|issues|counts||
|issues_created_from_gitlab_error_tracking_ui|counts||
|issues_with_associated_zoom_link|counts||
|issues_using_zoom_quick_actions|counts||
|issues_with_embedded_grafana_charts_approx|counts||
|issues_with_health_status|counts||
|keys|counts||
|label_lists|counts||
|lfs_objects|counts||
|milestone_lists|counts||
|milestones|counts||
|pages_domains|counts||
|pool_repositories|counts||
|projects|counts||
|projects_imported_from_github|counts||
|projects_with_repositories_enabled|counts||
|projects_with_error_tracking_enabled|counts||
|protected_branches|counts||
|releases|counts||
|remote_mirrors|counts||
|requirements_created|counts||
|snippets|counts||
|suggestions|counts||
|todos|counts||
|uploads|counts||
|web_hooks|counts||
|projects_alerts_active|counts||
|projects_asana_active|counts||
|projects_assembla_active|counts||
|projects_bamboo_active|counts||
|projects_bugzilla_active|counts||
|projects_buildkite_active|counts||
|projects_campfire_active|counts||
|projects_custom_issue_tracker_active|counts||
|projects_discord_active|counts||
|projects_drone_ci_active|counts||
|projects_emails_on_push_active|counts||
|projects_external_wiki_active|counts||
|projects_flowdock_active|counts||
|projects_github_active|counts||
|projects_hangouts_chat_active|counts||
|projects_hipchat_active|counts||
|projects_irker_active|counts||
|projects_jenkins_active|counts||
|projects_jira_active -|counts||
|projects_mattermost_active|counts||
|projects_mattermost_slash_commands_active|counts||
|projects_microsoft_teams_active|counts||
|projects_packagist_active|counts||
|projects_pipelines_email_active|counts||
|projects_pivotaltracker_active|counts||
|projects_prometheus_active|counts||
|projects_pushover_active|counts||
|projects_redmine_active|counts||
|projects_slack_active|counts||
|projects_slack_slash_commands_active|counts||
|projects_teamcity_active|counts||
|projects_unify_circuit_active|counts||
|projects_webex_teams_active|counts||
|projects_youtrack_active|counts||
|projects_slack_notifications_active|counts||
|projects_slack_slash_active|counts||
|projects_jira_server_active|counts||
|projects_jira_cloud_active|counts||
|projects_jira_dvcs_cloud_active|counts||
|projects_jira_dvcs_server_active|counts||
|labels|counts||
|merge_requests|counts||
|notes|counts||
|wiki_pages_create|counts||
|wiki_pages_update|counts||
|wiki_pages_delete|counts||
|web_ide_commits|counts||
|web_ide_views|counts||
|web_ide_merge_requests|counts||
|web_ide_previews|counts||
|snippet_comment|counts||
|commit_comment|counts||
|merge_request_comment|counts||
|snippet_create|counts||
|snippet_update|counts||
|navbar_searches|counts||
|cycle_analytics_views|counts||
|productivity_analytics_views|counts||
|source_code_pushes|counts||
|merge_request_create|counts||
|design_management_designs_create|counts||
|design_management_designs_update|counts||
|design_management_designs_delete|counts||
|licenses_list_views|counts||
|user_preferences_group_overview_details|counts||
|user_preferences_group_overview_security_dashboard|counts||
|ingress_modsecurity_logging|counts||
|ingress_modsecurity_blocking|counts||
|ingress_modsecurity_disabled|counts||
|ingress_modsecurity_not_installed|counts||
|dependency_list_usages_total|counts||
|epics|counts||
|feature_flags|counts||
|geo_nodes|counts||
|incident_issues|counts|monitor|Issues created by the alert bot|
|alert_bot_incident_issues|counts|monitor|Issues created by the alert bot|
|incident_labeled_issues|counts|monitor|Issues with the incident label|
|issues_created_gitlab_alerts|counts|monitor|issues created from alerts by non-alert bot users|
|ldap_group_links|counts||
|ldap_keys|counts||
|ldap_users|counts||
|pod_logs_usages_total|counts||
|projects_enforcing_code_owner_approval|counts||
|projects_mirrored_with_pipelines_enabled|counts||
|projects_reporting_ci_cd_back_to_github|counts||
|projects_with_packages|counts||
|projects_with_prometheus_alerts|counts||
|projects_with_tracing_enabled|counts||
|projects_with_alerts_service_enabled|counts||
|template_repositories|counts||
|container_scanning_jobs|counts||
|dependency_scanning_jobs|counts||
|license_management_jobs|counts||
|sast_jobs|counts||
|status_page_projects|counts|monitor|
|status_page_issues|counts|monitor|
|epics_deepest_relationship_level|counts||
|operations_dashboard_default_dashboard|counts||
|operations_dashboard_users_with_projects_added|counts||
|container_registry_enabled|||
|dependency_proxy_enabled|||
|gitlab_shared_runners_enabled|||
|gravatar_enabled|||
|ldap_enabled|||
|mattermost_enabled|||
|omniauth_enabled|||
|prometheus_metrics_enabled|||
|reply_by_email_enabled|||
|signup_enabled|||
|web_ide_clientside_preview_enabled|||
|ingress_modsecurity_enabled|||
|elasticsearch_enabled|||
|license_trial_ends_on|||
|geo_enabled|||
|version|Git||
|version|Gitaly||
|servers|Gitaly||
|filesystems|Gitaly||
|enabled|gitlab_pages||
|version|gitlab_pages||
|adapter|database||
|version|database||
|average|avg_cycle_analytics - issue||
|sd|avg_cycle_analytics - issue||
|missing|avg_cycle_analytics - issue||
|average|avg_cycle_analytics - plan||
|sd|avg_cycle_analytics - plan||
|missing|avg_cycle_analytics - plan||
|average|avg_cycle_analytics - code||
|sd|avg_cycle_analytics - code||
|missing|avg_cycle_analytics - code||
|average|avg_cycle_analytics - test||
|sd|avg_cycle_analytics - test||
|missing|avg_cycle_analytics - test||
|average|avg_cycle_analytics - review||
|sd|avg_cycle_analytics - review||
|missing|avg_cycle_analytics - review||
|average|avg_cycle_analytics - staging||
|sd|avg_cycle_analytics - staging||
|missing|avg_cycle_analytics - staging||
|average|avg_cycle_analytics - production||
|sd|avg_cycle_analytics - production||
|missing|avg_cycle_analytics - production||
|total|avg_cycle_analytics||
|clusters_applications_cert_managers|usage_activity_by_stage|configure|
|clusters_applications_helm|usage_activity_by_stage|configure|
|clusters_applications_ingress|usage_activity_by_stage|configure|
|clusters_applications_knative|usage_activity_by_stage|configure|
|clusters_management_project|usage_activity_by_stage|configure|
|clusters_disabled|usage_activity_by_stage|configure|
|clusters_enabled|usage_activity_by_stage|configure|
|clusters_platforms_gke|usage_activity_by_stage|configure|
|clusters_platforms_eks|usage_activity_by_stage|configure|
|clusters_platforms_user|usage_activity_by_stage|configure|
|instance_clusters_disabled|usage_activity_by_stage|configure|
|instance_clusters_enabled|usage_activity_by_stage|configure|
|group_clusters_disabled|usage_activity_by_stage|configure|
|group_clusters_enabled|usage_activity_by_stage|configure|
|project_clusters_disabled|usage_activity_by_stage|configure|
|project_clusters_enabled|usage_activity_by_stage|configure|
|projects_slack_notifications_active|usage_activity_by_stage|configure|
|projects_slack_slash_active|usage_activity_by_stage|configure|
|projects_with_prometheus_alerts: 0|usage_activity_by_stage|configure|
|deploy_keys|usage_activity_by_stage|create|
|keys|usage_activity_by_stage|create|
|merge_requests|usage_activity_by_stage|create|
|projects_enforcing_code_owner_approval|usage_activity_by_stage|create|
|projects_imported_from_github|usage_activity_by_stage|create|
|projects_with_repositories_enabled|usage_activity_by_stage|create|
|protected_branches|usage_activity_by_stage|create|
|remote_mirrors|usage_activity_by_stage|create|
|snippets|usage_activity_by_stage|create|
|suggestions:|usage_activity_by_stage|create|
|groups|usage_activity_by_stage|manage|
|ldap_keys|usage_activity_by_stage|manage|
|ldap_users: 0|usage_activity_by_stage|manage|
|users_created|usage_activity_by_stage|manage|
|clusters|usage_activity_by_stage|monitor|
|clusters_applications_prometheus|usage_activity_by_stage|monitor|
|operations_dashboard_default_dashboard|usage_activity_by_stage|monitor|
|operations_dashboard_users_with_projects_added|usage_activity_by_stage|monitor|
|projects_prometheus_active|usage_activity_by_stage|monitor|
|projects_with_error_tracking_enabled|usage_activity_by_stage|monitor|
|projects_with_tracing_enabled: 0|usage_activity_by_stage|monitor|
|projects_with_packages: 0|usage_activity_by_stage|package|
|assignee_lists|usage_activity_by_stage|plan|
|epics|usage_activity_by_stage|plan|
|issues|usage_activity_by_stage|plan|
|label_lists|usage_activity_by_stage|plan|
|milestone_lists|usage_activity_by_stage|plan|
|notes|usage_activity_by_stage|plan|
|projects|usage_activity_by_stage|plan|
|projects_jira_active|usage_activity_by_stage|plan|
|projects_jira_dvcs_cloud_active|usage_activity_by_stage|plan|
|projects_jira_dvcs_server_active|usage_activity_by_stage|plan|
|service_desk_enabled_projects|usage_activity_by_stage|plan|
|service_desk_issues|usage_activity_by_stage|plan|
|todos: 0|usage_activity_by_stage|plan|
|deployments|usage_activity_by_stage|release|
|failed_deployments|usage_activity_by_stage|release|
|projects_mirrored_with_pipelines_enabled|usage_activity_by_stage|release|
|releases|usage_activity_by_stage|release|
|successful_deployments: 0|usage_activity_by_stage|release|
|user_preferences_group_overview_security_dashboard: 0|usage_activity_by_stage|secure|
|ci_builds|usage_activity_by_stage|verify|
|ci_external_pipelines|usage_activity_by_stage|verify|
|ci_internal_pipelines|usage_activity_by_stage|verify|
|ci_pipeline_config_auto_devops|usage_activity_by_stage|verify|
|ci_pipeline_config_repository|usage_activity_by_stage|verify|
|ci_pipeline_schedules|usage_activity_by_stage|verify|
|ci_pipelines|usage_activity_by_stage|verify|
|ci_triggers|usage_activity_by_stage|verify|
|clusters_applications_runner|usage_activity_by_stage|verify|
|projects_reporting_ci_cd_back_to_github: 0|usage_activity_by_stage|verify|
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment