Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a6508d0028
commit
e4fc62c0af
|
|
@ -55,21 +55,25 @@ export const createNodeDict = (nodes) => {
|
|||
export const makeLinksFromNodes = (nodes, nodeDict) => {
|
||||
const constantLinkValue = 10; // all links are the same weight
|
||||
return nodes
|
||||
.map((group) => {
|
||||
return group.jobs.map((job) => {
|
||||
if (!job.needs) {
|
||||
return [];
|
||||
}
|
||||
.map(({ jobs, name: groupName }) =>
|
||||
jobs.map(({ needs = [] }) =>
|
||||
needs.reduce((acc, needed) => {
|
||||
// It's possible that we have an optional job, which
|
||||
// is being needed by another job. In that scenario,
|
||||
// the needed job doesn't exist, so we don't want to
|
||||
// create link for it.
|
||||
if (nodeDict[needed]?.name) {
|
||||
acc.push({
|
||||
source: nodeDict[needed].name,
|
||||
target: groupName,
|
||||
value: constantLinkValue,
|
||||
});
|
||||
}
|
||||
|
||||
return job.needs.map((needed) => {
|
||||
return {
|
||||
source: nodeDict[needed]?.name,
|
||||
target: group.name,
|
||||
value: constantLinkValue,
|
||||
};
|
||||
});
|
||||
});
|
||||
})
|
||||
return acc;
|
||||
}, []),
|
||||
),
|
||||
)
|
||||
.flat(2);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,13 @@ export const generateJobNeedsDict = (jobs = {}) => {
|
|||
}
|
||||
|
||||
return jobs[jobName].needs
|
||||
.map((job) => {
|
||||
.reduce((needsAcc, job) => {
|
||||
// It's possible that a needs refer to an optional job
|
||||
// that is not defined in which case we don't add that entry
|
||||
if (!jobs[job]) {
|
||||
return needsAcc;
|
||||
}
|
||||
|
||||
// If we already have the needs of a job in the accumulator,
|
||||
// then we use the memoized data instead of the recursive call
|
||||
// to save some performance.
|
||||
|
|
@ -50,11 +56,11 @@ export const generateJobNeedsDict = (jobs = {}) => {
|
|||
// to the list of `needs` to ensure we can properly reference it.
|
||||
const group = jobs[job];
|
||||
if (group.size > 1) {
|
||||
return [job, group.name, newNeeds];
|
||||
return [...needsAcc, job, group.name, newNeeds];
|
||||
}
|
||||
|
||||
return [job, newNeeds];
|
||||
})
|
||||
return [...needsAcc, job, newNeeds];
|
||||
}, [])
|
||||
.flat(Infinity);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -363,21 +363,8 @@
|
|||
// Collapsed nav
|
||||
|
||||
.toggle-sidebar-button,
|
||||
.close-nav-button,
|
||||
.toggle-right-sidebar-button {
|
||||
transition: width $sidebar-transition-duration;
|
||||
height: $toggle-sidebar-height;
|
||||
padding: 0 $gl-padding;
|
||||
background-color: $gray-light;
|
||||
border: 0;
|
||||
color: $gl-text-color-secondary;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background-color: $border-color;
|
||||
color: $gl-text-color;
|
||||
}
|
||||
.close-nav-button {
|
||||
@include side-panel-toggle;
|
||||
}
|
||||
|
||||
.toggle-sidebar-button,
|
||||
|
|
@ -396,10 +383,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.toggle-right-sidebar-button {
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.collapse-text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
|
|
|||
|
|
@ -446,3 +446,19 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin side-panel-toggle {
|
||||
transition: width $sidebar-transition-duration;
|
||||
height: $toggle-sidebar-height;
|
||||
padding: 0 $gl-padding;
|
||||
background-color: $gray-light;
|
||||
border: 0;
|
||||
color: $gl-text-color-secondary;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background-color: $border-color;
|
||||
color: $gl-text-color;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -232,3 +232,8 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-right-sidebar-button {
|
||||
@include side-panel-toggle;
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@
|
|||
}
|
||||
|
||||
.avatar-image {
|
||||
margin-bottom: $grid-size;
|
||||
|
||||
.avatar {
|
||||
float: none;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
float: left;
|
||||
margin-bottom: 0;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
|
|||
respond_to do |format|
|
||||
format.html do
|
||||
@issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
|
||||
Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter.track_loading_conflict_ui_action(user: current_user)
|
||||
end
|
||||
|
||||
format.json do
|
||||
|
|
@ -42,6 +43,8 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
|
|||
def resolve_conflicts
|
||||
return render_404 unless @conflicts_list.can_be_resolved_in_ui?
|
||||
|
||||
Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter.track_resolve_conflict_action(user: current_user)
|
||||
|
||||
if @merge_request.can_be_merged?
|
||||
render status: :bad_request, json: { message: _('The merge conflicts for this merge request have already been resolved.') }
|
||||
return
|
||||
|
|
|
|||
|
|
@ -524,7 +524,7 @@ class Issue < ApplicationRecord
|
|||
|
||||
def could_not_move(exception)
|
||||
# Symptom of running out of space - schedule rebalancing
|
||||
IssueRebalancingWorker.perform_async(nil, project_id)
|
||||
IssueRebalancingWorker.perform_async(nil, *project.self_or_root_group_ids)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2561,6 +2561,17 @@ class Project < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
# for projects that are part of user namespace, return project.
|
||||
def self_or_root_group_ids
|
||||
if group
|
||||
root_group = root_namespace
|
||||
else
|
||||
project = self
|
||||
end
|
||||
|
||||
[project&.id, root_group&.id]
|
||||
end
|
||||
|
||||
def package_already_taken?(package_name)
|
||||
namespace.root_ancestor.all_projects
|
||||
.joins(:packages)
|
||||
|
|
|
|||
|
|
@ -15,14 +15,13 @@ class IssueRebalancingService
|
|||
[5.seconds, 1.second]
|
||||
].freeze
|
||||
|
||||
def initialize(issue)
|
||||
@issue = issue
|
||||
@base = Issue.relative_positioning_query_base(issue)
|
||||
def initialize(projects_collection)
|
||||
@root_namespace = projects_collection.take.root_namespace # rubocop:disable CodeReuse/ActiveRecord
|
||||
@base = Issue.in_projects(projects_collection)
|
||||
end
|
||||
|
||||
def execute
|
||||
gates = [issue.project, issue.project.group].compact
|
||||
return unless gates.any? { |gate| Feature.enabled?(:rebalance_issues, gate) }
|
||||
return unless Feature.enabled?(:rebalance_issues, root_namespace)
|
||||
|
||||
raise TooManyIssues, "#{issue_count} issues" if issue_count > MAX_ISSUE_COUNT
|
||||
|
||||
|
|
@ -57,7 +56,7 @@ class IssueRebalancingService
|
|||
|
||||
private
|
||||
|
||||
attr_reader :issue, :base
|
||||
attr_reader :root_namespace, :base
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def indexed_ids
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ module Issues
|
|||
gates = [issue.project, issue.project.group].compact
|
||||
return unless gates.any? { |gate| Feature.enabled?(:rebalance_issues, gate) }
|
||||
|
||||
IssueRebalancingWorker.perform_async(nil, issue.project_id)
|
||||
IssueRebalancingWorker.perform_async(nil, *issue.project.self_or_root_group_ids)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -69,8 +69,7 @@ class WebHookService
|
|||
http_status: response.code,
|
||||
message: response.to_s
|
||||
}
|
||||
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
|
||||
Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep,
|
||||
rescue *Gitlab::HTTP::HTTP_ERRORS,
|
||||
Gitlab::Json::LimitedEncoder::LimitExceeded, URI::InvalidURIError => e
|
||||
execution_duration = Gitlab::Metrics::System.monotonic_time - start_time
|
||||
log_execution(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
.row.js-search-settings-section
|
||||
.col-lg-4.profile-settings-sidebar
|
||||
%h4.gl-mt-0
|
||||
= s_("Profiles|Public Avatar")
|
||||
= s_("Profiles|Public avatar")
|
||||
%p
|
||||
- if @user.avatar?
|
||||
- if gravatar_enabled?
|
||||
|
|
@ -27,18 +27,17 @@
|
|||
.md
|
||||
= brand_profile_image_guidelines
|
||||
.col-lg-8
|
||||
.clearfix.avatar-image.gl-mb-3
|
||||
.avatar-image
|
||||
= link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
|
||||
= image_tag avatar_icon_for_user(@user, 160), alt: '', class: 'avatar s160'
|
||||
= image_tag avatar_icon_for_user(@user, 96), alt: '', class: 'avatar s96'
|
||||
%h5.gl-mt-0= s_("Profiles|Upload new avatar")
|
||||
.gl-mt-2.gl-mb-3
|
||||
%button.gl-button.btn.js-choose-user-avatar-button{ type: 'button' }= s_("Profiles|Choose file...")
|
||||
.gl-my-3
|
||||
%button.gl-button.btn.btn-default.js-choose-user-avatar-button{ type: 'button' }= s_("Profiles|Choose file...")
|
||||
%span.avatar-file-name.gl-ml-3.js-avatar-filename= s_("Profiles|No file chosen.")
|
||||
= f.file_field_without_bootstrap :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*'
|
||||
.form-text.text-muted= s_("Profiles|The maximum file size allowed is 200KB.")
|
||||
.gl-text-gray-500= s_("Profiles|The maximum file size allowed is 200KB.")
|
||||
- if @user.avatar?
|
||||
%hr
|
||||
= link_to s_("Profiles|Remove avatar"), profile_avatar_path, data: { confirm: s_("Profiles|Avatar will be removed. Are you sure?") }, method: :delete, class: 'gl-button btn btn-danger btn-inverted'
|
||||
= link_to s_("Profiles|Remove avatar"), profile_avatar_path, data: { confirm: s_("Profiles|Avatar will be removed. Are you sure?") }, method: :delete, class: 'gl-button btn btn-danger-secondary btn-sm gl-mt-5'
|
||||
.col-lg-12
|
||||
%hr
|
||||
.row.js-search-settings-section
|
||||
|
|
@ -124,10 +123,10 @@
|
|||
= f.check_box :include_private_contributions, label: s_('Profiles|Include private contributions on my profile'), wrapper_class: 'mb-2', inline: true
|
||||
.help-block
|
||||
= s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information")
|
||||
.row.gl-mt-3.gl-mb-3.gl-justify-content-end
|
||||
.col-lg-8
|
||||
= f.submit s_("Profiles|Update profile settings"), class: 'gl-button btn btn-confirm'
|
||||
= link_to _("Cancel"), user_path(current_user), class: 'gl-button btn btn-cancel'
|
||||
.row.gl-justify-content-end.gl-mt-5
|
||||
.col-lg-8.gl-display-flex
|
||||
= f.submit s_("Profiles|Update profile settings"), class: 'gl-button btn btn-confirm gl-mr-3'
|
||||
= link_to _("Cancel"), user_path(current_user), class: 'gl-button btn btn-default btn-cancel'
|
||||
|
||||
.modal.modal-profile-crop{ data: { cropper_css_path: ActionController::Base.helpers.stylesheet_path('lazy_bundles/cropper.css') } }
|
||||
.modal-dialog
|
||||
|
|
|
|||
|
|
@ -19,9 +19,6 @@
|
|||
%li
|
||||
.monospace
|
||||
= File.basename(file)
|
||||
- if File.dirname(file).ends_with?('plugins')
|
||||
.text-warning
|
||||
= _('Plugins directory is deprecated and will be removed in 14.0. Please move this file into /file_hooks directory.')
|
||||
|
||||
- else
|
||||
.card.bg-light.text-center
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ class IssuePlacementWorker
|
|||
IssuePlacementWorker.perform_async(nil, leftover.project_id) if leftover.present?
|
||||
rescue RelativePositioning::NoSpaceLeft => e
|
||||
Gitlab::ErrorTracking.log_exception(e, issue_id: issue_id, project_id: project_id)
|
||||
IssueRebalancingWorker.perform_async(nil, project_id.presence || issue.project_id)
|
||||
IssueRebalancingWorker.perform_async(nil, *root_namespace_id_to_rebalance(issue, project_id))
|
||||
end
|
||||
|
||||
def find_issue(issue_id, project_id)
|
||||
|
|
@ -53,4 +53,11 @@ class IssuePlacementWorker
|
|||
project.issues.take
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
private
|
||||
|
||||
def root_namespace_id_to_rebalance(issue, project_id)
|
||||
project_id = project_id.presence || issue.project_id
|
||||
Project.find(project_id)&.self_or_root_group_ids
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,21 +9,44 @@ class IssueRebalancingWorker
|
|||
urgency :low
|
||||
feature_category :issue_tracking
|
||||
tags :exclude_from_kubernetes
|
||||
deduplicate :until_executed, including_scheduled: true
|
||||
|
||||
def perform(ignore = nil, project_id = nil)
|
||||
return if project_id.nil?
|
||||
def perform(ignore = nil, project_id = nil, root_namespace_id = nil)
|
||||
# we need to have exactly one of the project_id and root_namespace_id params be non-nil
|
||||
raise ArgumentError, "Expected only one of the params project_id: #{project_id} and root_namespace_id: #{root_namespace_id}" if project_id && root_namespace_id
|
||||
return if project_id.nil? && root_namespace_id.nil?
|
||||
|
||||
project = Project.find(project_id)
|
||||
# pull the projects collection to be rebalanced either the project if namespace is not a group(i.e. user namesapce)
|
||||
# or the root namespace, this also makes the worker backward compatible with previous version where a project_id was
|
||||
# passed as the param
|
||||
projects_to_rebalance = projects_collection(project_id, root_namespace_id)
|
||||
|
||||
# Temporary disable reabalancing for performance reasons
|
||||
# something might have happened with the namespace between scheduling the worker and actually running it,
|
||||
# maybe it was removed.
|
||||
if projects_to_rebalance.blank?
|
||||
Gitlab::ErrorTracking.log_exception(
|
||||
ArgumentError.new("Projects to be rebalanced not found for arguments: project_id #{project_id}, root_namespace_id: #{root_namespace_id}"),
|
||||
{ project_id: project_id, root_namespace_id: root_namespace_id })
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
# Temporary disable rebalancing for performance reasons
|
||||
# For more information check https://gitlab.com/gitlab-com/gl-infra/production/-/issues/4321
|
||||
return if project.root_namespace&.issue_repositioning_disabled?
|
||||
return if projects_to_rebalance.take&.root_namespace&.issue_repositioning_disabled? # rubocop:disable CodeReuse/ActiveRecord
|
||||
|
||||
# All issues are equivalent as far as we are concerned
|
||||
issue = project.issues.take # rubocop: disable CodeReuse/ActiveRecord
|
||||
IssueRebalancingService.new(projects_to_rebalance).execute
|
||||
rescue IssueRebalancingService::TooManyIssues => e
|
||||
Gitlab::ErrorTracking.log_exception(e, root_namespace_id: root_namespace_id, project_id: project_id)
|
||||
end
|
||||
|
||||
IssueRebalancingService.new(issue).execute
|
||||
rescue ActiveRecord::RecordNotFound, IssueRebalancingService::TooManyIssues => e
|
||||
Gitlab::ErrorTracking.log_exception(e, project_id: project_id)
|
||||
private
|
||||
|
||||
def projects_collection(project_id, root_namespace_id)
|
||||
# we can have either project_id(older version) or project_id if project is part of a user namespace and not a group
|
||||
# or root_namespace_id(newer version) never both.
|
||||
return Project.id_in([project_id]) if project_id
|
||||
|
||||
Namespace.find_by_id(root_namespace_id)&.all_projects
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
for file in config/*.yml.example; do
|
||||
cp ${file} config/$(basename ${file} .example)
|
||||
done
|
||||
|
||||
# Allow to override the GitLab URL from an environment variable, as this will avoid having to change the configuration file for simple deployments.
|
||||
config=$(echo '<% gitlab_url = URI(ENV["GITLAB_URL"] || "http://localhost:80") %>' | cat - config/gitlab.yml)
|
||||
echo "$config" > config/gitlab.yml
|
||||
sed -i "s/host: localhost/host: <%= gitlab_url.host %>/" config/gitlab.yml
|
||||
sed -i "s/port: 80/port: <%= gitlab_url.port %>/" config/gitlab.yml
|
||||
sed -i "s/https: false/https: <%= gitlab_url.scheme == 'https' %>/" config/gitlab.yml
|
||||
|
||||
# No need for config file. Will be taken care of by REDIS_URL env variable
|
||||
rm config/resque.yml
|
||||
|
||||
# Set default unicorn.rb file
|
||||
echo "" > config/unicorn.rb
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Update button variants and alignment to align with the Pajamas Design System
|
||||
and modify the avatar layout to have better flow.
|
||||
merge_request: 61504
|
||||
author:
|
||||
type: other
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Track usage of the resolve conflict UI
|
||||
merge_request: 61654
|
||||
author:
|
||||
type: other
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix pipeline graph undefined needs error
|
||||
merge_request: 62027
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Drop plugins directory support
|
||||
merge_request: 55168
|
||||
author:
|
||||
type: removed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Enable Kroki on reStructuredText and Textile documents
|
||||
merge_request: 60780
|
||||
author: Guillaume Grossetie
|
||||
type: added
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: create_vulnerability_jira_issue_via_graphql
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60593
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/329780
|
||||
milestone: '13.12'
|
||||
type: development
|
||||
group: group::protect
|
||||
default_enabled: false
|
||||
|
|
@ -63,6 +63,8 @@
|
|||
- 'i_code_review_diff_hide_whitespace'
|
||||
- 'i_code_review_diff_single_file'
|
||||
- 'i_code_review_diff_multiple_files'
|
||||
- 'i_code_review_user_load_conflict_ui'
|
||||
- 'i_code_review_user_resolve_conflict'
|
||||
- name: code_review_category_monthly_active_users
|
||||
operator: OR
|
||||
feature_flag: usage_data_code_review_aggregation
|
||||
|
|
@ -118,6 +120,8 @@
|
|||
- 'i_code_review_diff_hide_whitespace'
|
||||
- 'i_code_review_diff_single_file'
|
||||
- 'i_code_review_diff_multiple_files'
|
||||
- 'i_code_review_user_load_conflict_ui'
|
||||
- 'i_code_review_user_resolve_conflict'
|
||||
- name: code_review_extension_category_monthly_active_users
|
||||
operator: OR
|
||||
feature_flag: usage_data_code_review_aggregation
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_user_resolve_conflict_monthly
|
||||
name: resolve_conflict
|
||||
description: Count of unique users per week who attempt to resolve a conflict through the ui
|
||||
product_section:
|
||||
product_stage: create
|
||||
product_group: group::code review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: implemented
|
||||
milestone: "13.12"
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61654
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_user_load_conflict_ui_monthly
|
||||
name: load_conflict_ui
|
||||
description: Count of unique users per week who load the conflict resolution page
|
||||
product_section:
|
||||
product_stage: create
|
||||
product_group: group::code review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: implemented
|
||||
milestone: "13.12"
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61654
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_user_load_conflict_ui_weekly
|
||||
name: load_conflict_ui
|
||||
description: Count of unique users per week who load the conflict resolution page
|
||||
product_section:
|
||||
product_stage: create
|
||||
product_group: group::code review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: implemented
|
||||
milestone: "13.12"
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61654
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
key_path: redis_hll_counters.code_review.i_code_review_user_resolve_conflict_weekly
|
||||
name: resolve_conflict
|
||||
description: Count of unique users per week who attempt to resolve a conflict through the ui
|
||||
product_section:
|
||||
product_stage: create
|
||||
product_group: group::code review
|
||||
product_category: code_review
|
||||
value_type: number
|
||||
status: implemented
|
||||
milestone: "13.12"
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61654
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -11,6 +11,10 @@ Review this page for update instructions for your version. These steps
|
|||
accompany the [general steps](updating_the_geo_nodes.md#general-update-steps)
|
||||
for updating Geo nodes.
|
||||
|
||||
## Updating to GitLab 13.11
|
||||
|
||||
We found an [issue with Git clone/pull through HTTP(s)](https://gitlab.com/gitlab-org/gitlab/-/issues/330787) on Geo secondaries and on any GitLab instance if maintenance mode is enabled. This was caused by a regression in GitLab Workhorse. This is fixed in the [GitLab 13.11.4 patch release](https://about.gitlab.com/releases/2021/05/14/gitlab-13-11-4-released/). To avoid this issue, upgrade to GitLab 13.11.4 or later.
|
||||
|
||||
## Updating to GitLab 13.9
|
||||
|
||||
We've detected an issue [with a column rename](https://gitlab.com/gitlab-org/gitlab/-/issues/324160)
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
|
|||
- [Environment variables](environment_variables.md): Supported environment
|
||||
variables that can be used to override their default values to configure
|
||||
GitLab.
|
||||
- [Plugins](file_hooks.md): With custom plugins, GitLab administrators can
|
||||
- [File hooks](file_hooks.md): With custom file hooks, GitLab administrators can
|
||||
introduce custom integrations without modifying GitLab source code.
|
||||
- [Enforcing Terms of Service](../user/admin_area/settings/terms.md)
|
||||
- [Third party offers](../user/admin_area/settings/third_party_offers.md)
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Kroki diagrams **(FREE SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241744) in GitLab 13.7.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241744) in GitLab 13.7.
|
||||
> - Support for reStructuredText and Textile documents [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/324766) in GitLab 13.12.
|
||||
|
||||
When [Kroki](https://kroki.io) integration is enabled and configured in
|
||||
GitLab you can use it to create diagrams in AsciiDoc and Markdown documents.
|
||||
GitLab you can use it to create diagrams in AsciiDoc, Markdown, reStructuredText, and Textile documents.
|
||||
|
||||
## Kroki Server
|
||||
|
||||
|
|
@ -85,13 +86,29 @@ your AsciiDoc or Markdown documentation using delimited blocks:
|
|||
....
|
||||
```
|
||||
|
||||
- **reStructuredText**
|
||||
|
||||
```plaintext
|
||||
.. code-block:: plantuml
|
||||
|
||||
Bob->Alice : hello
|
||||
Alice -> Bob : hi
|
||||
```
|
||||
|
||||
- **Textile**
|
||||
|
||||
```plaintext
|
||||
bc[plantuml]. Bob->Alice : hello
|
||||
Alice -> Bob : hi
|
||||
```
|
||||
|
||||
The above blocks are converted to an HTML image tag with source pointing to the
|
||||
Kroki instance. If the Kroki server is correctly configured, this should
|
||||
render a nice diagram instead of the block:
|
||||
|
||||

|
||||
|
||||
Kroki supports more than a dozen diagram libraries. Here's a few examples:
|
||||
Kroki supports more than a dozen diagram libraries. Here's a few examples for AsciiDoc:
|
||||
|
||||
**GraphViz**
|
||||
|
||||
|
|
|
|||
|
|
@ -9346,6 +9346,30 @@ Status: `data_available`
|
|||
|
||||
Tiers: `free`, `premium`, `ultimate`
|
||||
|
||||
### `redis_hll_counters.code_review.i_code_review_user_load_conflict_ui_monthly`
|
||||
|
||||
Count of unique users per week who load the conflict resolution page
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210514013549_i_code_review_user_load_conflict_ui_monthly.yml)
|
||||
|
||||
Group: `group::code review`
|
||||
|
||||
Status: `implemented`
|
||||
|
||||
Tiers: `free`, `premium`, `ultimate`
|
||||
|
||||
### `redis_hll_counters.code_review.i_code_review_user_load_conflict_ui_weekly`
|
||||
|
||||
Count of unique users per week who load the conflict resolution page
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_7d/20210514013544_i_code_review_user_load_conflict_ui_weekly.yml)
|
||||
|
||||
Group: `group::code review`
|
||||
|
||||
Status: `implemented`
|
||||
|
||||
Tiers: `free`, `premium`, `ultimate`
|
||||
|
||||
### `redis_hll_counters.code_review.i_code_review_user_marked_as_draft_monthly`
|
||||
|
||||
Count of unique users per month who mark a merge request as a draft
|
||||
|
|
@ -9562,6 +9586,30 @@ Status: `data_available`
|
|||
|
||||
Tiers: `free`, `premium`, `ultimate`
|
||||
|
||||
### `redis_hll_counters.code_review.i_code_review_user_resolve_conflict_monthly`
|
||||
|
||||
Count of unique users per week who attempt to resolve a conflict through the ui
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210514013545_i_code_review_user_resolve_conflict_monthly.yml)
|
||||
|
||||
Group: `group::code review`
|
||||
|
||||
Status: `implemented`
|
||||
|
||||
Tiers: `free`, `premium`, `ultimate`
|
||||
|
||||
### `redis_hll_counters.code_review.i_code_review_user_resolve_conflict_weekly`
|
||||
|
||||
Count of unique users per week who attempt to resolve a conflict through the ui
|
||||
|
||||
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_7d/20210514013545_i_code_review_user_resolve_conflict_weekly.yml)
|
||||
|
||||
Group: `group::code review`
|
||||
|
||||
Status: `implemented`
|
||||
|
||||
Tiers: `free`, `premium`, `ultimate`
|
||||
|
||||
### `redis_hll_counters.code_review.i_code_review_user_resolve_thread_monthly`
|
||||
|
||||
Count of unique users per month who resolve a thread in a merge request
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ group: Monitor
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Project operations **(FREE)**
|
||||
# Monitor application performance **(FREE)**
|
||||
|
||||
GitLab provides a variety of tools to help operate and maintain
|
||||
your applications:
|
||||
your applications.
|
||||
|
||||
## Measure reliability and stability with metrics
|
||||
|
||||
|
|
|
|||
|
|
@ -351,16 +351,17 @@ You can customize the deployment namespace in a few ways:
|
|||
When you customize the namespace, existing environments remain linked to their current
|
||||
namespaces until you [clear the cluster cache](#clearing-the-cluster-cache).
|
||||
|
||||
WARNING:
|
||||
#### Protecting credentials
|
||||
|
||||
By default, anyone who can create a deployment job can access any CI/CD variable in
|
||||
an environment's deployment job. This includes `KUBECONFIG`, which gives access to
|
||||
any secret available to the associated service account in your cluster.
|
||||
To keep your production credentials safe, consider using
|
||||
[protected environments](../../../ci/environments/protected_environments.md),
|
||||
combined with either
|
||||
combined with *one* of the following:
|
||||
|
||||
- a GitLab-managed cluster and namespace per environment,
|
||||
- *or*, an environment-scoped cluster per protected environment. The same cluster
|
||||
- A GitLab-managed cluster and namespace per environment.
|
||||
- An environment-scoped cluster per protected environment. The same cluster
|
||||
can be added multiple times with multiple restricted service accounts.
|
||||
|
||||
### Integrations
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ module Banzai
|
|||
Filter::AssetProxyFilter,
|
||||
Filter::ExternalLinkFilter,
|
||||
Filter::PlantumlFilter,
|
||||
Filter::SyntaxHighlightFilter
|
||||
Filter::SyntaxHighlightFilter,
|
||||
Filter::KrokiFilter
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def self.dir_glob
|
||||
Dir.glob([Rails.root.join('file_hooks/*'), Rails.root.join('plugins/*')])
|
||||
Dir.glob(Rails.root.join('file_hooks/*'))
|
||||
end
|
||||
private_class_method :dir_glob
|
||||
|
||||
|
|
|
|||
|
|
@ -219,3 +219,11 @@
|
|||
category: code_review
|
||||
aggregation: weekly
|
||||
feature_flag: diff_settings_usage_data
|
||||
- name: i_code_review_user_load_conflict_ui
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
- name: i_code_review_user_resolve_conflict
|
||||
redis_slot: code_review
|
||||
category: code_review
|
||||
aggregation: weekly
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ module Gitlab
|
|||
MR_INCLUDING_CI_CONFIG_ACTION = 'o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile'
|
||||
MR_MILESTONE_CHANGED_ACTION = 'i_code_review_user_milestone_changed'
|
||||
MR_LABELS_CHANGED_ACTION = 'i_code_review_user_labels_changed'
|
||||
MR_LOAD_CONFLICT_UI_ACTION = 'i_code_review_user_load_conflict_ui'
|
||||
MR_RESOLVE_CONFLICT_ACTION = 'i_code_review_user_resolve_conflict'
|
||||
|
||||
class << self
|
||||
def track_mr_diffs_action(merge_request:)
|
||||
|
|
@ -201,6 +203,14 @@ module Gitlab
|
|||
track_unique_action_by_user(MR_LABELS_CHANGED_ACTION, user)
|
||||
end
|
||||
|
||||
def track_loading_conflict_ui_action(user:)
|
||||
track_unique_action_by_user(MR_LOAD_CONFLICT_UI_ACTION, user)
|
||||
end
|
||||
|
||||
def track_resolve_conflict_action(user:)
|
||||
track_unique_action_by_user(MR_RESOLVE_CONFLICT_ACTION, user)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def track_unique_action_by_merge_request(action, merge_request)
|
||||
|
|
|
|||
|
|
@ -3,14 +3,9 @@
|
|||
namespace :file_hooks do
|
||||
desc 'Validate existing file hooks'
|
||||
task validate: :environment do
|
||||
puts 'Validating file hooks from /file_hooks and /plugins directories'
|
||||
puts 'Validating file hooks from /file_hooks directories'
|
||||
|
||||
Gitlab::FileHook.files.each do |file|
|
||||
if File.dirname(file).ends_with?('plugins')
|
||||
puts 'DEPRECATED: /plugins directory is deprecated and will be removed in 14.0. ' \
|
||||
'Please move your files into /file_hooks directory.'
|
||||
end
|
||||
|
||||
success, message = Gitlab::FileHook.execute(file, Gitlab::DataBuilder::Push::SAMPLE_DATA)
|
||||
|
||||
if success
|
||||
|
|
|
|||
|
|
@ -24711,9 +24711,6 @@ msgstr ""
|
|||
msgid "Please wait while we import the repository for you. Refresh at will."
|
||||
msgstr ""
|
||||
|
||||
msgid "Plugins directory is deprecated and will be removed in 14.0. Please move this file into /file_hooks directory."
|
||||
msgstr ""
|
||||
|
||||
msgid "Pod does not exist"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -25254,7 +25251,7 @@ msgstr ""
|
|||
msgid "Profiles|Profile was successfully updated"
|
||||
msgstr ""
|
||||
|
||||
msgid "Profiles|Public Avatar"
|
||||
msgid "Profiles|Public avatar"
|
||||
msgstr ""
|
||||
|
||||
msgid "Profiles|Public email"
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
*
|
||||
!*/
|
||||
!.gitignore
|
||||
!.gitkeep
|
||||
!examples/*
|
||||
|
|
@ -17,8 +17,31 @@ RSpec.describe Projects::MergeRequests::ConflictsController do
|
|||
end
|
||||
|
||||
describe 'GET show' do
|
||||
context 'when the request is html' do
|
||||
before do
|
||||
allow(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.to receive(:track_loading_conflict_ui_action)
|
||||
|
||||
get :show,
|
||||
params: {
|
||||
namespace_id: merge_request_with_conflicts.project.namespace.to_param,
|
||||
project_id: merge_request_with_conflicts.project,
|
||||
id: merge_request_with_conflicts.iid
|
||||
},
|
||||
format: 'html'
|
||||
end
|
||||
|
||||
it 'does tracks the resolve call' do
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.to have_received(:track_loading_conflict_ui_action).with(user: user)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the conflicts cannot be resolved in the UI' do
|
||||
before do
|
||||
allow(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.to receive(:track_loading_conflict_ui_action)
|
||||
|
||||
allow(Gitlab::Git::Conflict::Parser).to receive(:parse)
|
||||
.and_raise(Gitlab::Git::Conflict::Parser::UnmergeableFile)
|
||||
|
||||
|
|
@ -38,6 +61,11 @@ RSpec.describe Projects::MergeRequests::ConflictsController do
|
|||
it 'returns JSON with a message' do
|
||||
expect(json_response.keys).to contain_exactly('message', 'type')
|
||||
end
|
||||
|
||||
it 'does not track the resolve call' do
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.not_to have_received(:track_loading_conflict_ui_action).with(user: user)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid conflicts' do
|
||||
|
|
@ -145,20 +173,19 @@ RSpec.describe Projects::MergeRequests::ConflictsController do
|
|||
conflict_for_path(path)
|
||||
end
|
||||
|
||||
it 'returns a 200 status code' do
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'returns the file in JSON format' do
|
||||
it 'returns a 200 and the file in JSON format' do
|
||||
content = MergeRequests::Conflicts::ListService.new(merge_request_with_conflicts)
|
||||
.file_for_path(path, path)
|
||||
.content
|
||||
|
||||
expect(json_response).to include('old_path' => path,
|
||||
'new_path' => path,
|
||||
'blob_icon' => 'doc-text',
|
||||
'blob_path' => a_string_ending_with(path),
|
||||
'content' => content)
|
||||
aggregate_failures do
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to include('old_path' => path,
|
||||
'new_path' => path,
|
||||
'blob_icon' => 'doc-text',
|
||||
'blob_path' => a_string_ending_with(path),
|
||||
'content' => content)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -166,6 +193,11 @@ RSpec.describe Projects::MergeRequests::ConflictsController do
|
|||
context 'POST resolve_conflicts' do
|
||||
let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha }
|
||||
|
||||
before do
|
||||
allow(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.to receive(:track_resolve_conflict_action)
|
||||
end
|
||||
|
||||
def resolve_conflicts(files)
|
||||
post :resolve_conflicts,
|
||||
params: {
|
||||
|
|
@ -201,13 +233,16 @@ RSpec.describe Projects::MergeRequests::ConflictsController do
|
|||
resolve_conflicts(resolved_files)
|
||||
end
|
||||
|
||||
it 'creates a new commit on the branch' do
|
||||
expect(original_head_sha).not_to eq(merge_request_with_conflicts.source_branch_head.sha)
|
||||
expect(merge_request_with_conflicts.source_branch_head.message).to include('Commit message')
|
||||
end
|
||||
it 'handles the success case' do
|
||||
aggregate_failures do
|
||||
# creates a new commit on the branch
|
||||
expect(original_head_sha).not_to eq(merge_request_with_conflicts.source_branch_head.sha)
|
||||
expect(merge_request_with_conflicts.source_branch_head.message).to include('Commit message')
|
||||
|
||||
it 'returns an OK response' do
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.to have_received(:track_resolve_conflict_action).with(user: user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -232,16 +267,17 @@ RSpec.describe Projects::MergeRequests::ConflictsController do
|
|||
resolve_conflicts(resolved_files)
|
||||
end
|
||||
|
||||
it 'returns a 400 error' do
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
it 'handles the error case' do
|
||||
aggregate_failures do
|
||||
# has a message with the name of the first missing section
|
||||
expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21')
|
||||
# does not create a new commit
|
||||
expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
|
||||
|
||||
it 'has a message with the name of the first missing section' do
|
||||
expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21')
|
||||
end
|
||||
|
||||
it 'does not create a new commit' do
|
||||
expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.to have_received(:track_resolve_conflict_action).with(user: user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -262,16 +298,17 @@ RSpec.describe Projects::MergeRequests::ConflictsController do
|
|||
resolve_conflicts(resolved_files)
|
||||
end
|
||||
|
||||
it 'returns a 400 error' do
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
it 'handles the error case' do
|
||||
aggregate_failures do
|
||||
# has a message with the name of the missing file
|
||||
expect(json_response['message']).to include('files/ruby/popen.rb')
|
||||
# does not create a new commit
|
||||
expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
|
||||
|
||||
it 'has a message with the name of the missing file' do
|
||||
expect(json_response['message']).to include('files/ruby/popen.rb')
|
||||
end
|
||||
|
||||
it 'does not create a new commit' do
|
||||
expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.to have_received(:track_resolve_conflict_action).with(user: user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -300,16 +337,17 @@ RSpec.describe Projects::MergeRequests::ConflictsController do
|
|||
resolve_conflicts(resolved_files)
|
||||
end
|
||||
|
||||
it 'returns a 400 error' do
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
it 'handles the error case' do
|
||||
aggregate_failures do
|
||||
# has a message with the path of the problem file
|
||||
expect(json_response['message']).to include('files/ruby/popen.rb')
|
||||
# does not create a new commit
|
||||
expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
|
||||
|
||||
it 'has a message with the path of the problem file' do
|
||||
expect(json_response['message']).to include('files/ruby/popen.rb')
|
||||
end
|
||||
|
||||
it 'does not create a new commit' do
|
||||
expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha)
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
|
||||
.to have_received(:track_resolve_conflict_action).with(user: user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -37,24 +37,6 @@ RSpec.describe 'Admin::Hooks' do
|
|||
expect(page).to have_content('foo.rb')
|
||||
expect(page).to have_content('bar.clj')
|
||||
end
|
||||
|
||||
context 'deprecation warning' do
|
||||
it 'shows warning for plugins directory' do
|
||||
allow(Gitlab::FileHook).to receive(:files).and_return(['plugins/foo.rb'])
|
||||
|
||||
visit admin_hooks_path
|
||||
|
||||
expect(page).to have_content('Plugins directory is deprecated and will be removed in 14.0')
|
||||
end
|
||||
|
||||
it 'does not show warning for file_hooks directory' do
|
||||
allow(Gitlab::FileHook).to receive(:files).and_return(['file_hooks/foo.rb'])
|
||||
|
||||
visit admin_hooks_path
|
||||
|
||||
expect(page).not_to have_content('Plugins directory is deprecated and will be removed in 14.0')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'New Hook' do
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ RSpec.describe 'User searches their settings', :js do
|
|||
visit profile_path
|
||||
end
|
||||
|
||||
it_behaves_like 'can search settings', 'Public Avatar', 'Main settings'
|
||||
it_behaves_like 'can search settings', 'Public avatar', 'Main settings'
|
||||
end
|
||||
|
||||
context 'in preferences page' do
|
||||
|
|
|
|||
|
|
@ -398,6 +398,8 @@ export const multiNote = {
|
|||
},
|
||||
};
|
||||
|
||||
export const missingJob = 'missing_job';
|
||||
|
||||
/*
|
||||
It is important that the base include parallel jobs
|
||||
as well as non-parallel jobs with spaces in the name to prevent
|
||||
|
|
@ -657,4 +659,16 @@ export const mockParsedGraphQLNodes = [
|
|||
],
|
||||
__typename: 'CiGroup',
|
||||
},
|
||||
{
|
||||
category: 'production',
|
||||
name: 'production_e',
|
||||
size: 1,
|
||||
jobs: [
|
||||
{
|
||||
name: 'production_e',
|
||||
needs: [missingJob],
|
||||
},
|
||||
],
|
||||
__typename: 'CiGroup',
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
getMaxNodes,
|
||||
} from '~/pipelines/components/parsing_utils';
|
||||
|
||||
import { mockParsedGraphQLNodes } from './components/dag/mock_data';
|
||||
import { mockParsedGraphQLNodes, missingJob } from './components/dag/mock_data';
|
||||
import { generateResponse, mockPipelineResponse } from './graph/mock_data';
|
||||
|
||||
describe('DAG visualization parsing utilities', () => {
|
||||
|
|
@ -24,6 +24,12 @@ describe('DAG visualization parsing utilities', () => {
|
|||
expect(unfilteredLinks[0]).toHaveProperty('target', 'test_a');
|
||||
expect(unfilteredLinks[0]).toHaveProperty('value', 10);
|
||||
});
|
||||
|
||||
it('does not generate a link for non-existing jobs', () => {
|
||||
const sources = unfilteredLinks.map(({ source }) => source);
|
||||
|
||||
expect(sources.includes(missingJob)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterByAncestors', () => {
|
||||
|
|
@ -88,7 +94,7 @@ describe('DAG visualization parsing utilities', () => {
|
|||
These lengths are determined by the mock data.
|
||||
If the data changes, the numbers may also change.
|
||||
*/
|
||||
expect(parsed.nodes).toHaveLength(21);
|
||||
expect(parsed.nodes).toHaveLength(mockParsedGraphQLNodes.length);
|
||||
expect(cleanedNodes).toHaveLength(12);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -111,6 +111,28 @@ describe('utils functions', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('removes needs which are not in the data', () => {
|
||||
const inexistantJobName = 'job5';
|
||||
const jobsWithNeeds = {
|
||||
[jobName1]: job1,
|
||||
[jobName2]: job2,
|
||||
[jobName3]: job3,
|
||||
[jobName4]: {
|
||||
name: jobName4,
|
||||
script: 'echo deploy',
|
||||
stage: 'deploy',
|
||||
needs: [inexistantJobName],
|
||||
},
|
||||
};
|
||||
|
||||
expect(generateJobNeedsDict(jobsWithNeeds)).toEqual({
|
||||
[jobName1]: [],
|
||||
[jobName2]: [],
|
||||
[jobName3]: [jobName1, jobName2],
|
||||
[jobName4]: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('handles parallel jobs by adding the group name as a need', () => {
|
||||
const size = 3;
|
||||
const jobOptimize1 = 'optimize_1';
|
||||
|
|
|
|||
|
|
@ -386,4 +386,20 @@ RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :cl
|
|||
let(:action) { described_class::MR_LABELS_CHANGED_ACTION }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.track_loading_conflict_ui_action' do
|
||||
subject { described_class.track_loading_conflict_ui_action(user: user) }
|
||||
|
||||
it_behaves_like 'a tracked merge request unique event' do
|
||||
let(:action) { described_class::MR_LOAD_CONFLICT_UI_ACTION }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.track_resolve_conflict_action' do
|
||||
subject { described_class.track_resolve_conflict_action(user: user) }
|
||||
|
||||
it_behaves_like 'a tracked merge request unique event' do
|
||||
let(:action) { described_class::MR_RESOLVE_CONFLICT_ACTION }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1287,15 +1287,33 @@ RSpec.describe Issue do
|
|||
end
|
||||
end
|
||||
|
||||
let(:project) { build_stubbed(:project_empty_repo) }
|
||||
let(:issue) { build_stubbed(:issue, relative_position: 100, project: project) }
|
||||
shared_examples 'schedules issues rebalancing' do
|
||||
let(:issue) { build_stubbed(:issue, relative_position: 100, project: project) }
|
||||
|
||||
it 'schedules rebalancing if we time-out when moving' do
|
||||
lhs = build_stubbed(:issue, relative_position: 99, project: project)
|
||||
to_move = build(:issue, project: project)
|
||||
expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
|
||||
it 'schedules rebalancing if we time-out when moving' do
|
||||
lhs = build_stubbed(:issue, relative_position: 99, project: project)
|
||||
to_move = build(:issue, project: project)
|
||||
expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project_id, namespace_id)
|
||||
|
||||
expect { to_move.move_between(lhs, issue) }.to raise_error(ActiveRecord::QueryCanceled)
|
||||
expect { to_move.move_between(lhs, issue) }.to raise_error(ActiveRecord::QueryCanceled)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project in user namespace' do
|
||||
let(:project) { build_stubbed(:project_empty_repo) }
|
||||
let(:project_id) { project.id }
|
||||
let(:namespace_id) { nil }
|
||||
|
||||
it_behaves_like 'schedules issues rebalancing'
|
||||
end
|
||||
|
||||
context 'when project in a group namespace' do
|
||||
let(:group) { create(:group) }
|
||||
let(:project) { build_stubbed(:project_empty_repo, group: group) }
|
||||
let(:project_id) { nil }
|
||||
let(:namespace_id) { group.id }
|
||||
|
||||
it_behaves_like 'schedules issues rebalancing'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ RSpec.describe IssueRebalancingService do
|
|||
shared_examples 'IssueRebalancingService shared examples' do
|
||||
it 'rebalances a set of issues with clumps at the end and start' do
|
||||
all_issues = start_clump + unclumped + end_clump.reverse
|
||||
service = described_class.new(project.issues.first)
|
||||
service = described_class.new(Project.id_in([project.id]))
|
||||
|
||||
expect { service.execute }.not_to change { issues_in_position_order.map(&:id) }
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ RSpec.describe IssueRebalancingService do
|
|||
end
|
||||
|
||||
it 'is idempotent' do
|
||||
service = described_class.new(project.issues.first)
|
||||
service = described_class.new(Project.id_in(project))
|
||||
|
||||
expect do
|
||||
service.execute
|
||||
|
|
@ -70,17 +70,17 @@ RSpec.describe IssueRebalancingService do
|
|||
issue.project.group
|
||||
old_pos = issue.relative_position
|
||||
|
||||
service = described_class.new(issue)
|
||||
service = described_class.new(Project.id_in(project))
|
||||
|
||||
expect { service.execute }.not_to exceed_query_limit(0)
|
||||
expect(old_pos).to eq(issue.reload.relative_position)
|
||||
end
|
||||
|
||||
it 'acts if the flag is enabled for the project' do
|
||||
it 'acts if the flag is enabled for the root namespace' do
|
||||
issue = create(:issue, project: project, author: user, relative_position: max_pos)
|
||||
stub_feature_flags(rebalance_issues: issue.project)
|
||||
stub_feature_flags(rebalance_issues: project.root_namespace)
|
||||
|
||||
service = described_class.new(issue)
|
||||
service = described_class.new(Project.id_in(project))
|
||||
|
||||
expect { service.execute }.to change { issue.reload.relative_position }
|
||||
end
|
||||
|
|
@ -90,23 +90,22 @@ RSpec.describe IssueRebalancingService do
|
|||
project.update!(group: create(:group))
|
||||
stub_feature_flags(rebalance_issues: issue.project.group)
|
||||
|
||||
service = described_class.new(issue)
|
||||
service = described_class.new(Project.id_in(project))
|
||||
|
||||
expect { service.execute }.to change { issue.reload.relative_position }
|
||||
end
|
||||
|
||||
it 'aborts if there are too many issues' do
|
||||
issue = project.issues.first
|
||||
base = double(count: 10_001)
|
||||
|
||||
allow(Issue).to receive(:relative_positioning_query_base).with(issue).and_return(base)
|
||||
allow(Issue).to receive(:in_projects).and_return(base)
|
||||
|
||||
expect { described_class.new(issue).execute }.to raise_error(described_class::TooManyIssues)
|
||||
expect { described_class.new(Project.id_in(project)).execute }.to raise_error(described_class::TooManyIssues)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'rebalancing is retried on statement timeout exceptions' do
|
||||
subject { described_class.new(project.issues.first) }
|
||||
subject { described_class.new(Project.id_in(project)) }
|
||||
|
||||
it 'retries update statement' do
|
||||
call_count = 0
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ RSpec.describe Issues::UpdateService, :mailer do
|
|||
|
||||
opts[:move_between_ids] = [issue1.id, issue2.id]
|
||||
|
||||
expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
|
||||
expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.root_namespace.id)
|
||||
|
||||
update_issue(opts)
|
||||
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
|
||||
|
|
@ -239,7 +239,7 @@ RSpec.describe Issues::UpdateService, :mailer do
|
|||
|
||||
opts[:move_between_ids] = [issue1.id, issue2.id]
|
||||
|
||||
expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
|
||||
expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.root_namespace.id)
|
||||
|
||||
update_issue(opts)
|
||||
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
|
||||
|
|
@ -253,7 +253,7 @@ RSpec.describe Issues::UpdateService, :mailer do
|
|||
|
||||
opts[:move_between_ids] = [issue1.id, issue2.id]
|
||||
|
||||
expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
|
||||
expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.root_namespace.id)
|
||||
|
||||
update_issue(opts)
|
||||
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
|
||||
|
|
|
|||
|
|
@ -128,11 +128,10 @@ RSpec.describe WebHookService do
|
|||
end
|
||||
|
||||
it 'handles exceptions' do
|
||||
exceptions = [
|
||||
SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED,
|
||||
Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout,
|
||||
Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep
|
||||
exceptions = Gitlab::HTTP::HTTP_ERRORS + [
|
||||
Gitlab::Json::LimitedEncoder::LimitExceeded, URI::InvalidURIError
|
||||
]
|
||||
|
||||
exceptions.each do |exception_class|
|
||||
exception = exception_class.new('Exception message')
|
||||
project_hook.enable!
|
||||
|
|
|
|||
|
|
@ -139,18 +139,18 @@ RSpec.describe Tooling::Danger::ProjectHelper do
|
|||
'db/post_migrate/foo' | [:database, :migration]
|
||||
'ee/db/geo/migrate/foo' | [:database, :migration]
|
||||
'ee/db/geo/post_migrate/foo' | [:database, :migration]
|
||||
'app/models/project_authorization.rb' | [:database]
|
||||
'app/services/users/refresh_authorized_projects_service.rb' | [:database]
|
||||
'app/services/authorized_project_update/find_records_due_for_refresh_service.rb' | [:database]
|
||||
'lib/gitlab/background_migration.rb' | [:database]
|
||||
'lib/gitlab/background_migration/foo' | [:database]
|
||||
'ee/lib/gitlab/background_migration/foo' | [:database]
|
||||
'lib/gitlab/database.rb' | [:database]
|
||||
'lib/gitlab/database/foo' | [:database]
|
||||
'ee/lib/gitlab/database/foo' | [:database]
|
||||
'lib/gitlab/github_import.rb' | [:database]
|
||||
'lib/gitlab/github_import/foo' | [:database]
|
||||
'lib/gitlab/sql/foo' | [:database]
|
||||
'app/models/project_authorization.rb' | [:database, :backend]
|
||||
'app/services/users/refresh_authorized_projects_service.rb' | [:database, :backend]
|
||||
'app/services/authorized_project_update/find_records_due_for_refresh_service.rb' | [:database, :backend]
|
||||
'lib/gitlab/background_migration.rb' | [:database, :backend]
|
||||
'lib/gitlab/background_migration/foo' | [:database, :backend]
|
||||
'ee/lib/gitlab/background_migration/foo' | [:database, :backend]
|
||||
'lib/gitlab/database.rb' | [:database, :backend]
|
||||
'lib/gitlab/database/foo' | [:database, :backend]
|
||||
'ee/lib/gitlab/database/foo' | [:database, :backend]
|
||||
'lib/gitlab/github_import.rb' | [:database, :backend]
|
||||
'lib/gitlab/github_import/foo' | [:database, :backend]
|
||||
'lib/gitlab/sql/foo' | [:database, :backend]
|
||||
'rubocop/cop/migration/foo' | [:database]
|
||||
|
||||
'db/fixtures/foo.rb' | [:backend]
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ RSpec.describe IssuePlacementWorker do
|
|||
it 'schedules rebalancing if needed' do
|
||||
issue_a.update!(relative_position: RelativePositioning::MAX_POSITION)
|
||||
|
||||
expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
|
||||
expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.group.id)
|
||||
|
||||
run_worker
|
||||
end
|
||||
|
|
@ -101,7 +101,7 @@ RSpec.describe IssuePlacementWorker do
|
|||
it 'anticipates the failure to place the issues, and schedules rebalancing' do
|
||||
allow(Issue).to receive(:move_nulls_to_end) { raise RelativePositioning::NoSpaceLeft }
|
||||
|
||||
expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
|
||||
expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.group.id)
|
||||
expect(Gitlab::ErrorTracking)
|
||||
.to receive(:log_exception)
|
||||
.with(RelativePositioning::NoSpaceLeft, worker_arguments)
|
||||
|
|
|
|||
|
|
@ -20,34 +20,83 @@ RSpec.describe IssueRebalancingWorker do
|
|||
end
|
||||
end
|
||||
|
||||
it 'runs an instance of IssueRebalancingService' do
|
||||
service = double(execute: nil)
|
||||
expect(IssueRebalancingService).to receive(:new).with(issue).and_return(service)
|
||||
shared_examples 'running the worker' do
|
||||
it 'runs an instance of IssueRebalancingService' do
|
||||
service = double(execute: nil)
|
||||
service_param = arguments.second.present? ? kind_of(Project.id_in([project]).class) : kind_of(group&.all_projects.class)
|
||||
|
||||
described_class.new.perform(nil, issue.project_id)
|
||||
expect(IssueRebalancingService).to receive(:new).with(service_param).and_return(service)
|
||||
|
||||
described_class.new.perform(*arguments)
|
||||
end
|
||||
|
||||
it 'anticipates there being too many issues' do
|
||||
service = double
|
||||
service_param = arguments.second.present? ? kind_of(Project.id_in([project]).class) : kind_of(group&.all_projects.class)
|
||||
|
||||
allow(service).to receive(:execute).and_raise(IssueRebalancingService::TooManyIssues)
|
||||
expect(IssueRebalancingService).to receive(:new).with(service_param).and_return(service)
|
||||
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(IssueRebalancingService::TooManyIssues, include(project_id: arguments.second, root_namespace_id: arguments.third))
|
||||
|
||||
described_class.new.perform(*arguments)
|
||||
end
|
||||
|
||||
it 'takes no action if the value is nil' do
|
||||
expect(IssueRebalancingService).not_to receive(:new)
|
||||
expect(Gitlab::ErrorTracking).not_to receive(:log_exception)
|
||||
|
||||
described_class.new.perform # all arguments are nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'anticipates the inability to find the issue' do
|
||||
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(ActiveRecord::RecordNotFound, include(project_id: -1))
|
||||
expect(IssueRebalancingService).not_to receive(:new)
|
||||
shared_examples 'safely handles non-existent ids' do
|
||||
it 'anticipates the inability to find the issue' do
|
||||
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(ArgumentError, include(project_id: arguments.second, root_namespace_id: arguments.third))
|
||||
expect(IssueRebalancingService).not_to receive(:new)
|
||||
|
||||
described_class.new.perform(nil, -1)
|
||||
described_class.new.perform(*arguments)
|
||||
end
|
||||
end
|
||||
|
||||
it 'anticipates there being too many issues' do
|
||||
service = double
|
||||
allow(service).to receive(:execute) { raise IssueRebalancingService::TooManyIssues }
|
||||
expect(IssueRebalancingService).to receive(:new).with(issue).and_return(service)
|
||||
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(IssueRebalancingService::TooManyIssues, include(project_id: issue.project_id))
|
||||
context 'without root_namespace param' do
|
||||
it_behaves_like 'running the worker' do
|
||||
let(:arguments) { [-1, project.id] }
|
||||
end
|
||||
|
||||
described_class.new.perform(nil, issue.project_id)
|
||||
it_behaves_like 'safely handles non-existent ids' do
|
||||
let(:arguments) { [nil, -1] }
|
||||
end
|
||||
|
||||
include_examples 'an idempotent worker' do
|
||||
let(:job_args) { [-1, project.id] }
|
||||
end
|
||||
|
||||
include_examples 'an idempotent worker' do
|
||||
let(:job_args) { [nil, -1] }
|
||||
end
|
||||
end
|
||||
|
||||
it 'takes no action if the value is nil' do
|
||||
expect(IssueRebalancingService).not_to receive(:new)
|
||||
expect(Gitlab::ErrorTracking).not_to receive(:log_exception)
|
||||
context 'with root_namespace param' do
|
||||
it_behaves_like 'running the worker' do
|
||||
let(:arguments) { [nil, nil, group.id] }
|
||||
end
|
||||
|
||||
described_class.new.perform(nil, nil)
|
||||
it_behaves_like 'safely handles non-existent ids' do
|
||||
let(:arguments) { [nil, nil, -1] }
|
||||
end
|
||||
|
||||
include_examples 'an idempotent worker' do
|
||||
let(:job_args) { [nil, nil, group.id] }
|
||||
end
|
||||
|
||||
include_examples 'an idempotent worker' do
|
||||
let(:job_args) { [nil, nil, -1] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'has the `until_executed` deduplicate strategy' do
|
||||
expect(described_class.get_deduplicate_strategy).to eq(:until_executed)
|
||||
expect(described_class.get_deduplication_options).to include({ including_scheduled: true })
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -76,11 +76,11 @@ module Tooling
|
|||
)\z}x => %i[frontend engineering_productivity],
|
||||
|
||||
%r{\A(ee/)?db/(geo/)?(migrate|post_migrate)/} => [:database, :migration],
|
||||
%r{\A(ee/)?db/(?!fixtures)[^/]+} => :database,
|
||||
%r{\A(ee/)?lib/gitlab/(database|background_migration|sql|github_import)(/|\.rb)} => :database,
|
||||
%r{\A(app/services/authorized_project_update/find_records_due_for_refresh_service)(/|\.rb)} => :database,
|
||||
%r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => :database,
|
||||
%r{\A(ee/)?app/finders/} => :database,
|
||||
%r{\A(ee/)?db/(?!fixtures)[^/]+} => [:database],
|
||||
%r{\A(ee/)?lib/gitlab/(database|background_migration|sql|github_import)(/|\.rb)} => [:database, :backend],
|
||||
%r{\A(app/services/authorized_project_update/find_records_due_for_refresh_service)(/|\.rb)} => [:database, :backend],
|
||||
%r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => [:database, :backend],
|
||||
%r{\A(ee/)?app/finders/} => [:database, :backend],
|
||||
%r{\Arubocop/cop/migration(/|\.rb)} => :database,
|
||||
|
||||
%r{\A(\.gitlab-ci\.yml\z|\.gitlab\/ci)} => :engineering_productivity,
|
||||
|
|
|
|||
Loading…
Reference in New Issue