summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-05-19 00:07:58 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-05-19 00:07:58 +0000
commit41456efd551f45f569a8c9e70734c88efbbbf0ec (patch)
tree4ac9d0f1773a8786ba263e88e3d472afcff3d3c1
parentfe289cff8b1b94020d22f7b3ff1c385a05086b9e (diff)
downloadgitlab-ce-41456efd551f45f569a8c9e70734c88efbbbf0ec.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.haml-lint_todo.yml1
-rw-r--r--.rubocop.yml1
-rw-r--r--app/services/projects/prometheus/alerts/notify_service.rb2
-rw-r--r--app/views/projects/mirrors/_mirror_repos_push.html.haml12
-rw-r--r--app/views/shared/_field.html.haml2
-rw-r--r--app/views/shared/issuable/_bulk_update_sidebar.html.haml38
-rw-r--r--app/views/shared/milestones/_labels_tab.html.haml4
-rw-r--r--app/views/shared/milestones/_milestone.html.haml16
-rw-r--r--changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-issuable-_bulk_upd.yml5
-rw-r--r--changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-milestones-_labels.yml5
-rw-r--r--changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-milestones-_milest.yml5
-rw-r--r--changelogs/unreleased/22691-externelize-i18n-strings-from---app-views-shared-_field-html-haml.yml5
-rw-r--r--changelogs/unreleased/leaky-constant-fix-4.yml5
-rw-r--r--changelogs/unreleased/rs-enable-keep-divergent-refs.yml5
-rw-r--r--doc/api/remote_mirrors.md5
-rw-r--r--doc/api/vulnerability_exports.md51
-rw-r--r--doc/development/README.md7
-rw-r--r--doc/development/event_tracking/backend.md4
-rw-r--r--doc/development/event_tracking/frontend.md4
-rw-r--r--doc/development/event_tracking/index.md4
-rw-r--r--doc/development/experiment_guide/index.md2
-rw-r--r--doc/development/fe_guide/event_tracking.md4
-rw-r--r--doc/development/img/snowplow_flow.pngbin0 -> 16589 bytes
-rw-r--r--doc/development/img/telemetry_system_overview.pngbin0 -> 429082 bytes
-rw-r--r--doc/development/instrumentation.md2
-rw-r--r--doc/development/telemetry/index.md165
-rw-r--r--doc/development/telemetry/snowplow.md393
-rw-r--r--doc/development/telemetry/usage_ping.md489
-rw-r--r--doc/telemetry/index.md71
-rw-r--r--doc/telemetry/snowplow.md212
-rw-r--r--doc/user/admin_area/settings/index.md2
-rw-r--r--doc/user/admin_area/settings/usage_statistics.md375
-rw-r--r--doc/user/project/repository/repository_mirroring.md22
-rw-r--r--lib/api/entities/remote_mirror.rb4
-rw-r--r--lib/api/remote_mirrors.rb2
-rw-r--r--lib/static_model.rb6
-rw-r--r--locale/gitlab.pot48
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb14
-rw-r--r--spec/models/performance_monitoring/prometheus_dashboard_spec.rb102
-rw-r--r--spec/models/performance_monitoring/prometheus_metric_spec.rb59
-rw-r--r--spec/models/performance_monitoring/prometheus_panel_group_spec.rb54
-rw-r--r--spec/models/performance_monitoring/prometheus_panel_spec.rb77
-rw-r--r--spec/models/remote_mirror_spec.rb4
-rw-r--r--spec/requests/api/remote_mirrors_spec.rb23
-rw-r--r--spec/services/projects/prometheus/alerts/notify_service_spec.rb37
-rw-r--r--spec/support_specs/helpers/active_record/query_recorder_spec.rb8
46 files changed, 1572 insertions, 784 deletions
diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml
index b46995dd63c..a79bba24edb 100644
--- a/.haml-lint_todo.yml
+++ b/.haml-lint_todo.yml
@@ -289,7 +289,6 @@ linters:
- "app/views/shared/hook_logs/_content.html.haml"
- "app/views/shared/issuable/_assignees.html.haml"
- "app/views/shared/issuable/_board_create_list_dropdown.html.haml"
- - "app/views/shared/issuable/_bulk_update_sidebar.html.haml"
- "app/views/shared/issuable/_close_reopen_report_toggle.html.haml"
- "app/views/shared/issuable/_form.html.haml"
- "app/views/shared/issuable/_search_bar.html.haml"
diff --git a/.rubocop.yml b/.rubocop.yml
index d744fe4d7db..22806de5083 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -402,7 +402,6 @@ RSpec/LeakyConstantDeclaration:
- 'spec/services/metrics/dashboard/clone_dashboard_service_spec.rb'
- 'spec/support/shared_contexts/spam_constants.rb'
- 'spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb'
- - 'spec/support_specs/helpers/active_record/query_recorder_spec.rb'
- 'spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb'
- 'spec/uploaders/records_uploads_spec.rb'
diff --git a/app/services/projects/prometheus/alerts/notify_service.rb b/app/services/projects/prometheus/alerts/notify_service.rb
index c1793d2a2d6..2583a6cae9f 100644
--- a/app/services/projects/prometheus/alerts/notify_service.rb
+++ b/app/services/projects/prometheus/alerts/notify_service.rb
@@ -117,8 +117,6 @@ module Projects
end
def process_prometheus_alerts
- return unless Feature.enabled?(:alert_management_minimal, project)
-
alerts.each do |alert|
AlertManagement::ProcessPrometheusAlertService
.new(project, nil, alert.to_h)
diff --git a/app/views/projects/mirrors/_mirror_repos_push.html.haml b/app/views/projects/mirrors/_mirror_repos_push.html.haml
index 8482424a184..9b5b31bfc15 100644
--- a/app/views/projects/mirrors/_mirror_repos_push.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos_push.html.haml
@@ -1,15 +1,13 @@
- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
-- keep_divergent_refs = Feature.enabled?(:keep_divergent_refs, @project)
= f.fields_for :remote_mirrors, @project.remote_mirrors.build do |rm_f|
= rm_f.hidden_field :enabled, value: '1'
= rm_f.hidden_field :url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+"
= rm_f.hidden_field :only_protected_branches, class: 'js-mirror-protected-hidden'
- - if keep_divergent_refs
- = rm_f.hidden_field :keep_divergent_refs, class: 'js-mirror-keep-divergent-refs-hidden'
+ = rm_f.hidden_field :keep_divergent_refs, class: 'js-mirror-keep-divergent-refs-hidden'
= render partial: 'projects/mirrors/ssh_host_keys', locals: { f: rm_f }
= render partial: 'projects/mirrors/authentication_method', locals: { f: rm_f }
- - if keep_divergent_refs
- .form-check.append-bottom-10
- = check_box_tag :keep_divergent_refs, '1', false, class: 'js-mirror-keep-divergent-refs form-check-input'
- = label_tag :keep_divergent_refs, 'Keep divergent refs', class: 'form-check-label'
+ .form-check.append-bottom-10
+ = check_box_tag :keep_divergent_refs, '1', false, class: 'js-mirror-keep-divergent-refs form-check-input'
+ = label_tag :keep_divergent_refs, _('Keep divergent refs'), class: 'form-check-label'
+ = link_to icon('question-circle'), help_page_path('user/project/repository/repository_mirroring', anchor: 'keep-divergent-refs-core'), target: '_blank'
diff --git a/app/views/shared/_field.html.haml b/app/views/shared/_field.html.haml
index 53718b4c001..4f416c483f2 100644
--- a/app/views/shared/_field.html.haml
+++ b/app/views/shared/_field.html.haml
@@ -10,7 +10,7 @@
.form-group.row
- if type == "password" && value.present?
- = form.label name, "Enter new #{title.downcase}", class: "col-form-label col-sm-2"
+ = form.label name, _("Enter new %{field_title}") % { field_title: title.downcase }, class: "col-form-label col-sm-2"
- else
= form.label name, title, class: "col-form-label col-sm-2"
.col-sm-10
diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
index a05a13814ac..4bc6c1dee37 100644
--- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml
+++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
@@ -5,45 +5,49 @@
= form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: "bulk-update" do
.block.issuable-sidebar-header
.filter-item.inline.update-issues-btn.float-left
- = button_tag "Update all", class: "btn update-selected-issues btn-info", disabled: true
- = button_tag "Cancel", class: "btn btn-default js-bulk-update-menu-hide float-right"
+ = button_tag _('Update all'), class: "btn update-selected-issues btn-info", disabled: true
+ = button_tag _('Cancel'), class: "btn btn-default js-bulk-update-menu-hide float-right"
.block
.title
- Status
+ = _('Status')
.filter-item
- = dropdown_tag("Select status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]", default_label: "Status" } } ) do
+ = dropdown_tag(_("Select status"), options: { toggle_class: "js-issue-status", title: _("Change status"), dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]", default_label: _("Status") } } ) do
%ul
%li
- %a{ href: "#", data: { id: "reopen" } } Open
+ %a{ href: "#", data: { id: "reopen" } }
+ = _('Open')
%li
- %a{ href: "#", data: { id: "close" } } Closed
+ %a{ href: "#", data: { id: "close" } }
+ = _('Closed')
.block
.title
- Assignee
+ = _('Assignee')
.filter-item
- field_name = "update[assignee_ids][]"
- = dropdown_tag("Select assignee", options: { toggle_class: "js-user-search js-update-assignee js-filter-submit js-filter-bulk-update", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
- placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: field_name } })
+ = dropdown_tag(_("Select assignee"), options: { toggle_class: "js-user-search js-update-assignee js-filter-submit js-filter-bulk-update", title: _("Assign to"), filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
+ placeholder: _("Search authors"), data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: field_name } })
.block
.title
- Milestone
+ = _('Milestone')
.filter-item
- = dropdown_tag("Select milestone", options: { title: "Assign milestone", toggle_class: "js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: project_milestones_path(@project, :json), use_id: true, default_label: "Milestone" } })
+ = dropdown_tag(_("Select milestone"), options: { title: _("Assign milestone"), toggle_class: "js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: _("Search milestones"), data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: project_milestones_path(@project, :json), use_id: true, default_label: _("Milestone") } })
.block
.title
- Labels
+ = _('Labels')
.filter-item.labels-filter
- = render "shared/issuable/label_dropdown", classes: ["js-filter-bulk-update", "js-multiselect"], dropdown_title: "Apply a label", show_create: false, show_footer: false, extra_options: false, filter_submit: false, data_options: { persist_when_hide: "true", field_name: "update[label_ids][]", show_no: false, show_any: false, use_id: true, default_label: "Labels" }, label_name: "Select labels", no_default_styles: true
+ = render "shared/issuable/label_dropdown", classes: ["js-filter-bulk-update", "js-multiselect"], dropdown_title: _("Apply a label"), show_create: false, show_footer: false, extra_options: false, filter_submit: false, data_options: { persist_when_hide: "true", field_name: "update[label_ids][]", show_no: false, show_any: false, use_id: true, default_label: _("Labels") }, label_name: _("Select labels"), no_default_styles: true
.block
.title
- Subscriptions
+ = _('Subscriptions')
.filter-item
- = dropdown_tag("Select subscription", options: { toggle_class: "js-subscription-event", title: "Change subscription", dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[subscription_event]", default_label: "Subscription" } } ) do
+ = dropdown_tag(_("Select subscription"), options: { toggle_class: "js-subscription-event", title: _("Change subscription"), dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[subscription_event]", default_label: _("Subscription") } } ) do
%ul
%li
- %a{ href: "#", data: { id: "subscribe" } } Subscribe
+ %a{ href: "#", data: { id: "subscribe" } }
+ = _('Subscribe')
%li
- %a{ href: "#", data: { id: "unsubscribe" } } Unsubscribe
+ %a{ href: "#", data: { id: "unsubscribe" } }
+ = _('Unsubscribe')
= hidden_field_tag "update[issuable_ids]", []
= hidden_field_tag :state_event, params[:state_event]
diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml
index 6d79b0d31b2..3b4d29ca7b0 100644
--- a/app/views/shared/milestones/_labels_tab.html.haml
+++ b/app/views/shared/milestones/_labels_tab.html.haml
@@ -9,6 +9,6 @@
.float-right.d-none.d-lg-block.d-xl-block
= link_to milestones_issues_path(options.merge(state: 'opened')), class: 'btn btn-transparent btn-action' do
- - pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue'
+ - pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), _('open issue')
= link_to milestones_issues_path(options.merge(state: 'closed')), class: 'btn btn-transparent btn-action' do
- - pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue'
+ - pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), _('closed issue')
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index 451c2c2ba10..9f61082d605 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -1,6 +1,6 @@
- dashboard = local_assigns[:dashboard]
- custom_dom_id = dom_id(milestone.try(:milestone) ? milestone.milestone : milestone)
-- milestone_type = milestone.group_milestone? ? 'Group Milestone' : 'Project Milestone'
+- milestone_type = milestone.group_milestone? ? s_('Milestones|Group Milestone') : s_('Milestones|Project Milestone')
%li{ class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id }
.row
@@ -42,17 +42,17 @@
.col-sm-4.milestone-progress
= milestone_progress_bar(milestone)
- = link_to pluralize(milestone.total_issues_count, 'Issue'), issues_path
+ = link_to pluralize(milestone.total_issues_count, _('Issue')), issues_path
- if milestone.merge_requests_enabled?
&middot;
- = link_to pluralize(milestone.merge_requests_visible_to_user(current_user).size, 'Merge Request'), merge_requests_path
+ = link_to pluralize(milestone.merge_requests_visible_to_user(current_user).size, _('Merge Request')), merge_requests_path
.float-lg-right.light #{milestone.percent_complete}% complete
.col-sm-2
.milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end
- if @project
- if can_admin_project_milestones? and milestone.active?
- if can_admin_group_milestones?
- %button.js-promote-project-milestone-button.btn.btn-blank.btn-sm.btn-grouped.has-tooltip{ title: _('Promote to Group Milestone'),
+ %button.js-promote-project-milestone-button.btn.btn-blank.btn-sm.btn-grouped.has-tooltip{ title: s_('Milestones|Promote to Group Milestone'),
disabled: true,
type: 'button',
data: { url: promote_project_milestone_path(milestone.project, milestone),
@@ -63,15 +63,15 @@
toggle: 'modal' } }
= sprite_icon('level-up', size: 14)
- = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close btn-grouped"
+ = link_to s_('Milestones|Close Milestone'), project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close btn-grouped"
- unless milestone.active?
- = link_to 'Reopen Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
+ = link_to s_('Milestones|Reopen Milestone'), project_milestone_path(@project, milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
- if @group
- if can?(current_user, :admin_milestone, @group)
- if milestone.closed?
- = link_to 'Reopen Milestone', group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen"
+ = link_to s_('Milestones|Reopen Milestone'), group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen"
- else
- = link_to 'Close Milestone', group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-sm btn-grouped btn-close"
+ = link_to s_('Milestones|Close Milestone'), group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-sm btn-grouped btn-close"
- if dashboard
.label-badge.label-badge-gray
= milestone_type
diff --git a/changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-issuable-_bulk_upd.yml b/changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-issuable-_bulk_upd.yml
new file mode 100644
index 00000000000..bb4a2ad2e62
--- /dev/null
+++ b/changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-issuable-_bulk_upd.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize i18n strings from ./app/views/shared/issuable/_bulk_update_sidebar.html.haml
+merge_request: 32173
+author: Gilang Gumilar
+type: changed
diff --git a/changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-milestones-_labels.yml b/changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-milestones-_labels.yml
new file mode 100644
index 00000000000..6b10c7cc2cb
--- /dev/null
+++ b/changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-milestones-_labels.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize i18n strings from ./app/views/shared/milestones/_labels_tab.html.haml
+merge_request: 32159
+author: Gilang Gumilar
+type: changed
diff --git a/changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-milestones-_milest.yml b/changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-milestones-_milest.yml
new file mode 100644
index 00000000000..0b071794ad9
--- /dev/null
+++ b/changelogs/unreleased/22691-externalize-i18n-strings-from---app-views-shared-milestones-_milest.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize i18n strings from ./app/views/shared/milestones/_milestone.html.haml
+merge_request: 32154
+author: Gilang Gumilar
+type: changed
diff --git a/changelogs/unreleased/22691-externelize-i18n-strings-from---app-views-shared-_field-html-haml.yml b/changelogs/unreleased/22691-externelize-i18n-strings-from---app-views-shared-_field-html-haml.yml
new file mode 100644
index 00000000000..cb9b46fd15e
--- /dev/null
+++ b/changelogs/unreleased/22691-externelize-i18n-strings-from---app-views-shared-_field-html-haml.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize i18n strings from ./app/views/shared/_field.html.haml
+merge_request: 32136
+author: Gilang Gumilar
+type: changed
diff --git a/changelogs/unreleased/leaky-constant-fix-4.yml b/changelogs/unreleased/leaky-constant-fix-4.yml
new file mode 100644
index 00000000000..fa1a25c7bba
--- /dev/null
+++ b/changelogs/unreleased/leaky-constant-fix-4.yml
@@ -0,0 +1,5 @@
+---
+title: Add class stubs and fix leaky constant alert in query recorder spec
+merge_request: 31954
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/rs-enable-keep-divergent-refs.yml b/changelogs/unreleased/rs-enable-keep-divergent-refs.yml
new file mode 100644
index 00000000000..b7611c9dec9
--- /dev/null
+++ b/changelogs/unreleased/rs-enable-keep-divergent-refs.yml
@@ -0,0 +1,5 @@
+---
+title: Add "Keep divergent refs" option for push mirrors
+merge_request: 32381
+author:
+type: added
diff --git a/doc/api/remote_mirrors.md b/doc/api/remote_mirrors.md
index 0ffff194976..ecd35239e00 100644
--- a/doc/api/remote_mirrors.md
+++ b/doc/api/remote_mirrors.md
@@ -33,6 +33,7 @@ Example response:
"last_update_at": "2020-01-06T17:32:02.823Z",
"last_update_started_at": "2020-01-06T17:31:55.864Z",
"only_protected_branches": true,
+ "keep_divergent_refs": true,
"update_status": "finished",
"url": "https://*****:*****@gitlab.com/gitlab-org/security/gitlab.git"
}
@@ -58,6 +59,7 @@ POST /projects/:id/remote_mirrors
| `url` | String | yes | The URL of the remote repository to be mirrored. |
| `enabled` | Boolean | no | Determines if the mirror is enabled. |
| `only_protected_branches` | Boolean | no | Determines if only protected branches are mirrored. |
+| `keep_divergent_refs` | Boolean | no | Determines if divergent refs are skipped. |
Example request:
@@ -76,6 +78,7 @@ Example response:
"last_update_at": null,
"last_update_started_at": null,
"only_protected_branches": false,
+ "keep_divergent_refs": false,
"update_status": "none",
"url": "https://*****:*****@example.com/gitlab/example.git"
}
@@ -97,6 +100,7 @@ PUT /projects/:id/remote_mirrors/:mirror_id
| `mirror_id` | Integer | yes | The remote mirror ID. |
| `enabled` | Boolean | no | Determines if the mirror is enabled. |
| `only_protected_branches` | Boolean | no | Determines if only protected branches are mirrored. |
+| `keep_divergent_refs` | Boolean | no | Determines if divergent refs are skipped. |
Example request:
@@ -115,6 +119,7 @@ Example response:
"last_update_at": "2020-01-06T17:32:02.823Z",
"last_update_started_at": "2020-01-06T17:31:55.864Z",
"only_protected_branches": true,
+ "keep_divergent_refs": true,
"update_status": "finished",
"url": "https://*****:*****@gitlab.com/gitlab-org/security/gitlab.git"
}
diff --git a/doc/api/vulnerability_exports.md b/doc/api/vulnerability_exports.md
index f53a0ca08a3..2c9ac5d65eb 100644
--- a/doc/api/vulnerability_exports.md
+++ b/doc/api/vulnerability_exports.md
@@ -42,7 +42,7 @@ POST /security/projects/:id/vulnerability_exports
curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/security/projects/1/vulnerability_exports
```
-The created vulnerability export will be automatically deleted after 1 hour.
+The created vulnerability export is automatically deleted after 1 hour.
Example response:
@@ -51,6 +51,53 @@ Example response:
"id": 2,
"created_at": "2020-03-30T09:35:38.746Z",
"project_id": 1,
+ "group_id": null,
+ "format": "csv",
+ "status": "created",
+ "started_at": null,
+ "finished_at": null,
+ "_links": {
+ "self": "https://gitlab.example.com/api/v4/security/vulnerability_exports/2",
+ "download": "https://gitlab.example.com/api/v4/security/vulnerability_exports/2/download"
+ }
+}
+```
+
+## Create a group-level vulnerability export
+
+Creates a new vulnerability export for a group.
+
+Vulnerability export permissions inherit permissions from their group. If a group is
+private and a user isn't a member of the group to which the vulnerability
+belongs, requests to that group return a `404 Not Found` status code.
+Vulnerability exports can be only accessed by the export's author.
+
+If an authenticated user doesn't have permission to
+[create a new vulnerability](../user/permissions.md#group-members-permissions),
+this request results in a `403` status code.
+
+```plaintext
+POST /security/groups/:id/vulnerability_exports
+```
+
+| Attribute | Type | Required | Description |
+| ------------------- | ----------------- | ---------- | -----------------------------------------------------------------------------------------------------------------------------|
+| `id` | integer or string | yes | The ID or [URL-encoded path](README.md#namespaced-path-encoding) of the group which the authenticated user is a member of |
+
+```shell
+curl --header POST "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/security/groups/1/vulnerability_exports
+```
+
+The created vulnerability export is automatically deleted after 1 hour.
+
+Example response:
+
+```json
+{
+ "id": 2,
+ "created_at": "2020-03-30T09:35:38.746Z",
+ "project_id": null,
+ "group_id": 1,
"format": "csv",
"status": "created",
"started_at": null,
@@ -83,6 +130,7 @@ Example response:
"id": 2,
"created_at": "2020-03-30T09:35:38.746Z",
"project_id": null,
+ "group_id": null,
"format": "csv",
"status": "created",
"started_at": null,
@@ -119,6 +167,7 @@ Example response:
"id": 2,
"created_at": "2020-03-30T09:35:38.746Z",
"project_id": 1,
+ "group_id": null,
"format": "csv",
"status": "finished",
"started_at": "2020-03-30T09:36:54.469Z",
diff --git a/doc/development/README.md b/doc/development/README.md
index 22644b77c06..08407cad85b 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -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)
diff --git a/doc/development/event_tracking/backend.md b/doc/development/event_tracking/backend.md
index ae555e99c6b..93e135772ef 100644
--- a/doc/development/event_tracking/backend.md
+++ b/doc/development/event_tracking/backend.md
@@ -1,5 +1,5 @@
---
-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).
diff --git a/doc/development/event_tracking/frontend.md b/doc/development/event_tracking/frontend.md
index ae555e99c6b..93e135772ef 100644
--- a/doc/development/event_tracking/frontend.md
+++ b/doc/development/event_tracking/frontend.md
@@ -1,5 +1,5 @@
---
-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).
diff --git a/doc/development/event_tracking/index.md b/doc/development/event_tracking/index.md
index ae555e99c6b..93e135772ef 100644
--- a/doc/development/event_tracking/index.md
+++ b/doc/development/event_tracking/index.md
@@ -1,5 +1,5 @@
---
-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).
diff --git a/doc/development/experiment_guide/index.md b/doc/development/experiment_guide/index.md
index 12ec9084b91..f0e05139cba 100644
--- a/doc/development/experiment_guide/index.md
+++ b/doc/development/experiment_guide/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.
diff --git a/doc/development/fe_guide/event_tracking.md b/doc/development/fe_guide/event_tracking.md
index ae555e99c6b..93e135772ef 100644
--- a/doc/development/fe_guide/event_tracking.md
+++ b/doc/development/fe_guide/event_tracking.md
@@ -1,5 +1,5 @@
---
-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).
diff --git a/doc/development/img/snowplow_flow.png b/doc/development/img/snowplow_flow.png
new file mode 100644
index 00000000000..5996cf01537
--- /dev/null
+++ b/doc/development/img/snowplow_flow.png
Binary files differ
diff --git a/doc/development/img/telemetry_system_overview.png b/doc/development/img/telemetry_system_overview.png
new file mode 100644
index 00000000000..1667039e8cd
--- /dev/null
+++ b/doc/development/img/telemetry_system_overview.png
Binary files differ
diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md
index fd01d0ea405..d72e1c6635e 100644
--- a/doc/development/instrumentation.md
+++ b/doc/development/instrumentation.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
diff --git a/doc/development/telemetry/index.md b/doc/development/telemetry/index.md
new file mode 100644
index 00000000000..32f63d5221e
--- /dev/null
+++ b/doc/development/telemetry/index.md
@@ -0,0 +1,165 @@
+# 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.
diff --git a/doc/development/telemetry/snowplow.md b/doc/development/telemetry/snowplow.md
new file mode 100644
index 00000000000..aeaad6e5624
--- /dev/null
+++ b/doc/development/telemetry/snowplow.md
@@ -0,0 +1,393 @@
+# 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.
diff --git a/doc/development/telemetry/usage_ping.md b/doc/development/telemetry/usage_ping.md
new file mode 100644
index 00000000000..e9b959eaa96
--- /dev/null
+++ b/doc/development/telemetry/usage_ping.md
@@ -0,0 +1,489 @@
+# 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.
diff --git a/doc/telemetry/index.md b/doc/telemetry/index.md
index 16f501a5fb5..977b93b712e 100644
--- a/doc/telemetry/index.md
+++ b/doc/telemetry/index.md
@@ -1,68 +1,5 @@
-# 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).
diff --git a/doc/telemetry/snowplow.md b/doc/telemetry/snowplow.md
index 4961f4f0938..977b93b712e 100644
--- a/doc/telemetry/snowplow.md
+++ b/doc/telemetry/snowplow.md
@@ -1,209 +1,5 @@
-# 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).
diff --git a/doc/user/admin_area/settings/index.md b/doc/user/admin_area/settings/index.md
index 0dc9afb1c2a..bc1afea9bb7 100644
--- a/doc/user/admin_area/settings/index.md
+++ b/doc/user/admin_area/settings/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. |
diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md
index a406210f983..f3eb094887e 100644
--- a/doc/user/admin_area/settings/usage_statistics.md
+++ b/doc/user/admin_area/settings/usage_statistics.md
@@ -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|
diff --git a/doc/user/project/repository/repository_mirroring.md b/doc/user/project/repository/repository_mirroring.md
index 8064eacf404..fdbea385998 100644
--- a/doc/user/project/repository/repository_mirroring.md
+++ b/doc/user/project/repository/repository_mirroring.md
@@ -55,6 +55,7 @@ For an existing project, you can set up push mirroring as follows:
1. Select **Push** from the **Mirror direction** dropdown.
1. Select an authentication method from the **Authentication method** dropdown, if necessary.
1. Check the **Only mirror protected branches** box, if necessary.
+1. Check the **Keep divergent refs** box, if desired.
1. Click the **Mirror repository** button to save the configuration.
![Repository mirroring push settings screen](img/repository_mirroring_push_settings.png)
@@ -88,6 +89,27 @@ You can choose to only push your protected branches from GitLab to your remote r
To use this option, check the **Only mirror protected branches** box when creating a repository
mirror.
+### Keep divergent refs **(CORE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/208828) in GitLab 13.0.
+
+By default, if any ref on the remote mirror has diverged from the local
+repository, the *entire push* will fail, and nothing will be updated.
+
+For example, if a repository has `master`, `develop`, and `stable` branches that
+have been mirrored to a remote, and then a new commit is added to `develop` on
+the mirror, the next push attempt will fail, leaving `master` and `stable`
+out-of-date despite not having diverged. No change on any branch can be mirrored
+until the divergence is resolved.
+
+With the **Keep divergent refs** option enabled, the `develop` branch is
+skipped, allowing `master` and `stable` to be updated. The mirror status will
+reflect that `develop` has diverged and was skipped, and be marked as a failed
+update.
+
+NOTE: **Note:**
+After the mirror is created, this option can currently only be modified via the [API](../../../api/remote_mirrors.md).
+
## Setting up a push mirror from GitLab to GitHub **(CORE)**
To set up a mirror from GitLab to GitHub, you need to follow these steps:
diff --git a/lib/api/entities/remote_mirror.rb b/lib/api/entities/remote_mirror.rb
index 18d51726bab..87daef9a05c 100644
--- a/lib/api/entities/remote_mirror.rb
+++ b/lib/api/entities/remote_mirror.rb
@@ -12,9 +12,7 @@ module API
expose :last_successful_update_at
expose :last_error
expose :only_protected_branches
- expose :keep_divergent_refs, if: -> (mirror, _options) do
- ::Feature.enabled?(:keep_divergent_refs, mirror.project)
- end
+ expose :keep_divergent_refs
end
end
end
diff --git a/lib/api/remote_mirrors.rb b/lib/api/remote_mirrors.rb
index 7e484eb8885..0808541d3c7 100644
--- a/lib/api/remote_mirrors.rb
+++ b/lib/api/remote_mirrors.rb
@@ -34,7 +34,6 @@ module API
end
post ':id/remote_mirrors' do
create_params = declared_params(include_missing: false)
- create_params.delete(:keep_divergent_refs) unless ::Feature.enabled?(:keep_divergent_refs, user_project)
new_mirror = user_project.remote_mirrors.create(create_params)
@@ -59,7 +58,6 @@ module API
mirror_params = declared_params(include_missing: false)
mirror_params[:id] = mirror_params.delete(:mirror_id)
- mirror_params.delete(:keep_divergent_refs) unless ::Feature.enabled?(:keep_divergent_refs, user_project)
update_params = { remote_mirrors_attributes: mirror_params }
diff --git a/lib/static_model.rb b/lib/static_model.rb
index 86bf8d62f9a..27805817f4d 100644
--- a/lib/static_model.rb
+++ b/lib/static_model.rb
@@ -40,10 +40,6 @@ module StaticModel
end
def ==(other)
- if other.is_a? ::StaticModel
- id == other.id
- else
- super
- end
+ other.present? && other.is_a?(self.class) && id == other.id
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 86261d56ec3..4729152e59b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3779,6 +3779,12 @@ msgstr ""
msgid "Change permissions"
msgstr ""
+msgid "Change status"
+msgstr ""
+
+msgid "Change subscription"
+msgstr ""
+
msgid "Change template"
msgstr ""
@@ -8186,6 +8192,9 @@ msgstr ""
msgid "Enter merge request URLs"
msgstr ""
+msgid "Enter new %{field_title}"
+msgstr ""
+
msgid "Enter new AWS Secret Access Key"
msgstr ""
@@ -12266,6 +12275,9 @@ msgstr ""
msgid "Just me"
msgstr ""
+msgid "Keep divergent refs"
+msgstr ""
+
msgid "Key"
msgstr ""
@@ -13734,6 +13746,9 @@ msgstr ""
msgid "Milestones| You’re about to permanently delete the milestone %{milestoneTitle}. This milestone is not currently used in any issues or merge requests."
msgstr ""
+msgid "Milestones|Close Milestone"
+msgstr ""
+
msgid "Milestones|Completed Issues (closed)"
msgstr ""
@@ -13746,21 +13761,33 @@ msgstr ""
msgid "Milestones|Failed to delete milestone %{milestoneTitle}"
msgstr ""
+msgid "Milestones|Group Milestone"
+msgstr ""
+
msgid "Milestones|Milestone %{milestoneTitle} was not found"
msgstr ""
msgid "Milestones|Ongoing Issues (open and assigned)"
msgstr ""
+msgid "Milestones|Project Milestone"
+msgstr ""
+
msgid "Milestones|Promote %{milestoneTitle} to group milestone?"
msgstr ""
msgid "Milestones|Promote Milestone"
msgstr ""
+msgid "Milestones|Promote to Group Milestone"
+msgstr ""
+
msgid "Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}. Existing project milestones with the same title will be merged."
msgstr ""
+msgid "Milestones|Reopen Milestone"
+msgstr ""
+
msgid "Milestones|This action cannot be reversed."
msgstr ""
@@ -17098,9 +17125,6 @@ msgstr ""
msgid "Promote these project milestones into a group milestone."
msgstr ""
-msgid "Promote to Group Milestone"
-msgstr ""
-
msgid "Promote to group label"
msgstr ""
@@ -18601,6 +18625,9 @@ msgstr ""
msgid "Search an environment spec"
msgstr ""
+msgid "Search authors"
+msgstr ""
+
msgid "Search branches"
msgstr ""
@@ -19098,6 +19125,9 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
+msgid "Select assignee"
+msgstr ""
+
msgid "Select branch"
msgstr ""
@@ -19149,9 +19179,15 @@ msgstr ""
msgid "Select source branch"
msgstr ""
+msgid "Select status"
+msgstr ""
+
msgid "Select strategy activation method"
msgstr ""
+msgid "Select subscription"
+msgstr ""
+
msgid "Select target branch"
msgstr ""
@@ -25541,6 +25577,9 @@ msgstr[1] ""
msgid "ciReport|View full report"
msgstr ""
+msgid "closed issue"
+msgstr ""
+
msgid "comment"
msgstr ""
@@ -26256,6 +26295,9 @@ msgstr ""
msgid "on track"
msgstr ""
+msgid "open issue"
+msgstr ""
+
msgid "opened %{timeAgoString} by %{user}"
msgstr ""
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index b0bc6bbfc70..b8baaa3e963 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -222,20 +222,6 @@ describe 'Projects > Settings > Repository settings' do
end
end
- # Removal: https://gitlab.com/gitlab-org/gitlab/-/issues/208828
- context 'with the `keep_divergent_refs` feature flag disabled' do
- before do
- stub_feature_flags(keep_divergent_refs: false)
- end
-
- it 'hides the "Keep divergent refs" option' do
- visit project_settings_repository_path(project)
-
- expect(page).not_to have_selector('#keep_divergent_refs')
- expect(page).not_to have_text('Keep divergent refs')
- end
- end
-
context 'repository cleanup settings' do
let(:object_map_file) { Rails.root.join('spec', 'fixtures', 'bfg_object_map.txt') }
diff --git a/spec/models/performance_monitoring/prometheus_dashboard_spec.rb b/spec/models/performance_monitoring/prometheus_dashboard_spec.rb
new file mode 100644
index 00000000000..e6fc03a0fb6
--- /dev/null
+++ b/spec/models/performance_monitoring/prometheus_dashboard_spec.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PerformanceMonitoring::PrometheusDashboard do
+ let(:json_content) do
+ {
+ "dashboard" => "Dashboard Title",
+ "templating" => {
+ "variables" => {
+ "variable1" => %w(value1 value2 value3)
+ }
+ },
+ "panel_groups" => [{
+ "group" => "Group Title",
+ "panels" => [{
+ "type" => "area-chart",
+ "title" => "Chart Title",
+ "y_label" => "Y-Axis",
+ "metrics" => [{
+ "id" => "metric_of_ages",
+ "unit" => "count",
+ "label" => "Metric of Ages",
+ "query_range" => "http_requests_total"
+ }]
+ }]
+ }]
+ }
+ end
+
+ describe '.from_json' do
+ subject { described_class.from_json(json_content) }
+
+ it 'creates a PrometheusDashboard object' do
+ expect(subject).to be_a PerformanceMonitoring::PrometheusDashboard
+ expect(subject.dashboard).to eq(json_content['dashboard'])
+ expect(subject.panel_groups).to all(be_a PerformanceMonitoring::PrometheusPanelGroup)
+ end
+
+ describe 'validations' do
+ context 'when dashboard is missing' do
+ before do
+ json_content['dashboard'] = nil
+ end
+
+ subject { described_class.from_json(json_content) }
+
+ it { expect { subject }.to raise_error(ActiveModel::ValidationError) }
+ end
+
+ context 'when panel groups are missing' do
+ before do
+ json_content['panel_groups'] = []
+ end
+
+ subject { described_class.from_json(json_content) }
+
+ it { expect { subject }.to raise_error(ActiveModel::ValidationError) }
+ end
+ end
+ end
+
+ describe '.find_for' do
+ let(:project) { build_stubbed(:project) }
+ let(:user) { build_stubbed(:user) }
+ let(:environment) { build_stubbed(:environment) }
+ let(:path) { ::Metrics::Dashboard::SystemDashboardService::DASHBOARD_PATH }
+
+ context 'dashboard has been found' do
+ it 'uses dashboard finder to find and load dashboard data and returns dashboard instance', :aggregate_failures do
+ expect(Gitlab::Metrics::Dashboard::Finder).to receive(:find).with(project, user, environment: environment, dashboard_path: path).and_return(status: :success, dashboard: json_content)
+
+ dashboard_instance = described_class.find_for(project: project, user: user, path: path, options: { environment: environment })
+
+ expect(dashboard_instance).to be_instance_of described_class
+ expect(dashboard_instance.environment).to be environment
+ expect(dashboard_instance.path).to be path
+ end
+ end
+
+ context 'dashboard has NOT been found' do
+ it 'returns nil' do
+ allow(Gitlab::Metrics::Dashboard::Finder).to receive(:find).and_return(status: :error)
+
+ dashboard_instance = described_class.find_for(project: project, user: user, path: path, options: { environment: environment })
+
+ expect(dashboard_instance).to be_nil
+ end
+ end
+ end
+
+ describe '#to_yaml' do
+ subject { prometheus_dashboard.to_yaml }
+
+ let(:prometheus_dashboard) { described_class.from_json(json_content) }
+ let(:expected_yaml) do
+ "---\npanel_groups:\n- panels:\n - metrics:\n - id: metric_of_ages\n unit: count\n label: Metric of Ages\n query: \n query_range: http_requests_total\n type: area-chart\n title: Chart Title\n y_label: Y-Axis\n weight: \n group: Group Title\n priority: \ndashboard: Dashboard Title\n"
+ end
+
+ it { is_expected.to eq(expected_yaml) }
+ end
+end
diff --git a/spec/models/performance_monitoring/prometheus_metric_spec.rb b/spec/models/performance_monitoring/prometheus_metric_spec.rb
new file mode 100644
index 00000000000..08288e5d993
--- /dev/null
+++ b/spec/models/performance_monitoring/prometheus_metric_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PerformanceMonitoring::PrometheusMetric do
+ let(:json_content) do
+ {
+ "id" => "metric_of_ages",
+ "unit" => "count",
+ "label" => "Metric of Ages",
+ "query_range" => "http_requests_total"
+ }
+ end
+
+ describe '.from_json' do
+ subject { described_class.from_json(json_content) }
+
+ it 'creates a PrometheusMetric object' do
+ expect(subject).to be_a PerformanceMonitoring::PrometheusMetric
+ expect(subject.id).to eq(json_content['id'])
+ expect(subject.unit).to eq(json_content['unit'])
+ expect(subject.label).to eq(json_content['label'])
+ expect(subject.query_range).to eq(json_content['query_range'])
+ end
+
+ describe 'validations' do
+ context 'when unit is missing' do
+ before do
+ json_content['unit'] = nil
+ end
+
+ subject { described_class.from_json(json_content) }
+
+ it { expect { subject }.to raise_error(ActiveModel::ValidationError) }
+ end
+
+ context 'when query and query_range is missing' do
+ before do
+ json_content['query_range'] = nil
+ end
+
+ subject { described_class.from_json(json_content) }
+
+ it { expect { subject }.to raise_error(ActiveModel::ValidationError) }
+ end
+
+ context 'when query_range is missing but query is available' do
+ before do
+ json_content['query_range'] = nil
+ json_content['query'] = 'http_requests_total'
+ end
+
+ subject { described_class.from_json(json_content) }
+
+ it { is_expected.to be_valid }
+ end
+ end
+ end
+end
diff --git a/spec/models/performance_monitoring/prometheus_panel_group_spec.rb b/spec/models/performance_monitoring/prometheus_panel_group_spec.rb
new file mode 100644
index 00000000000..2447bb5df94
--- /dev/null
+++ b/spec/models/performance_monitoring/prometheus_panel_group_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PerformanceMonitoring::PrometheusPanelGroup do
+ let(:json_content) do
+ {
+ "group" => "Group Title",
+ "panels" => [{
+ "type" => "area-chart",
+ "title" => "Chart Title",
+ "y_label" => "Y-Axis",
+ "metrics" => [{
+ "id" => "metric_of_ages",
+ "unit" => "count",
+ "label" => "Metric of Ages",
+ "query_range" => "http_requests_total"
+ }]
+ }]
+ }
+ end
+
+ describe '.from_json' do
+ subject { described_class.from_json(json_content) }
+
+ it 'creates a PrometheusPanelGroup object' do
+ expect(subject).to be_a PerformanceMonitoring::PrometheusPanelGroup
+ expect(subject.group).to eq(json_content['group'])
+ expect(subject.panels).to all(be_a PerformanceMonitoring::PrometheusPanel)
+ end
+
+ describe 'validations' do
+ context 'when group is missing' do
+ before do
+ json_content['group'] = nil
+ end
+
+ subject { described_class.from_json(json_content) }
+
+ it { expect { subject }.to raise_error(ActiveModel::ValidationError) }
+ end
+
+ context 'when panels are missing' do
+ before do
+ json_content['panels'] = []
+ end
+
+ subject { described_class.from_json(json_content) }
+
+ it { expect { subject }.to raise_error(ActiveModel::ValidationError) }
+ end
+ end
+ end
+end
diff --git a/spec/models/performance_monitoring/prometheus_panel_spec.rb b/spec/models/performance_monitoring/prometheus_panel_spec.rb
new file mode 100644
index 00000000000..f5e04ec91e2
--- /dev/null
+++ b/spec/models/performance_monitoring/prometheus_panel_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe PerformanceMonitoring::PrometheusPanel do
+ let(:json_content) do
+ {
+ "max_value" => 1,
+ "type" => "area-chart",
+ "title" => "Chart Title",
+ "y_label" => "Y-Axis",
+ "weight" => 1,
+ "metrics" => [{
+ "id" => "metric_of_ages",
+ "unit" => "count",
+ "label" => "Metric of Ages",
+ "query_range" => "http_requests_total"
+ }]
+ }
+ end
+
+ describe '#new' do
+ it 'accepts old schema format' do
+ expect { described_class.new(json_content) }.not_to raise_error
+ end
+
+ it 'accepts new schema format' do
+ expect { described_class.new(json_content.merge("y_axis" => { "precision" => 0 })) }.not_to raise_error
+ end
+ end
+
+ describe '.from_json' do
+ subject { described_class.from_json(json_content) }
+
+ it 'creates a PrometheusPanelGroup object' do
+ expect(subject).to be_a PerformanceMonitoring::PrometheusPanel
+ expect(subject.type).to eq(json_content['type'])
+ expect(subject.title).to eq(json_content['title'])
+ expect(subject.y_label).to eq(json_content['y_label'])
+ expect(subject.weight).to eq(json_content['weight'])
+ expect(subject.metrics).to all(be_a PerformanceMonitoring::PrometheusMetric)
+ end
+
+ describe 'validations' do
+ context 'when title is missing' do
+ before do
+ json_content['title'] = nil
+ end
+
+ subject { described_class.from_json(json_content) }
+
+ it { expect { subject }.to raise_error(ActiveModel::ValidationError) }
+ end
+
+ context 'when metrics are missing' do
+ before do
+ json_content['metrics'] = []
+ end
+
+ subject { described_class.from_json(json_content) }
+
+ it { expect { subject }.to raise_error(ActiveModel::ValidationError) }
+ end
+ end
+ end
+
+ describe '.id' do
+ it 'returns hexdigest of group_title, type and title as the panel id' do
+ group_title = 'Business Group'
+ panel_type = 'area-chart'
+ panel_title = 'New feature requests made'
+
+ expect(Digest::SHA2).to receive(:hexdigest).with("#{group_title}#{panel_type}#{panel_title}").and_return('hexdigest')
+ expect(described_class.new(title: panel_title, type: panel_type).id(group_title)).to eql 'hexdigest'
+ end
+ end
+end
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index 356b0e18559..a87cdcf9344 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -147,13 +147,13 @@ describe RemoteMirror, :mailer do
git_remote_mirror = stub_const('Gitlab::Git::RemoteMirror', spy)
mirror = build(:remote_mirror)
- expect(mirror).to receive(:options_for_update).and_return(options: true)
+ expect(mirror).to receive(:options_for_update).and_return(keep_divergent_refs: true)
mirror.update_repository
expect(git_remote_mirror).to have_received(:new).with(
mirror.project.repository.raw,
mirror.remote_name,
- options: true
+ keep_divergent_refs: true
)
expect(git_remote_mirror).to have_received(:update)
end
diff --git a/spec/requests/api/remote_mirrors_spec.rb b/spec/requests/api/remote_mirrors_spec.rb
index e32cd22ce18..3029b8443b0 100644
--- a/spec/requests/api/remote_mirrors_spec.rb
+++ b/spec/requests/api/remote_mirrors_spec.rb
@@ -78,10 +78,6 @@ describe API::RemoteMirrors do
let(:route) { ->(id) { "/projects/#{project.id}/remote_mirrors/#{id}" } }
let(:mirror) { project.remote_mirrors.first }
- before do
- stub_feature_flags(keep_divergent_refs: false)
- end
-
it 'requires `admin_remote_mirror` permission' do
put api(route[mirror.id], developer)
@@ -100,24 +96,7 @@ describe API::RemoteMirrors do
expect(response).to have_gitlab_http_status(:success)
expect(json_response['enabled']).to eq(false)
expect(json_response['only_protected_branches']).to eq(true)
-
- # Deleted due to lack of feature availability
- expect(json_response['keep_divergent_refs']).to be_nil
- end
-
- context 'with the `keep_divergent_refs` feature enabled' do
- before do
- stub_feature_flags(keep_divergent_refs: project)
- end
-
- it 'updates the `keep_divergent_refs` attribute' do
- project.add_maintainer(user)
-
- put api(route[mirror.id], user), params: { keep_divergent_refs: 'true' }
-
- expect(response).to have_gitlab_http_status(:success)
- expect(json_response['keep_divergent_refs']).to eq(true)
- end
+ expect(json_response['keep_divergent_refs']).to eq(true)
end
end
end
diff --git a/spec/services/projects/prometheus/alerts/notify_service_spec.rb b/spec/services/projects/prometheus/alerts/notify_service_spec.rb
index bfa784cd212..009543f9016 100644
--- a/spec/services/projects/prometheus/alerts/notify_service_spec.rb
+++ b/spec/services/projects/prometheus/alerts/notify_service_spec.rb
@@ -225,37 +225,18 @@ describe Projects::Prometheus::Alerts::NotifyService do
create(:project_alerting_setting, project: project, token: token)
end
- context 'when alert_management_minimal feature enabled' do
- before do
- stub_feature_flags(alert_management_minimal: true)
- end
-
- context 'with multiple firing alerts and resolving alerts' do
- let(:payload_raw) do
- payload_for(firing: [alert_firing, alert_firing], resolved: [alert_resolved])
- end
-
- it 'processes Prometheus alerts' do
- expect(AlertManagement::ProcessPrometheusAlertService)
- .to receive(:new)
- .with(project, nil, kind_of(Hash))
- .exactly(3).times
- .and_return(process_service)
- expect(process_service).to receive(:execute).exactly(3).times
-
- subject
- end
- end
- end
-
- context 'when alert_management_minimal feature disabled' do
- before do
- stub_feature_flags(alert_management_minimal: false)
+ context 'with multiple firing alerts and resolving alerts' do
+ let(:payload_raw) do
+ payload_for(firing: [alert_firing, alert_firing], resolved: [alert_resolved])
end
- it 'does not process Prometheus alerts' do
+ it 'processes Prometheus alerts' do
expect(AlertManagement::ProcessPrometheusAlertService)
- .not_to receive(:new)
+ .to receive(:new)
+ .with(project, nil, kind_of(Hash))
+ .exactly(3).times
+ .and_return(process_service)
+ expect(process_service).to receive(:execute).exactly(3).times
subject
end
diff --git a/spec/support_specs/helpers/active_record/query_recorder_spec.rb b/spec/support_specs/helpers/active_record/query_recorder_spec.rb
index 0827ce37b07..d15fbb5d4c3 100644
--- a/spec/support_specs/helpers/active_record/query_recorder_spec.rb
+++ b/spec/support_specs/helpers/active_record/query_recorder_spec.rb
@@ -3,8 +3,12 @@
require 'spec_helper'
describe ActiveRecord::QueryRecorder do
- class TestQueries < ActiveRecord::Base
- self.table_name = 'schema_migrations'
+ before do
+ stub_const('TestQueries', Class.new(ActiveRecord::Base))
+
+ TestQueries.class_eval do
+ self.table_name = 'schema_migrations'
+ end
end
describe 'detecting the right number of calls and their origin' do