Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a51e52bf5b
commit
fb553bbc18
|
@ -768,6 +768,8 @@
|
||||||
|
|
||||||
.setup:rules:verify-tests-yml:
|
.setup:rules:verify-tests-yml:
|
||||||
rules:
|
rules:
|
||||||
|
- <<: *if-not-ee
|
||||||
|
when: never
|
||||||
- <<: *if-default-refs
|
- <<: *if-default-refs
|
||||||
changes: *code-backstage-patterns
|
changes: *code-backstage-patterns
|
||||||
when: on_success
|
when: on_success
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -316,7 +316,7 @@ gem 'ruby_parser', '~> 3.8', require: false
|
||||||
gem 'rails-i18n', '~> 6.0'
|
gem 'rails-i18n', '~> 6.0'
|
||||||
gem 'gettext_i18n_rails', '~> 1.8.0'
|
gem 'gettext_i18n_rails', '~> 1.8.0'
|
||||||
gem 'gettext_i18n_rails_js', '~> 1.3'
|
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'
|
gem 'batch-loader', '~> 1.4.0'
|
||||||
|
|
||||||
|
|
|
@ -403,7 +403,7 @@ GEM
|
||||||
json
|
json
|
||||||
get_process_mem (0.2.5)
|
get_process_mem (0.2.5)
|
||||||
ffi (~> 1.0)
|
ffi (~> 1.0)
|
||||||
gettext (3.2.9)
|
gettext (3.3.6)
|
||||||
locale (>= 2.0.5)
|
locale (>= 2.0.5)
|
||||||
text (>= 1.3.0)
|
text (>= 1.3.0)
|
||||||
gettext_i18n_rails (1.8.0)
|
gettext_i18n_rails (1.8.0)
|
||||||
|
@ -653,7 +653,7 @@ GEM
|
||||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||||
rb-inotify (~> 0.9, >= 0.9.7)
|
rb-inotify (~> 0.9, >= 0.9.7)
|
||||||
ruby_dep (~> 1.2)
|
ruby_dep (~> 1.2)
|
||||||
locale (2.1.2)
|
locale (2.1.3)
|
||||||
lockbox (0.3.3)
|
lockbox (0.3.3)
|
||||||
lograge (0.11.2)
|
lograge (0.11.2)
|
||||||
actionpack (>= 4)
|
actionpack (>= 4)
|
||||||
|
@ -1297,7 +1297,7 @@ DEPENDENCIES
|
||||||
fugit (~> 1.2.1)
|
fugit (~> 1.2.1)
|
||||||
fuubar (~> 2.2.0)
|
fuubar (~> 2.2.0)
|
||||||
gemojione (~> 3.3)
|
gemojione (~> 3.3)
|
||||||
gettext (~> 3.2.2)
|
gettext (~> 3.3)
|
||||||
gettext_i18n_rails (~> 1.8.0)
|
gettext_i18n_rails (~> 1.8.0)
|
||||||
gettext_i18n_rails_js (~> 1.3)
|
gettext_i18n_rails_js (~> 1.3)
|
||||||
gitaly (~> 13.3.0.pre.rc1)
|
gitaly (~> 13.3.0.pre.rc1)
|
||||||
|
|
|
@ -72,8 +72,8 @@ export default class MilestoneSelect {
|
||||||
return initDeprecatedJQueryDropdown($dropdown, {
|
return initDeprecatedJQueryDropdown($dropdown, {
|
||||||
showMenuAbove,
|
showMenuAbove,
|
||||||
data: (term, callback) => {
|
data: (term, callback) => {
|
||||||
let contextId = $dropdown.get(0).dataset.projectId;
|
let contextId = parseInt($dropdown.get(0).dataset.projectId, 10);
|
||||||
let getMilestones = Api.projectMilestones;
|
let getMilestones = Api.projectMilestones.bind(Api);
|
||||||
const reqParams = { state: 'active', include_parent_milestones: true };
|
const reqParams = { state: 'active', include_parent_milestones: true };
|
||||||
|
|
||||||
if (term) {
|
if (term) {
|
||||||
|
@ -83,7 +83,7 @@ export default class MilestoneSelect {
|
||||||
if (!contextId) {
|
if (!contextId) {
|
||||||
contextId = $dropdown.get(0).dataset.groupId;
|
contextId = $dropdown.get(0).dataset.groupId;
|
||||||
delete reqParams.include_parent_milestones;
|
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!
|
// We don't use $.data() as it caches initial value and never updates!
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { GlButton, GlLoadingIcon } from '@gitlab/ui';
|
||||||
import { deprecatedCreateFlash as Flash } from '~/flash';
|
import { deprecatedCreateFlash as Flash } from '~/flash';
|
||||||
import { __, sprintf } from '~/locale';
|
import { __, sprintf } from '~/locale';
|
||||||
import TitleField from '~/vue_shared/components/form/title.vue';
|
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 FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue';
|
||||||
|
|
||||||
import UpdateSnippetMutation from '../mutations/updateSnippet.mutation.graphql';
|
import UpdateSnippetMutation from '../mutations/updateSnippet.mutation.graphql';
|
||||||
|
@ -88,9 +88,7 @@ export default {
|
||||||
},
|
},
|
||||||
cancelButtonHref() {
|
cancelButtonHref() {
|
||||||
if (this.newSnippet) {
|
if (this.newSnippet) {
|
||||||
return this.projectPath
|
return joinPaths('/', gon.relative_url_root, this.projectPath, '-/snippets');
|
||||||
? `${gon.relative_url_root}${this.projectPath}/-/snippets`
|
|
||||||
: `${gon.relative_url_root}/-/snippets`;
|
|
||||||
}
|
}
|
||||||
return this.snippet.webUrl;
|
return this.snippet.webUrl;
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,6 +17,7 @@ import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||||
import DeleteSnippetMutation from '../mutations/deleteSnippet.mutation.graphql';
|
import DeleteSnippetMutation from '../mutations/deleteSnippet.mutation.graphql';
|
||||||
import CanCreatePersonalSnippet from '../queries/userPermissions.query.graphql';
|
import CanCreatePersonalSnippet from '../queries/userPermissions.query.graphql';
|
||||||
import CanCreateProjectSnippet from '../queries/projectPermissions.query.graphql';
|
import CanCreateProjectSnippet from '../queries/projectPermissions.query.graphql';
|
||||||
|
import { joinPaths } from '~/lib/utils/url_utility';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -96,8 +97,8 @@ export default {
|
||||||
condition: this.canCreateSnippet,
|
condition: this.canCreateSnippet,
|
||||||
text: __('New snippet'),
|
text: __('New snippet'),
|
||||||
href: this.snippet.project
|
href: this.snippet.project
|
||||||
? `${this.snippet.project.webUrl}/-/snippets/new`
|
? joinPaths(this.snippet.project.webUrl, '-/snippets/new')
|
||||||
: `${gon.relative_url_root}/-/snippets/new`,
|
: joinPaths('/', gon.relative_url_root, '/-/snippets/new'),
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
category: 'secondary',
|
category: 'secondary',
|
||||||
cssClass: 'ml-2',
|
cssClass: 'ml-2',
|
||||||
|
|
|
@ -6,8 +6,26 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
GlDrawer,
|
GlDrawer,
|
||||||
},
|
},
|
||||||
|
props: {
|
||||||
|
features: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['open']),
|
...mapState(['open']),
|
||||||
|
parsedFeatures() {
|
||||||
|
let features;
|
||||||
|
|
||||||
|
try {
|
||||||
|
features = JSON.parse(this.$props.features) || [];
|
||||||
|
} catch (err) {
|
||||||
|
features = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return features;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['closeDrawer']),
|
...mapActions(['closeDrawer']),
|
||||||
|
@ -22,7 +40,12 @@ export default {
|
||||||
<h4>{{ __("What's new at GitLab") }}</h4>
|
<h4>{{ __("What's new at GitLab") }}</h4>
|
||||||
</template>
|
</template>
|
||||||
<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>
|
</template>
|
||||||
</gl-drawer>
|
</gl-drawer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,16 +4,21 @@ import Trigger from './components/trigger.vue';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
|
const whatsNewElm = document.getElementById('whats-new-app');
|
||||||
|
|
||||||
// eslint-disable-next-line no-new
|
// eslint-disable-next-line no-new
|
||||||
new Vue({
|
new Vue({
|
||||||
el: document.getElementById('whats-new-app'),
|
el: whatsNewElm,
|
||||||
store,
|
store,
|
||||||
components: {
|
components: {
|
||||||
App,
|
App,
|
||||||
},
|
},
|
||||||
|
|
||||||
render(createElement) {
|
render(createElement) {
|
||||||
return createElement('app');
|
return createElement('app', {
|
||||||
|
props: {
|
||||||
|
features: whatsNewElm.getAttribute('data-features'),
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -100,7 +100,7 @@
|
||||||
= sprite_icon('close', size: 12, css_class: 'close-icon js-navbar-toggle-left')
|
= sprite_icon('close', size: 12, css_class: 'close-icon js-navbar-toggle-left')
|
||||||
|
|
||||||
- if ::Feature.enabled?(:whats_new_drawer)
|
- 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)
|
- 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 : '' } }
|
.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 : '' } }
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
name: "issue[milestone_id]",
|
name: "issue[milestone_id]",
|
||||||
"v-if" => "issue.milestone" }
|
"v-if" => "issue.milestone" }
|
||||||
.dropdown
|
.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-selected" => "milestoneTitle",
|
||||||
":data-issuable-id" => "issue.iid",
|
":data-issuable-id" => "issue.iid",
|
||||||
":data-project-id" => "issue.project_id" }
|
":data-project-id" => "issue.project_id" }
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
.title
|
.title
|
||||||
= _('Milestone')
|
= _('Milestone')
|
||||||
.filter-item
|
.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
|
.block
|
||||||
.title
|
.title
|
||||||
= _('Labels')
|
= _('Labels')
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
- if selected.present? || params[:milestone_title].present?
|
- if selected.present? || params[:milestone_title].present?
|
||||||
= hidden_field_tag(name, name == :milestone_title ? selected_text : selected.id)
|
= 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",
|
= 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
|
- if project
|
||||||
%ul.dropdown-footer-list
|
%ul.dropdown-footer-list
|
||||||
- if can? current_user, :admin_milestone, project
|
- if can? current_user, :admin_milestone, project
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
|
|
||||||
.selectbox.hide-collapsed
|
.selectbox.hide-collapsed
|
||||||
= f.hidden_field 'milestone_id', value: milestone[:id], id: nil
|
= 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?
|
- 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 }
|
= 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 }
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add virtual actions tracker for Usage Ping
|
||||||
|
merge_request: 39694
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -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
|
|
@ -4,3 +4,13 @@
|
||||||
tmp/restart.txt
|
tmp/restart.txt
|
||||||
tmp/caching-dev.txt
|
tmp/caching-dev.txt
|
||||||
).each { |path| Spring.watch(path) }
|
).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
|
||||||
|
|
|
@ -7,14 +7,13 @@ class SchedulePopulateResolvedOnDefaultBranchColumn < ActiveRecord::Migration[6.
|
||||||
BATCH_SIZE = 100
|
BATCH_SIZE = 100
|
||||||
DELAY_INTERVAL = 5.minutes.to_i
|
DELAY_INTERVAL = 5.minutes.to_i
|
||||||
MIGRATION_CLASS = 'PopulateResolvedOnDefaultBranchColumn'
|
MIGRATION_CLASS = 'PopulateResolvedOnDefaultBranchColumn'
|
||||||
BASE_MODEL = EE::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchColumn::Vulnerability
|
|
||||||
|
|
||||||
disable_ddl_transaction!
|
disable_ddl_transaction!
|
||||||
|
|
||||||
def up
|
def up
|
||||||
return unless run_migration?
|
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)
|
project_ids = batch.pluck(:project_id)
|
||||||
migrate_in(index * DELAY_INTERVAL, MIGRATION_CLASS, project_ids)
|
migrate_in(index * DELAY_INTERVAL, MIGRATION_CLASS, project_ids)
|
||||||
end
|
end
|
||||||
|
|
|
@ -179,12 +179,14 @@ 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
|
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.
|
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
Be aware that strace can have major impacts to system performance when it is running.
|
Be aware that strace can have major impacts to system performance when it is running.
|
||||||
|
|
||||||
|
#### Strace Resources
|
||||||
|
|
||||||
|
- 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
|
### The Strace Parser tool
|
||||||
|
|
||||||
Our [strace-parser tool](https://gitlab.com/wchandler/strace-parser) can be used to
|
Our [strace-parser tool](https://gitlab.com/wchandler/strace-parser) can be used to
|
||||||
|
|
|
@ -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
|
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.
|
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:
|
Arguments:
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,8 @@ group is public.
|
||||||
|
|
||||||
#### Eligible Approvers
|
#### 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:
|
The following users can approve merge requests:
|
||||||
|
|
||||||
- Users who have been added as approvers at the project or merge request levels with
|
- 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)
|
and [**Prevent committers approval**](#prevent-approval-of-merge-requests-by-their-committers) (disabled by default)
|
||||||
are enabled on the project settings.
|
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
|
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.
|
to if they have any questions or inputs about the content of the merge request.
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -3,8 +3,6 @@
|
||||||
module Gitlab
|
module Gitlab
|
||||||
module UsageDataCounters
|
module UsageDataCounters
|
||||||
module TrackUniqueEvents
|
module TrackUniqueEvents
|
||||||
KEY_EXPIRY_LENGTH = 29.days
|
|
||||||
|
|
||||||
WIKI_ACTION = :wiki_action
|
WIKI_ACTION = :wiki_action
|
||||||
DESIGN_ACTION = :design_action
|
DESIGN_ACTION = :design_action
|
||||||
PUSH_ACTION = :project_action
|
PUSH_ACTION = :project_action
|
||||||
|
@ -27,21 +25,17 @@ module Gitlab
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def track_event(event_action:, event_target:, author_id:, time: Time.zone.now)
|
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_target?(event_target)
|
||||||
return unless valid_action?(event_action)
|
return unless valid_action?(event_action)
|
||||||
|
|
||||||
transformed_target = transform_target(event_target)
|
transformed_target = transform_target(event_target)
|
||||||
transformed_action = transform_action(event_action, transformed_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
|
end
|
||||||
|
|
||||||
def count_unique_events(event_action:, date_from:, date_to:)
|
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::UsageDataCounters::TrackUniqueActions.count_unique(action: event_action, date_from: date_from, date_to: date_to)
|
||||||
|
|
||||||
Gitlab::Redis::HLL.count(keys: keys)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -61,11 +55,6 @@ module Gitlab
|
||||||
def valid_action?(action)
|
def valid_action?(action)
|
||||||
Event.actions.key?(action)
|
Event.actions.key?(action)
|
||||||
end
|
end
|
||||||
|
|
||||||
def key(event_action, date)
|
|
||||||
year_day = date.strftime('%G-%j')
|
|
||||||
"#{year_day}-{#{event_action}}"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
require 'airborne'
|
require 'airborne'
|
||||||
|
|
||||||
module QA
|
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
|
describe 'Users API' do
|
||||||
let(:api_client) { Runtime::API::Client.new(:gitlab, ip_limits: true) }
|
let(:api_client) { Runtime::API::Client.new(:gitlab, ip_limits: true) }
|
||||||
let(:request) { Runtime::API::Request.new(api_client, '/users') }
|
let(:request) { Runtime::API::Request.new(api_client, '/users') }
|
||||||
|
|
|
@ -20,7 +20,7 @@ module QA
|
||||||
end
|
end
|
||||||
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
|
# Issue pages always make AJAX requests
|
||||||
Resource::Issue.fabricate_via_browser_ui! do |issue|
|
Resource::Issue.fabricate_via_browser_ui! do |issue|
|
||||||
issue.title = 'Performance bar test'
|
issue.title = 'Performance bar test'
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
||||||
|
---
|
||||||
|
- title: It's gonna be a bright
|
|
@ -0,0 +1,2 @@
|
||||||
|
---
|
||||||
|
- title: bright
|
|
@ -0,0 +1,2 @@
|
||||||
|
---
|
||||||
|
- title: bright and sunshinin' day
|
|
@ -200,8 +200,8 @@ describe('Snippet Edit app', () => {
|
||||||
|
|
||||||
it.each`
|
it.each`
|
||||||
projectPath | snippetArg | expectation
|
projectPath | snippetArg | expectation
|
||||||
${''} | ${[]} | ${`${relativeUrlRoot}/-/snippets`}
|
${''} | ${[]} | ${urlUtils.joinPaths('/', relativeUrlRoot, '-', 'snippets')}
|
||||||
${'project/path'} | ${[]} | ${`${relativeUrlRoot}project/path/-/snippets`}
|
${'project/path'} | ${[]} | ${urlUtils.joinPaths('/', relativeUrlRoot, 'project/path/-', 'snippets')}
|
||||||
${''} | ${[createTestSnippet()]} | ${TEST_WEB_URL}
|
${''} | ${[createTestSnippet()]} | ${TEST_WEB_URL}
|
||||||
${'project/path'} | ${[createTestSnippet()]} | ${TEST_WEB_URL}
|
${'project/path'} | ${[createTestSnippet()]} | ${TEST_WEB_URL}
|
||||||
`(
|
`(
|
||||||
|
|
|
@ -11,8 +11,9 @@ describe('App', () => {
|
||||||
let store;
|
let store;
|
||||||
let actions;
|
let actions;
|
||||||
let state;
|
let state;
|
||||||
|
let propsData = { features: '[ {"title":"Whats New Drawer"} ]' };
|
||||||
|
|
||||||
beforeEach(() => {
|
const buildWrapper = () => {
|
||||||
actions = {
|
actions = {
|
||||||
closeDrawer: jest.fn(),
|
closeDrawer: jest.fn(),
|
||||||
};
|
};
|
||||||
|
@ -29,7 +30,12 @@ describe('App', () => {
|
||||||
wrapper = mount(App, {
|
wrapper = mount(App, {
|
||||||
localVue,
|
localVue,
|
||||||
store,
|
store,
|
||||||
|
propsData,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
buildWrapper();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -54,4 +60,15 @@ describe('App', () => {
|
||||||
|
|
||||||
expect(getDrawer().props('open')).toBe(openState);
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -6,7 +6,7 @@ RSpec.describe RemoteMirrorNotificationWorker, :mailer do
|
||||||
let_it_be(:project) { create(:project, :repository, :remote_mirror) }
|
let_it_be(:project) { create(:project, :repository, :remote_mirror) }
|
||||||
let_it_be(:mirror) { project.remote_mirrors.first }
|
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
|
it 'calls NotificationService#remote_mirror_update_failed when the mirror exists' do
|
||||||
mirror.update_column(:last_error, "There was a problem fetching")
|
mirror.update_column(:last_error, "There was a problem fetching")
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue