Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-08-26 09:10:34 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-08-26 09:10:34 +0300
commitfb553bbc1899eddaddb07cd9685cdabffbed9962 (patch)
tree473f1ad59a01e98d6ee1a04f462e524bb585f1e4
parenta51e52bf5b7a708255a858ca51de8d4a6e58b074 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock6
-rw-r--r--app/assets/javascripts/milestone_select.js6
-rw-r--r--app/assets/javascripts/snippets/components/edit.vue6
-rw-r--r--app/assets/javascripts/snippets/components/snippet_header.vue5
-rw-r--r--app/assets/javascripts/whats_new/components/app.vue25
-rw-r--r--app/assets/javascripts/whats_new/index.js11
-rw-r--r--app/controllers/concerns/redis_tracking.rb44
-rw-r--r--app/helpers/whats_new_helper.rb24
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/shared/boards/components/sidebar/_milestone.html.haml2
-rw-r--r--app/views/shared/issuable/_bulk_update_sidebar.html.haml2
-rw-r--r--app/views/shared/issuable/_milestone_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--changelogs/unreleased/232824-fj-track-unique-virtual-actions.yml5
-rw-r--r--config/feature_flags/development/track_editor_edit_actions.yml7
-rw-r--r--config/spring.rb10
-rw-r--r--db/post_migrate/20200806100713_schedule_populate_resolved_on_default_branch_column.rb3
-rw-r--r--doc/administration/troubleshooting/linux_cheat_sheet.md8
-rw-r--r--doc/development/telemetry/usage_ping.md34
-rw-r--r--doc/user/project/merge_requests/merge_request_approvals.md5
-rw-r--r--lib/gitlab/usage_data_counters/editor_unique_counter.rb49
-rw-r--r--lib/gitlab/usage_data_counters/track_unique_actions.rb36
-rw-r--r--lib/gitlab/usage_data_counters/track_unique_events.rb15
-rw-r--r--qa/qa/specs/features/api/1_manage/rate_limits_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb2
-rw-r--r--spec/controllers/concerns/redis_tracking_spec.rb98
-rw-r--r--spec/fixtures/whats_new/01.yml2
-rw-r--r--spec/fixtures/whats_new/02.yml2
-rw-r--r--spec/fixtures/whats_new/05.yml2
-rw-r--r--spec/frontend/snippets/components/edit_spec.js4
-rw-r--r--spec/frontend/whats_new/components/app_spec.js19
-rw-r--r--spec/helpers/whats_new_helper_spec.rb22
-rw-r--r--spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb70
-rw-r--r--spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb43
-rw-r--r--spec/workers/remote_mirror_notification_worker_spec.rb2
37 files changed, 532 insertions, 49 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index b2836d2600e..fbadec1f63d 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -768,6 +768,8 @@
.setup:rules:verify-tests-yml:
rules:
+ - <<: *if-not-ee
+ when: never
- <<: *if-default-refs
changes: *code-backstage-patterns
when: on_success
diff --git a/Gemfile b/Gemfile
index 05f7801d0f7..1bf60a4098d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -316,7 +316,7 @@ gem 'ruby_parser', '~> 3.8', require: false
gem 'rails-i18n', '~> 6.0'
gem 'gettext_i18n_rails', '~> 1.8.0'
gem 'gettext_i18n_rails_js', '~> 1.3'
-gem 'gettext', '~> 3.2.2', require: false, group: :development
+gem 'gettext', '~> 3.3', require: false, group: :development
gem 'batch-loader', '~> 1.4.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 13165a1e8b2..d36f9b70ca8 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -403,7 +403,7 @@ GEM
json
get_process_mem (0.2.5)
ffi (~> 1.0)
- gettext (3.2.9)
+ gettext (3.3.6)
locale (>= 2.0.5)
text (>= 1.3.0)
gettext_i18n_rails (1.8.0)
@@ -653,7 +653,7 @@ GEM
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
- locale (2.1.2)
+ locale (2.1.3)
lockbox (0.3.3)
lograge (0.11.2)
actionpack (>= 4)
@@ -1297,7 +1297,7 @@ DEPENDENCIES
fugit (~> 1.2.1)
fuubar (~> 2.2.0)
gemojione (~> 3.3)
- gettext (~> 3.2.2)
+ gettext (~> 3.3)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly (~> 13.3.0.pre.rc1)
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 53598165384..52f6786ca28 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -72,8 +72,8 @@ export default class MilestoneSelect {
return initDeprecatedJQueryDropdown($dropdown, {
showMenuAbove,
data: (term, callback) => {
- let contextId = $dropdown.get(0).dataset.projectId;
- let getMilestones = Api.projectMilestones;
+ let contextId = parseInt($dropdown.get(0).dataset.projectId, 10);
+ let getMilestones = Api.projectMilestones.bind(Api);
const reqParams = { state: 'active', include_parent_milestones: true };
if (term) {
@@ -83,7 +83,7 @@ export default class MilestoneSelect {
if (!contextId) {
contextId = $dropdown.get(0).dataset.groupId;
delete reqParams.include_parent_milestones;
- getMilestones = Api.groupMilestones;
+ getMilestones = Api.groupMilestones.bind(Api);
}
// We don't use $.data() as it caches initial value and never updates!
diff --git a/app/assets/javascripts/snippets/components/edit.vue b/app/assets/javascripts/snippets/components/edit.vue
index 5bca486b92e..227d9043d7f 100644
--- a/app/assets/javascripts/snippets/components/edit.vue
+++ b/app/assets/javascripts/snippets/components/edit.vue
@@ -4,7 +4,7 @@ import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { deprecatedCreateFlash as Flash } from '~/flash';
import { __, sprintf } from '~/locale';
import TitleField from '~/vue_shared/components/form/title.vue';
-import { redirectTo } from '~/lib/utils/url_utility';
+import { redirectTo, joinPaths } from '~/lib/utils/url_utility';
import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue';
import UpdateSnippetMutation from '../mutations/updateSnippet.mutation.graphql';
@@ -88,9 +88,7 @@ export default {
},
cancelButtonHref() {
if (this.newSnippet) {
- return this.projectPath
- ? `${gon.relative_url_root}${this.projectPath}/-/snippets`
- : `${gon.relative_url_root}/-/snippets`;
+ return joinPaths('/', gon.relative_url_root, this.projectPath, '-/snippets');
}
return this.snippet.webUrl;
},
diff --git a/app/assets/javascripts/snippets/components/snippet_header.vue b/app/assets/javascripts/snippets/components/snippet_header.vue
index 05a09347d06..0ca69f3161a 100644
--- a/app/assets/javascripts/snippets/components/snippet_header.vue
+++ b/app/assets/javascripts/snippets/components/snippet_header.vue
@@ -17,6 +17,7 @@ import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import DeleteSnippetMutation from '../mutations/deleteSnippet.mutation.graphql';
import CanCreatePersonalSnippet from '../queries/userPermissions.query.graphql';
import CanCreateProjectSnippet from '../queries/projectPermissions.query.graphql';
+import { joinPaths } from '~/lib/utils/url_utility';
export default {
components: {
@@ -96,8 +97,8 @@ export default {
condition: this.canCreateSnippet,
text: __('New snippet'),
href: this.snippet.project
- ? `${this.snippet.project.webUrl}/-/snippets/new`
- : `${gon.relative_url_root}/-/snippets/new`,
+ ? joinPaths(this.snippet.project.webUrl, '-/snippets/new')
+ : joinPaths('/', gon.relative_url_root, '/-/snippets/new'),
variant: 'success',
category: 'secondary',
cssClass: 'ml-2',
diff --git a/app/assets/javascripts/whats_new/components/app.vue b/app/assets/javascripts/whats_new/components/app.vue
index d974556cb9e..9a06383a57b 100644
--- a/app/assets/javascripts/whats_new/components/app.vue
+++ b/app/assets/javascripts/whats_new/components/app.vue
@@ -6,8 +6,26 @@ export default {
components: {
GlDrawer,
},
+ props: {
+ features: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
computed: {
...mapState(['open']),
+ parsedFeatures() {
+ let features;
+
+ try {
+ features = JSON.parse(this.$props.features) || [];
+ } catch (err) {
+ features = [];
+ }
+
+ return features;
+ },
},
methods: {
...mapActions(['closeDrawer']),
@@ -22,7 +40,12 @@ export default {
<h4>{{ __("What's new at GitLab") }}</h4>
</template>
<template>
- <div></div>
+ <ul>
+ <li v-for="feature in parsedFeatures" :key="feature.title">
+ <h5>{{ feature.title }}</h5>
+ <p>{{ feature.body }}</p>
+ </li>
+ </ul>
</template>
</gl-drawer>
</div>
diff --git a/app/assets/javascripts/whats_new/index.js b/app/assets/javascripts/whats_new/index.js
index c9ee3404d2a..a6519ab63b1 100644
--- a/app/assets/javascripts/whats_new/index.js
+++ b/app/assets/javascripts/whats_new/index.js
@@ -4,16 +4,21 @@ import Trigger from './components/trigger.vue';
import store from './store';
export default () => {
+ const whatsNewElm = document.getElementById('whats-new-app');
+
// eslint-disable-next-line no-new
new Vue({
- el: document.getElementById('whats-new-app'),
+ el: whatsNewElm,
store,
components: {
App,
},
-
render(createElement) {
- return createElement('app');
+ return createElement('app', {
+ props: {
+ features: whatsNewElm.getAttribute('data-features'),
+ },
+ });
},
});
diff --git a/app/controllers/concerns/redis_tracking.rb b/app/controllers/concerns/redis_tracking.rb
new file mode 100644
index 00000000000..0d8b3e12ebb
--- /dev/null
+++ b/app/controllers/concerns/redis_tracking.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+# Example:
+#
+# # In controller include module
+# # Track event for index action
+#
+# include RedisTracking
+#
+# track_redis_hll_event :index, :show, name: 'i_analytics_dev_ops_score', feature: :my_feature
+module RedisTracking
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def track_redis_hll_event(*controller_actions, name:, feature:)
+ after_action only: controller_actions, if: -> { request.format.html? && request.headers['DNT'] != '1' } do
+ track_unique_redis_hll_event(name, feature)
+ end
+ end
+ end
+
+ private
+
+ def track_unique_redis_hll_event(event_name, feature)
+ return unless metric_feature_enabled?(feature)
+ return unless Gitlab::CurrentSettings.usage_ping_enabled?
+ return unless visitor_id
+
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event(visitor_id, event_name)
+ end
+
+ def metric_feature_enabled?(feature)
+ Feature.enabled?(feature)
+ end
+
+ def visitor_id
+ return cookies[:visitor_id] if cookies[:visitor_id].present?
+ return unless current_user
+
+ uuid = SecureRandom.uuid
+ cookies[:visitor_id] = { value: uuid, expires: 24.months }
+ uuid
+ end
+end
diff --git a/app/helpers/whats_new_helper.rb b/app/helpers/whats_new_helper.rb
new file mode 100644
index 00000000000..f0044daa645
--- /dev/null
+++ b/app/helpers/whats_new_helper.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module WhatsNewHelper
+ EMPTY_JSON = ''.to_json
+
+ def whats_new_most_recent_release_items
+ YAML.load_file(most_recent_release_file_path).to_json
+
+ rescue => e
+ Gitlab::ErrorTracking.track_exception(e, yaml_file_path: most_recent_release_file_path)
+
+ EMPTY_JSON
+ end
+
+ private
+
+ def most_recent_release_file_path
+ Dir.glob(files_path).max
+ end
+
+ def files_path
+ Rails.root.join('data', 'whats_new', '*.yml')
+ end
+end
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 56b70c463d0..8a05768109b 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -100,7 +100,7 @@
= sprite_icon('close', size: 12, css_class: 'close-icon js-navbar-toggle-left')
- if ::Feature.enabled?(:whats_new_drawer)
- #whats-new-app
+ #whats-new-app{ data: { features: whats_new_most_recent_release_items } }
- if can?(current_user, :update_user_status, current_user)
.js-set-status-modal-wrapper{ data: { current_emoji: current_user.status.present? ? current_user.status.emoji : '', current_message: current_user.status.present? ? current_user.status.message : '' } }
diff --git a/app/views/shared/boards/components/sidebar/_milestone.html.haml b/app/views/shared/boards/components/sidebar/_milestone.html.haml
index 23d63fde671..2c894e9b1b3 100644
--- a/app/views/shared/boards/components/sidebar/_milestone.html.haml
+++ b/app/views/shared/boards/components/sidebar/_milestone.html.haml
@@ -16,7 +16,7 @@
name: "issue[milestone_id]",
"v-if" => "issue.milestone" }
.dropdown
- %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", milestones: milestones_filter_path(format: :json), ability_name: "issue", use_id: "true", default_no: "true" },
+ %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", ability_name: "issue", use_id: "true", default_no: "true" },
":data-selected" => "milestoneTitle",
":data-issuable-id" => "issue.iid",
":data-project-id" => "issue.project_id" }
diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
index 0c15d20bfe0..09abe9e89c4 100644
--- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml
+++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
@@ -40,7 +40,7 @@
.title
= _('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, use_id: true, default_label: _("Milestone") } })
.block
.title
= _('Labels')
diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml
index c2da363b8c6..f58156b7c08 100644
--- a/app/views/shared/issuable/_milestone_dropdown.html.haml
+++ b/app/views/shared/issuable/_milestone_dropdown.html.haml
@@ -8,7 +8,7 @@
- if selected.present? || params[:milestone_title].present?
= hidden_field_tag(name, name == :milestone_title ? selected_text : selected.id)
= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "qa-issuable-milestone-dropdown js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "qa-issuable-dropdown-menu-milestone dropdown-menu-selectable dropdown-menu-milestone",
- placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, show_started: show_started, field_name: name, selected: selected_text, project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
+ placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, show_started: show_started, field_name: name, selected: selected_text, project_id: project.try(:id), default_label: "Milestone" } }) do
- if project
%ul.dropdown-footer-list
- if can? current_user, :admin_milestone, project
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 987e875674d..c2fe16be257 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -53,7 +53,7 @@
.selectbox.hide-collapsed
= f.hidden_field 'milestone_id', value: milestone[:id], id: nil
- = dropdown_tag('Milestone', options: { title: _('Assign milestone'), toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: _('Search milestones'), data: { show_no: true, field_name: "#{issuable_type}[milestone_id]", project_id: issuable_sidebar[:project_id], issuable_id: issuable_sidebar[:id], milestones: issuable_sidebar[:project_milestones_path], ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], use_id: true, default_no: true, selected: milestone[:title], null_default: true, display: 'static' }})
+ = dropdown_tag('Milestone', options: { title: _('Assign milestone'), toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: _('Search milestones'), data: { show_no: true, field_name: "#{issuable_type}[milestone_id]", project_id: issuable_sidebar[:project_id], issuable_id: issuable_sidebar[:id], ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], use_id: true, default_no: true, selected: milestone[:title], null_default: true, display: 'static' }})
- if @project.group.present?
= render_if_exists 'shared/issuable/iteration_select', { can_edit: can_edit_issuable, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type }
diff --git a/changelogs/unreleased/232824-fj-track-unique-virtual-actions.yml b/changelogs/unreleased/232824-fj-track-unique-virtual-actions.yml
new file mode 100644
index 00000000000..d7ee320a3ba
--- /dev/null
+++ b/changelogs/unreleased/232824-fj-track-unique-virtual-actions.yml
@@ -0,0 +1,5 @@
+---
+title: Add virtual actions tracker for Usage Ping
+merge_request: 39694
+author:
+type: added
diff --git a/config/feature_flags/development/track_editor_edit_actions.yml b/config/feature_flags/development/track_editor_edit_actions.yml
new file mode 100644
index 00000000000..662dd9f600b
--- /dev/null
+++ b/config/feature_flags/development/track_editor_edit_actions.yml
@@ -0,0 +1,7 @@
+---
+name: track_editor_edit_actions
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39694
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/240928
+group: group::editor
+type: development
+default_enabled: false
diff --git a/config/spring.rb b/config/spring.rb
index c9119b40c08..0092d0fd1b0 100644
--- a/config/spring.rb
+++ b/config/spring.rb
@@ -4,3 +4,13 @@
tmp/restart.txt
tmp/caching-dev.txt
).each { |path| Spring.watch(path) }
+
+Spring.after_fork do
+ if ENV['DEBUGGER_STORED_RUBYLIB']
+ ENV['DEBUGGER_STORED_RUBYLIB'].split(File::PATH_SEPARATOR).each do |path|
+ next unless path =~ /ruby-debug-ide/
+
+ load path + '/ruby-debug-ide/multiprocess/starter.rb'
+ end
+ end
+end
diff --git a/db/post_migrate/20200806100713_schedule_populate_resolved_on_default_branch_column.rb b/db/post_migrate/20200806100713_schedule_populate_resolved_on_default_branch_column.rb
index 818d4e5704b..396b95257e8 100644
--- a/db/post_migrate/20200806100713_schedule_populate_resolved_on_default_branch_column.rb
+++ b/db/post_migrate/20200806100713_schedule_populate_resolved_on_default_branch_column.rb
@@ -7,14 +7,13 @@ class SchedulePopulateResolvedOnDefaultBranchColumn < ActiveRecord::Migration[6.
BATCH_SIZE = 100
DELAY_INTERVAL = 5.minutes.to_i
MIGRATION_CLASS = 'PopulateResolvedOnDefaultBranchColumn'
- BASE_MODEL = EE::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchColumn::Vulnerability
disable_ddl_transaction!
def up
return unless run_migration?
- BASE_MODEL.distinct.each_batch(of: BATCH_SIZE, column: :project_id) do |batch, index|
+ EE::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchColumn::Vulnerability.distinct.each_batch(of: BATCH_SIZE, column: :project_id) do |batch, index|
project_ids = batch.pluck(:project_id)
migrate_in(index * DELAY_INTERVAL, MIGRATION_CLASS, project_ids)
end
diff --git a/doc/administration/troubleshooting/linux_cheat_sheet.md b/doc/administration/troubleshooting/linux_cheat_sheet.md
index 06c49d67f40..f24234e1aff 100644
--- a/doc/administration/troubleshooting/linux_cheat_sheet.md
+++ b/doc/administration/troubleshooting/linux_cheat_sheet.md
@@ -179,11 +179,13 @@ strace -tt -T -f -y -yy -s 1024 -p <pid>
ps auwx | grep unicorn | awk '{ print " -p " $2}' | xargs strace -tt -T -f -y -yy -s 1024 -o /tmp/unicorn.txt
```
-See the [strace zine](https://wizardzines.com/zines/strace/) for a quick walkthrough.
+Be aware that strace can have major impacts to system performance when it is running.
-Brendan Gregg has a more detailed explanation of [how to use strace](http://www.brendangregg.com/blog/2014-05-11/strace-wow-much-syscall.html).
+#### Strace Resources
-Be aware that strace can have major impacts to system performance when it is running.
+- See the [strace zine](https://wizardzines.com/zines/strace/) for a quick walkthrough.
+- Brendan Gregg has a more detailed explanation of [how to use strace](http://www.brendangregg.com/blog/2014-05-11/strace-wow-much-syscall.html).
+- We have a [series of GitLab Unfiltered videos](https://www.youtube.com/playlist?list=PL05JrBw4t0KoC7cIkoAFcRhr4gsVesekg) on using strace to understand GitLab.
### The Strace Parser tool
diff --git a/doc/development/telemetry/usage_ping.md b/doc/development/telemetry/usage_ping.md
index 383fb4a126b..7a3a501fa94 100644
--- a/doc/development/telemetry/usage_ping.md
+++ b/doc/development/telemetry/usage_ping.md
@@ -256,7 +256,39 @@ Implemented using Redis methods [PFADD](https://redis.io/commands/pfadd) and [PF
keys for data storage. For `daily` we keep a key for metric per day of the year, for `weekly` we
keep a key for metric per week of the year.
-1. Track event using `Gitlab::UsageDataCounters::HLLRedisCounter.track_event(entity_id, event_name)`.
+1. Track event in controller using `RedisTracking` module with `track_redis_hll_event(*controller_actions, name:, feature:)`.
+
+ Arguments:
+
+ - `controller_actions`: controller actions we want to track.
+ - `name`: event name.
+ - `feature`: feature name, all metrics we track should be under feature flag.
+
+ Example usage:
+
+ ```ruby
+ # controller
+ class ProjectsController < Projects::ApplicationController
+ include RedisTracking
+
+ skip_before_action :authenticate_user!, only: :show
+ track_redis_hll_event :index, :show, name: 'i_analytics_dev_ops_score', feature: :g_compliance_dashboard_feature
+
+ def index
+ render html: 'index'
+ end
+
+ def new
+ render html: 'new'
+ end
+
+ def show
+ render html: 'show'
+ end
+ end
+ ```
+
+1. Track event using base module `Gitlab::UsageDataCounters::HLLRedisCounter.track_event(entity_id, event_name)`.
Arguments:
diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md
index 407fc5db425..cbfa87651f2 100644
--- a/doc/user/project/merge_requests/merge_request_approvals.md
+++ b/doc/user/project/merge_requests/merge_request_approvals.md
@@ -61,6 +61,8 @@ group is public.
#### Eligible Approvers
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10294) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.3, when an eligible approver comments on a merge request, it appears in the **Commented by** column of the Approvals widget.
+
The following users can approve merge requests:
- Users who have been added as approvers at the project or merge request levels with
@@ -84,8 +86,7 @@ if [**Prevent author approval**](#allowing-merge-request-authors-to-approve-thei
and [**Prevent committers approval**](#prevent-approval-of-merge-requests-by-their-committers) (disabled by default)
are enabled on the project settings.
-[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10294) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.3,
-when an eligible approver comments on a merge request, it appears in the **Commented by** column of the Approvals widget,
+When an eligible approver comments on a merge request, it appears in the **Commented by** column of the Approvals widget,
indicating who has engaged in the merge request review. Authors and reviewers can also easily identify who they should reach out
to if they have any questions or inputs about the content of the merge request.
diff --git a/lib/gitlab/usage_data_counters/editor_unique_counter.rb b/lib/gitlab/usage_data_counters/editor_unique_counter.rb
new file mode 100644
index 00000000000..251c83d3eed
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/editor_unique_counter.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module UsageDataCounters
+ module EditorUniqueCounter
+ EDIT_BY_SNIPPET_EDITOR = :edit_by_snippet_editor
+ EDIT_BY_SFE = :edit_by_sfe
+ EDIT_BY_WEB_IDE = :edit_by_web_ide
+
+ class << self
+ def track_web_ide_edit_action(author:, time: Time.zone.now)
+ track_unique_action(EDIT_BY_WEB_IDE, author, time)
+ end
+
+ def count_web_ide_edit_actions(date_from:, date_to:)
+ count_unique(EDIT_BY_WEB_IDE, date_from, date_to)
+ end
+
+ def track_sfe_edit_action(author:, time: Time.zone.now)
+ track_unique_action(EDIT_BY_SFE, author, time)
+ end
+
+ def count_sfe_edit_actions(date_from:, date_to:)
+ count_unique(EDIT_BY_SFE, date_from, date_to)
+ end
+
+ def track_snippet_editor_edit_action(author:, time: Time.zone.now)
+ track_unique_action(EDIT_BY_SNIPPET_EDITOR, author, time)
+ end
+
+ def count_snippet_editor_edit_actions(date_from:, date_to:)
+ count_unique(EDIT_BY_SNIPPET_EDITOR, date_from, date_to)
+ end
+
+ private
+
+ def track_unique_action(action, author, time)
+ return unless Feature.enabled?(:track_editor_edit_actions)
+
+ Gitlab::UsageDataCounters::TrackUniqueActions.track_action(action: action, author_id: author.id, time: time)
+ end
+
+ def count_unique(action, date_from, date_to)
+ Gitlab::UsageDataCounters::TrackUniqueActions.count_unique(action: action, date_from: date_from, date_to: date_to)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data_counters/track_unique_actions.rb b/lib/gitlab/usage_data_counters/track_unique_actions.rb
new file mode 100644
index 00000000000..97e85bef9a5
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/track_unique_actions.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module UsageDataCounters
+ module TrackUniqueActions
+ KEY_EXPIRY_LENGTH = 29.days
+
+ class << self
+ def track_action(action:, author_id:, time: Time.zone.now)
+ return unless Gitlab::CurrentSettings.usage_ping_enabled
+
+ target_key = key(action, time)
+
+ add_key(target_key, author_id)
+ end
+
+ def count_unique(action:, date_from:, date_to:)
+ keys = (date_from.to_date..date_to.to_date).map { |date| key(action, date) }
+
+ Gitlab::Redis::HLL.count(keys: keys)
+ end
+
+ private
+
+ def key(action, date)
+ year_day = date.strftime('%G-%j')
+ "#{year_day}-{#{action}}"
+ end
+
+ def add_key(key, value)
+ Gitlab::Redis::HLL.add(key: key, value: value, expiry: KEY_EXPIRY_LENGTH)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data_counters/track_unique_events.rb b/lib/gitlab/usage_data_counters/track_unique_events.rb
index db18200f059..f2a217e980e 100644
--- a/lib/gitlab/usage_data_counters/track_unique_events.rb
+++ b/lib/gitlab/usage_data_counters/track_unique_events.rb
@@ -3,8 +3,6 @@
module Gitlab
module UsageDataCounters
module TrackUniqueEvents
- KEY_EXPIRY_LENGTH = 29.days
-
WIKI_ACTION = :wiki_action
DESIGN_ACTION = :design_action
PUSH_ACTION = :project_action
@@ -27,21 +25,17 @@ module Gitlab
class << self
def track_event(event_action:, event_target:, author_id:, time: Time.zone.now)
- return unless Gitlab::CurrentSettings.usage_ping_enabled
return unless valid_target?(event_target)
return unless valid_action?(event_action)
transformed_target = transform_target(event_target)
transformed_action = transform_action(event_action, transformed_target)
- target_key = key(transformed_action, time)
- Gitlab::Redis::HLL.add(key: target_key, value: author_id, expiry: KEY_EXPIRY_LENGTH)
+ Gitlab::UsageDataCounters::TrackUniqueActions.track_action(action: transformed_action, author_id: author_id, time: time)
end
def count_unique_events(event_action:, date_from:, date_to:)
- keys = (date_from.to_date..date_to.to_date).map { |date| key(event_action, date) }
-
- Gitlab::Redis::HLL.count(keys: keys)
+ Gitlab::UsageDataCounters::TrackUniqueActions.count_unique(action: event_action, date_from: date_from, date_to: date_to)
end
private
@@ -61,11 +55,6 @@ module Gitlab
def valid_action?(action)
Event.actions.key?(action)
end
-
- def key(event_action, date)
- year_day = date.strftime('%G-%j')
- "#{year_day}-{#{event_action}}"
- end
end
end
end
diff --git a/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb b/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb
index f253923619b..49d670433ca 100644
--- a/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb
@@ -3,7 +3,7 @@
require 'airborne'
module QA
- RSpec.describe 'Manage with IP rate limits', :requires_admin, quarantine: { only: { subdomain: :staging }, issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/240936', type: :investigating } do
+ RSpec.describe 'Manage with IP rate limits', :requires_admin, :skip_live_env do
describe 'Users API' do
let(:api_client) { Runtime::API::Client.new(:gitlab, ip_limits: true) }
let(:request) { Runtime::API::Request.new(api_client, '/users') }
diff --git a/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb b/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb
index 73bb6aeb5fd..06aa59d3573 100644
--- a/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb
+++ b/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb
@@ -20,7 +20,7 @@ module QA
end
end
- it 'shows results for the original request and AJAX requests' do
+ it 'shows results for the original request and AJAX requests', status_issue: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/478' do
# Issue pages always make AJAX requests
Resource::Issue.fabricate_via_browser_ui! do |issue|
issue.title = 'Performance bar test'
diff --git a/spec/controllers/concerns/redis_tracking_spec.rb b/spec/controllers/concerns/redis_tracking_spec.rb
new file mode 100644
index 00000000000..09ea9bd726f
--- /dev/null
+++ b/spec/controllers/concerns/redis_tracking_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe RedisTracking do
+ let(:event_name) { 'g_compliance_dashboard' }
+ let(:feature) { 'g_compliance_dashboard_feature' }
+ let(:user) { create(:user) }
+
+ controller(ApplicationController) do
+ include RedisTracking
+
+ skip_before_action :authenticate_user!, only: :show
+ track_redis_hll_event :index, :show, name: 'i_analytics_dev_ops_score', feature: :g_compliance_dashboard_feature
+
+ def index
+ render html: 'index'
+ end
+
+ def new
+ render html: 'new'
+ end
+
+ def show
+ render html: 'show'
+ end
+ end
+
+ context 'with feature disabled' do
+ it 'does not track the event' do
+ stub_feature_flags(feature => false)
+
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+
+ get :index
+ end
+ end
+
+ context 'with usage ping disabled' do
+ it 'does not track the event' do
+ stub_feature_flags(feature => true)
+ allow(Gitlab::CurrentSettings).to receive(:usage_ping_enabled?).and_return(false)
+
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+
+ get :index
+ end
+ end
+
+ context 'with feature enabled and usage ping enabled' do
+ before do
+ stub_feature_flags(feature => true)
+ allow(Gitlab::CurrentSettings).to receive(:usage_ping_enabled?).and_return(true)
+ end
+
+ context 'when user is logged in' do
+ it 'tracks the event' do
+ sign_in(user)
+
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
+
+ get :index
+ end
+ end
+
+ context 'when user is not logged in and there is a visitor_id' do
+ let(:visitor_id) { SecureRandom.uuid }
+
+ before do
+ routes.draw { get 'show' => 'anonymous#show' }
+ end
+
+ it 'tracks the event' do
+ cookies[:visitor_id] = { value: visitor_id, expires: 24.months }
+
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
+
+ get :show
+ end
+ end
+
+ context 'when user is not logged in and there is no visitor_id' do
+ it 'does not tracks the event' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+
+ get :index
+ end
+ end
+
+ context 'for untracked action' do
+ it 'does not tracks the event' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
+
+ get :new
+ end
+ end
+ end
+end
diff --git a/spec/fixtures/whats_new/01.yml b/spec/fixtures/whats_new/01.yml
new file mode 100644
index 00000000000..06db95be44f
--- /dev/null
+++ b/spec/fixtures/whats_new/01.yml
@@ -0,0 +1,2 @@
+---
+- title: It's gonna be a bright
diff --git a/spec/fixtures/whats_new/02.yml b/spec/fixtures/whats_new/02.yml
new file mode 100644
index 00000000000..91b0bd7036e
--- /dev/null
+++ b/spec/fixtures/whats_new/02.yml
@@ -0,0 +1,2 @@
+---
+- title: bright
diff --git a/spec/fixtures/whats_new/05.yml b/spec/fixtures/whats_new/05.yml
new file mode 100644
index 00000000000..5b8939a2bc6
--- /dev/null
+++ b/spec/fixtures/whats_new/05.yml
@@ -0,0 +1,2 @@
+---
+- title: bright and sunshinin' day
diff --git a/spec/frontend/snippets/components/edit_spec.js b/spec/frontend/snippets/components/edit_spec.js
index 648285bd2f1..dff9bf088c0 100644
--- a/spec/frontend/snippets/components/edit_spec.js
+++ b/spec/frontend/snippets/components/edit_spec.js
@@ -200,8 +200,8 @@ describe('Snippet Edit app', () => {
it.each`
projectPath | snippetArg | expectation
- ${''} | ${[]} | ${`${relativeUrlRoot}/-/snippets`}
- ${'project/path'} | ${[]} | ${`${relativeUrlRoot}project/path/-/snippets`}
+ ${''} | ${[]} | ${urlUtils.joinPaths('/', relativeUrlRoot, '-', 'snippets')}
+ ${'project/path'} | ${[]} | ${urlUtils.joinPaths('/', relativeUrlRoot, 'project/path/-', 'snippets')}
${''} | ${[createTestSnippet()]} | ${TEST_WEB_URL}
${'project/path'} | ${[createTestSnippet()]} | ${TEST_WEB_URL}
`(
diff --git a/spec/frontend/whats_new/components/app_spec.js b/spec/frontend/whats_new/components/app_spec.js
index a349aad9f1c..3b989f3c297 100644
--- a/spec/frontend/whats_new/components/app_spec.js
+++ b/spec/frontend/whats_new/components/app_spec.js
@@ -11,8 +11,9 @@ describe('App', () => {
let store;
let actions;
let state;
+ let propsData = { features: '[ {"title":"Whats New Drawer"} ]' };
- beforeEach(() => {
+ const buildWrapper = () => {
actions = {
closeDrawer: jest.fn(),
};
@@ -29,7 +30,12 @@ describe('App', () => {
wrapper = mount(App, {
localVue,
store,
+ propsData,
});
+ };
+
+ beforeEach(() => {
+ buildWrapper();
});
afterEach(() => {
@@ -54,4 +60,15 @@ describe('App', () => {
expect(getDrawer().props('open')).toBe(openState);
});
+
+ it('renders features when provided as props', () => {
+ expect(wrapper.find('h5').text()).toBe('Whats New Drawer');
+ });
+
+ it('handles bad json argument gracefully', () => {
+ propsData = { features: 'this is not json' };
+ buildWrapper();
+
+ expect(getDrawer().exists()).toBe(true);
+ });
});
diff --git a/spec/helpers/whats_new_helper_spec.rb b/spec/helpers/whats_new_helper_spec.rb
new file mode 100644
index 00000000000..db880163454
--- /dev/null
+++ b/spec/helpers/whats_new_helper_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe WhatsNewHelper do
+ describe '#whats_new_most_recent_release_items' do
+ let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) }
+
+ it 'returns json from the most recent file' do
+ allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
+
+ expect(helper.whats_new_most_recent_release_items).to include({ title: "bright and sunshinin' day" }.to_json)
+ end
+
+ it 'fails gracefully and logs an error' do
+ allow(YAML).to receive(:load_file).and_raise
+
+ expect(Gitlab::ErrorTracking).to receive(:track_exception)
+ expect(helper.whats_new_most_recent_release_items).to eq(''.to_json)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb
new file mode 100644
index 00000000000..ef2435b9cb8
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_redis_shared_state do
+ shared_examples 'tracks and counts action' do
+ let(:user1) { build(:user, id: 1) }
+ let(:user2) { build(:user, id: 2) }
+ let(:user3) { build(:user, id: 3) }
+ let(:time) { Time.zone.now }
+
+ specify do
+ stub_application_setting(usage_ping_enabled: true)
+
+ aggregate_failures do
+ expect(track_action(author: user1)).to be_truthy
+ expect(track_action(author: user1)).to be_truthy
+ expect(track_action(author: user2)).to be_truthy
+ expect(track_action(author: user3, time: time - 3.days)).to be_truthy
+
+ expect(count_unique(date_from: time, date_to: Date.today)).to eq(2)
+ expect(count_unique(date_from: time - 5.days, date_to: Date.tomorrow)).to eq(3)
+ end
+ end
+
+ context 'when feature flag track_editor_edit_actions is disabled' do
+ it 'does not track edit actions' do
+ stub_feature_flags(track_editor_edit_actions: false)
+
+ expect(track_action(author: user1)).to be_nil
+ end
+ end
+ end
+
+ context 'for web IDE edit actions' do
+ it_behaves_like 'tracks and counts action' do
+ def track_action(params)
+ described_class.track_web_ide_edit_action(params)
+ end
+
+ def count_unique(params)
+ described_class.count_web_ide_edit_actions(params)
+ end
+ end
+ end
+
+ context 'for SFE edit actions' do
+ it_behaves_like 'tracks and counts action' do
+ def track_action(params)
+ described_class.track_sfe_edit_action(params)
+ end
+
+ def count_unique(params)
+ described_class.count_sfe_edit_actions(params)
+ end
+ end
+ end
+
+ context 'for snippet editor edit actions' do
+ it_behaves_like 'tracks and counts action' do
+ def track_action(params)
+ described_class.track_snippet_editor_edit_action(params)
+ end
+
+ def count_unique(params)
+ described_class.count_snippet_editor_edit_actions(params)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb b/spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb
new file mode 100644
index 00000000000..d86b8a23dc7
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/track_unique_actions_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataCounters::TrackUniqueActions, :clean_gitlab_redis_shared_state do
+ let(:time) { Time.zone.now }
+ let(:action) { 'example_action' }
+
+ def track_action(params)
+ described_class.track_action(params)
+ end
+
+ def count_unique(params)
+ described_class.count_unique(params)
+ end
+
+ context 'tracking an event' do
+ context 'when tracking successfully' do
+ it 'tracks and counts the events as expected' do
+ stub_application_setting(usage_ping_enabled: true)
+
+ aggregate_failures do
+ expect(track_action(action: action, author_id: 1)).to be_truthy
+ expect(track_action(action: action, author_id: 1)).to be_truthy
+ expect(track_action(action: action, author_id: 2)).to be_truthy
+ expect(track_action(action: action, author_id: 3, time: time - 3.days)).to be_truthy
+
+ expect(count_unique(action: action, date_from: time, date_to: Date.today)).to eq(2)
+ expect(count_unique(action: action, date_from: time - 5.days, date_to: Date.tomorrow)).to eq(3)
+ end
+ end
+ end
+
+ context 'when tracking unsuccessfully' do
+ it 'does not track the event' do
+ stub_application_setting(usage_ping_enabled: false)
+
+ expect(track_action(action: action, author_id: 2)).to be_nil
+ expect(count_unique(action: action, date_from: time, date_to: Date.today)).to eq(0)
+ end
+ end
+ end
+end
diff --git a/spec/workers/remote_mirror_notification_worker_spec.rb b/spec/workers/remote_mirror_notification_worker_spec.rb
index c6fd614fdea..e415e72645c 100644
--- a/spec/workers/remote_mirror_notification_worker_spec.rb
+++ b/spec/workers/remote_mirror_notification_worker_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe RemoteMirrorNotificationWorker, :mailer do
let_it_be(:project) { create(:project, :repository, :remote_mirror) }
let_it_be(:mirror) { project.remote_mirrors.first }
- describe '#execute' do
+ describe '#perform' do
it 'calls NotificationService#remote_mirror_update_failed when the mirror exists' do
mirror.update_column(:last_error, "There was a problem fetching")