Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-11-01 15:11:22 +00:00
parent 3a5bee7879
commit eac94e5cd6
47 changed files with 575 additions and 559 deletions

View File

@ -35,6 +35,11 @@ export default {
retryButtonCategory() { retryButtonCategory() {
return this.job.status && this.job.recoverable ? 'primary' : 'secondary'; return this.job.status && this.job.recoverable ? 'primary' : 'secondary';
}, },
buttonTitle() {
return this.job.status && this.job.status.text === 'passed'
? this.$options.i18n.runAgainJobButtonLabel
: this.$options.i18n.retryJobButtonLabel;
},
}, },
methods: { methods: {
...mapActions(['toggleSidebar']), ...mapActions(['toggleSidebar']),
@ -66,8 +71,8 @@ export default {
<job-sidebar-retry-button <job-sidebar-retry-button
v-if="job.retry_path" v-if="job.retry_path"
v-gl-tooltip.left v-gl-tooltip.left
:title="$options.i18n.retryJobButtonLabel" :title="buttonTitle"
:aria-label="$options.i18n.retryJobButtonLabel" :aria-label="buttonTitle"
:category="retryButtonCategory" :category="retryButtonCategory"
:href="job.retry_path" :href="job.retry_path"
:modal-id="$options.forwardDeploymentFailureModalId" :modal-id="$options.forwardDeploymentFailureModalId"

View File

@ -15,6 +15,7 @@ export const JOB_SIDEBAR_COPY = {
retry: __('Retry'), retry: __('Retry'),
retryJobButtonLabel: s__('Job|Retry'), retryJobButtonLabel: s__('Job|Retry'),
toggleSidebar: __('Toggle Sidebar'), toggleSidebar: __('Toggle Sidebar'),
runAgainJobButtonLabel: s__('Job|Run again'),
}; };
export const JOB_RETRY_FORWARD_DEPLOYMENT_MODAL = { export const JOB_RETRY_FORWARD_DEPLOYMENT_MODAL = {

View File

@ -49,17 +49,22 @@ export default {
:class="{ 'gl-border-t gl-py-3 gl-pl-7': level === 2 }" :class="{ 'gl-border-t gl-py-3 gl-pl-7': level === 2 }"
> >
<status-icon v-if="statusIconName" :level="2" :name="widgetName" :icon-name="statusIconName" /> <status-icon v-if="statusIconName" :level="2" :name="widgetName" :icon-name="statusIconName" />
<div> <div class="gl-w-full">
<slot name="header"> <div class="gl-display-flex">
<div v-if="header" class="gl-mb-2"> <slot name="header">
<strong v-safe-html="generatedHeader" class="gl-display-block"></strong <div v-if="header" class="gl-mb-2">
><span <strong v-safe-html="generatedHeader" class="gl-display-block"></strong
v-if="generatedSubheader" ><span
v-safe-html="generatedSubheader" v-if="generatedSubheader"
class="gl-display-block" v-safe-html="generatedSubheader"
></span> class="gl-display-block"
></span>
</div>
</slot>
<div v-if="$scopedSlots['header-actions']" class="gl-ml-auto">
<slot name="header-actions"></slot>
</div> </div>
</slot> </div>
<div class="gl-display-flex gl-align-items-baseline gl-w-full"> <div class="gl-display-flex gl-align-items-baseline gl-w-full">
<slot name="body"></slot> <slot name="body"></slot>
</div> </div>

View File

@ -44,7 +44,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
diff_view: diff_view, diff_view: diff_view,
merge_ref_head_diff: render_merge_ref_head_diff?, merge_ref_head_diff: render_merge_ref_head_diff?,
pagination_data: diffs.pagination_data, pagination_data: diffs.pagination_data,
allow_tree_conflicts: display_merge_conflicts_in_diff? merge_conflicts_in_diff: display_merge_conflicts_in_diff?
} }
# NOTE: Any variables that would affect the resulting json needs to be added to the cache_context to avoid stale cache issues. # NOTE: Any variables that would affect the resulting json needs to be added to the cache_context to avoid stale cache issues.
@ -57,7 +57,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
params[:page], params[:page],
params[:per_page], params[:per_page],
options[:merge_ref_head_diff], options[:merge_ref_head_diff],
options[:allow_tree_conflicts] options[:merge_conflicts_in_diff]
] ]
if Feature.enabled?(:check_etags_diffs_batch_before_write_cache, merge_request.project) && !stale?(etag: [cache_context + diff_options_hash.fetch(:paths, []), diffs]) if Feature.enabled?(:check_etags_diffs_batch_before_write_cache, merge_request.project) && !stale?(etag: [cache_context + diff_options_hash.fetch(:paths, []), diffs])
@ -81,7 +81,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
options = additional_attributes.merge( options = additional_attributes.merge(
only_context_commits: show_only_context_commits?, only_context_commits: show_only_context_commits?,
merge_ref_head_diff: render_merge_ref_head_diff?, merge_ref_head_diff: render_merge_ref_head_diff?,
allow_tree_conflicts: display_merge_conflicts_in_diff? merge_conflicts_in_diff: display_merge_conflicts_in_diff?
) )
render json: DiffsMetadataSerializer.new(project: @merge_request.project, current_user: current_user) render json: DiffsMetadataSerializer.new(project: @merge_request.project, current_user: current_user)
@ -110,7 +110,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
options = additional_attributes.merge( options = additional_attributes.merge(
diff_view: "inline", diff_view: "inline",
merge_ref_head_diff: render_merge_ref_head_diff?, merge_ref_head_diff: render_merge_ref_head_diff?,
allow_tree_conflicts: display_merge_conflicts_in_diff? merge_conflicts_in_diff: display_merge_conflicts_in_diff?
) )
options[:context_commits] = @merge_request.recent_context_commits options[:context_commits] = @merge_request.recent_context_commits

View File

@ -227,7 +227,6 @@ module DiffHelper
end end
def conflicts(allow_tree_conflicts: false) def conflicts(allow_tree_conflicts: false)
return unless options[:merge_ref_head_diff]
return unless merge_request.cannot_be_merged? return unless merge_request.cannot_be_merged?
conflicts_service = MergeRequests::Conflicts::ListService.new(merge_request, allow_tree_conflicts: allow_tree_conflicts) # rubocop:disable CodeReuse/ServiceClass conflicts_service = MergeRequests::Conflicts::ListService.new(merge_request, allow_tree_conflicts: allow_tree_conflicts) # rubocop:disable CodeReuse/ServiceClass

View File

@ -0,0 +1,62 @@
# frozen_string_literal: true
module Integrations
class BaseSlackNotification < BaseChatNotification
SUPPORTED_EVENTS_FOR_USAGE_LOG = %w[
push issue confidential_issue merge_request note confidential_note tag_push wiki_page deployment
].freeze
prop_accessor EVENT_CHANNEL['alert']
override :default_channel_placeholder
def default_channel_placeholder
_('#general, #development')
end
override :get_message
def get_message(object_kind, data)
return Integrations::ChatMessage::AlertMessage.new(data) if object_kind == 'alert'
super
end
override :supported_events
def supported_events
additional = ['alert']
super + additional
end
override :configurable_channels?
def configurable_channels?
true
end
override :log_usage
def log_usage(event, user_id)
return unless user_id
return unless SUPPORTED_EVENTS_FOR_USAGE_LOG.include?(event)
key = "i_ecosystem_slack_service_#{event}_notification"
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(key, values: user_id)
return unless Feature.enabled?(:route_hll_to_snowplow_phase2)
optional_arguments = {
project: project,
namespace: group || project&.namespace
}.compact
Gitlab::Tracking.event(
self.class.name,
Integration::SNOWPLOW_EVENT_ACTION,
label: Integration::SNOWPLOW_EVENT_LABEL,
property: key,
user: User.find(user_id),
**optional_arguments
)
end
end
end

View File

@ -1,17 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
module Integrations module Integrations
class Slack < BaseChatNotification class Slack < BaseSlackNotification
include SlackMattermostNotifier include SlackMattermostNotifier
SUPPORTED_EVENTS_FOR_USAGE_LOG = %w[
push issue confidential_issue merge_request note confidential_note
tag_push wiki_page deployment
].freeze
SNOWPLOW_EVENT_CATEGORY = self.name
prop_accessor EVENT_CHANNEL['alert']
def title def title
'Slack notifications' 'Slack notifications'
end end
@ -24,57 +16,9 @@ module Integrations
'slack' 'slack'
end end
def default_channel_placeholder override :webhook_placeholder
_('#general, #development')
end
def webhook_placeholder def webhook_placeholder
'https://hooks.slack.com/services/…' 'https://hooks.slack.com/services/…'
end end
def supported_events
additional = []
additional << 'alert'
super + additional
end
def get_message(object_kind, data)
return Integrations::ChatMessage::AlertMessage.new(data) if object_kind == 'alert'
super
end
override :log_usage
def log_usage(event, user_id)
return unless user_id
return unless SUPPORTED_EVENTS_FOR_USAGE_LOG.include?(event)
key = "i_ecosystem_slack_service_#{event}_notification"
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(key, values: user_id)
return unless Feature.enabled?(:route_hll_to_snowplow_phase2)
optional_arguments = {
project: project,
namespace: group || project&.namespace
}.compact
Gitlab::Tracking.event(
SNOWPLOW_EVENT_CATEGORY,
Integration::SNOWPLOW_EVENT_ACTION,
label: Integration::SNOWPLOW_EVENT_LABEL,
property: key,
user: User.find(user_id),
**optional_arguments
)
end
override :configurable_channels?
def configurable_channels?
true
end
end end
end end

View File

@ -1134,7 +1134,7 @@ class MergeRequest < ApplicationRecord
# rubocop: enable CodeReuse/ServiceClass # rubocop: enable CodeReuse/ServiceClass
def diffable_merge_ref? def diffable_merge_ref?
open? && merge_head_diff.present? && (Feature.enabled?(:display_merge_conflicts_in_diff, project) || can_be_merged?) open? && merge_head_diff.present? && can_be_merged?
end end
# Returns boolean indicating the merge_status should be rechecked in order to # Returns boolean indicating the merge_status should be rechecked in order to

View File

@ -55,17 +55,9 @@ class DiffFileEntity < DiffFileBaseEntity
end end
# Used for inline diffs # Used for inline diffs
expose :highlighted_diff_lines, using: DiffLineEntity, if: -> (diff_file, options) { inline_diff_view?(options) && diff_file.text? } do |diff_file| expose :diff_lines_for_serializer, as: :highlighted_diff_lines, using: DiffLineEntity, if: -> (diff_file, options) { inline_diff_view?(options) && diff_file.text? }
highlighted_diff_lines_for(diff_file, options)
end
expose :is_fully_expanded do |diff_file| expose :fully_expanded?, as: :is_fully_expanded
if conflict_file(options, diff_file)
false
else
diff_file.fully_expanded?
end
end
# Used for parallel diffs # Used for parallel diffs
expose :parallel_diff_lines, using: DiffLineParallelEntity, if: -> (diff_file, options) { parallel_diff_view?(options) && diff_file.text? } expose :parallel_diff_lines, using: DiffLineParallelEntity, if: -> (diff_file, options) { parallel_diff_view?(options) && diff_file.text? }
@ -88,15 +80,6 @@ class DiffFileEntity < DiffFileBaseEntity
# If nothing is present, inline will be the default. # If nothing is present, inline will be the default.
options.fetch(:diff_view, :inline).to_sym options.fetch(:diff_view, :inline).to_sym
end end
def highlighted_diff_lines_for(diff_file, options)
file = conflict_file(options, diff_file) || diff_file
file.diff_lines_for_serializer
rescue Gitlab::Git::Conflict::Parser::UnmergeableFile
# Fallback to diff_file as it means that conflict lines can't be parsed due to limit
diff_file.diff_lines_for_serializer
end
end end
DiffFileEntity.prepend_mod DiffFileEntity.prepend_mod

View File

@ -74,7 +74,7 @@ class DiffsEntity < Grape::Entity
options.merge( options.merge(
submodule_links: submodule_links, submodule_links: submodule_links,
code_navigation_path: code_navigation_path(diffs), code_navigation_path: code_navigation_path(diffs),
conflicts: conflicts(allow_tree_conflicts: options[:allow_tree_conflicts]) conflicts: (conflicts(allow_tree_conflicts: true) if options[:merge_conflicts_in_diff])
) )
) )
end end

View File

@ -6,7 +6,7 @@ class DiffsMetadataEntity < DiffsEntity
DiffFileMetadataEntity.represent( DiffFileMetadataEntity.represent(
diffs.raw_diff_files(sorted: true), diffs.raw_diff_files(sorted: true),
options.merge( options.merge(
conflicts: conflicts(allow_tree_conflicts: options[:allow_tree_conflicts]) conflicts: (conflicts(allow_tree_conflicts: true) if options[:merge_conflicts_in_diff])
) )
) )
end end

View File

@ -17,7 +17,7 @@ class PaginatedDiffEntity < Grape::Entity
options.merge( options.merge(
submodule_links: submodule_links, submodule_links: submodule_links,
code_navigation_path: code_navigation_path(diffs), code_navigation_path: code_navigation_path(diffs),
conflicts: conflicts(allow_tree_conflicts: options[:allow_tree_conflicts]) conflicts: (conflicts(allow_tree_conflicts: true) if options[:merge_conflicts_in_diff])
) )
) )
end end

View File

@ -156,8 +156,7 @@ module MergeRequests
end end
def merge_to_ref def merge_to_ref
params = { allow_conflicts: Feature.enabled?(:display_merge_conflicts_in_diff, project) } result = MergeRequests::MergeToRefService.new(project: project, current_user: merge_request.author, params: {}).execute(merge_request)
result = MergeRequests::MergeToRefService.new(project: project, current_user: merge_request.author, params: params).execute(merge_request)
result[:status] == :success result[:status] == :success
end end

View File

@ -8,19 +8,15 @@
- grouped_emojis = awardable.grouped_awards(with_thumbs: inline) - grouped_emojis = awardable.grouped_awards(with_thumbs: inline)
.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } } .awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } }
- awards_sort(grouped_emojis).each do |emoji, awards| - awards_sort(grouped_emojis).each do |emoji, awards|
%button.gl-button.btn.btn-default.award-control.js-emoji-btn.has-tooltip{ type: "button", = render Pajamas::ButtonComponent.new(button_options: { class: "#{award_state_class(awardable, awards, current_user)} award-control js-emoji-btn", title: award_user_list(awards, current_user), data: { toggle: 'tooltip', container: 'body' } }) do
class: [award_state_class(awardable, awards, current_user)],
data: { title: award_user_list(awards, current_user) } }
= emoji_icon(emoji) = emoji_icon(emoji)
%span.award-control-text.js-counter %span.award-control-text.js-counter
= awards.count = awards.count
- if can?(current_user, :award_emoji, awardable) - if can?(current_user, :award_emoji, awardable)
.award-menu-holder.js-award-holder .award-menu-holder.js-award-holder
%button.gl-button.btn.btn-default.award-control.has-tooltip.js-add-award{ type: 'button', = render Pajamas::ButtonComponent.new(button_text_classes: 'gl-display-flex', button_options: { title: _('Add reaction'), class: 'js-add-award award-control has-tooltip', 'aria-label': _('Add reaction') }) do
'aria-label': _('Add reaction'), = sprite_icon('slight-smile', css_class: 'award-control-icon-neutral gl-icon gl-mr-0!')
data: { title: _('Add reaction') } } = sprite_icon('smiley', css_class: 'award-control-icon-positive gl-icon')
%span{ class: "award-control-icon award-control-icon-neutral gl-icon" }= sprite_icon('slight-smile') = sprite_icon('smile', css_class: 'award-control-icon-super-positive gl-icon')
%span{ class: "award-control-icon award-control-icon-positive gl-icon" }= sprite_icon('smiley')
%span{ class: "award-control-icon award-control-icon-super-positive gl-icon" }= sprite_icon('smile')
= yield = yield

View File

@ -10,45 +10,32 @@
.import-buttons .import-buttons
- if gitlab_project_import_enabled? - if gitlab_project_import_enabled?
.import_gitlab_project.has-tooltip{ data: { container: 'body', qa_selector: 'gitlab_import_button' } } .import_gitlab_project.has-tooltip{ data: { container: 'body', qa_selector: 'gitlab_import_button' } }
= link_to '#', class: 'gl-button btn-default btn btn_import_gitlab_project js-import-project-btn', data: { href: new_import_gitlab_project_path, platform: 'gitlab_export', **tracking_attrs_data(track_label, 'click_button', 'gitlab_export') } do = render Pajamas::ButtonComponent.new(href: '#', icon: 'tanuki', button_options: { class: 'btn_import_gitlab_project js-import-project-btn', data: { href: new_import_gitlab_project_path, platform: 'gitlab_export', **tracking_attrs_data(track_label, 'click_button', 'gitlab_export') } }) do
.gl-button-icon = _('GitLab export')
= sprite_icon('tanuki')
= _("GitLab export") - if gitlab_import_enabled?
%div
= render Pajamas::ButtonComponent.new(href: status_import_gitlab_path(namespace_id: namespace_id), icon: 'tanuki', button_options: { class: "import_gitlab js-import-project-btn #{'js-how-to-import-link' unless gitlab_import_configured?}", data: { modal_title: _("Import projects from GitLab.com"), modal_message: import_from_gitlab_message, platform: 'gitlab_com', **tracking_attrs_data(track_label, 'click_button', 'gitlab_com') } }) do
GitLab.com
- if github_import_enabled? - if github_import_enabled?
%div %div
= link_to new_import_github_path(namespace_id: namespace_id), class: 'gl-button btn-default btn js-import-github js-import-project-btn', data: { platform: 'github', **tracking_attrs_data(track_label, 'click_button', 'github') } do = render Pajamas::ButtonComponent.new(href: new_import_github_path(namespace_id: namespace_id), icon: 'github', button_options: { class: 'js-import-github js-import-project-btn', data: { platform: 'github', **tracking_attrs_data(track_label, 'click_button', 'github') } }) do
.gl-button-icon
= sprite_icon('github')
GitHub GitHub
- if bitbucket_import_enabled? - if bitbucket_import_enabled?
%div %div
= link_to status_import_bitbucket_path(namespace_id: namespace_id), class: "gl-button btn-default btn import_bitbucket js-import-project-btn #{'js-how-to-import-link' unless bitbucket_import_configured?}", = render Pajamas::ButtonComponent.new(href: status_import_bitbucket_path(namespace_id: namespace_id), icon: 'bitbucket', button_options: { class: "import_bitbucket js-import-project-btn #{'js-how-to-import-link' unless bitbucket_import_configured?}", data: { modal_title: _("Import projects from Bitbucket"), modal_message: import_from_bitbucket_message, platform: 'bitbucket_cloud', **tracking_attrs_data(track_label, 'click_button', 'bitbucket_cloud') } }) do
data: { modal_title: _("Import projects from Bitbucket"), modal_message: import_from_bitbucket_message, platform: 'bitbucket_cloud', **tracking_attrs_data(track_label, 'click_button', 'bitbucket_cloud') } do
.gl-button-icon
= sprite_icon('bitbucket')
Bitbucket Cloud Bitbucket Cloud
- if bitbucket_server_import_enabled? - if bitbucket_server_import_enabled?
%div %div
= link_to status_import_bitbucket_server_path(namespace_id: namespace_id), class: "gl-button btn-default btn import_bitbucket js-import-project-btn", data: { platform: 'bitbucket_server', **tracking_attrs_data(track_label, 'click_button', 'bitbucket_server') } do = render Pajamas::ButtonComponent.new(href: status_import_bitbucket_server_path(namespace_id: namespace_id), icon: 'bitbucket', button_options: { class: 'import_bitbucket js-import-project-btn', data: { platform: 'bitbucket_server', **tracking_attrs_data(track_label, 'click_button', 'bitbucket_server') } }) do
.gl-button-icon
= sprite_icon('bitbucket')
Bitbucket Server Bitbucket Server
%div
- if gitlab_import_enabled?
%div
= link_to status_import_gitlab_path(namespace_id: namespace_id), class: "gl-button btn-default btn import_gitlab js-import-project-btn #{'js-how-to-import-link' unless gitlab_import_configured?}",
data: { modal_title: _("Import projects from GitLab.com"), modal_message: import_from_gitlab_message, platform: 'gitlab_com', **tracking_attrs_data(track_label, 'click_button', 'gitlab_com') } do
.gl-button-icon
= sprite_icon('tanuki')
= _("GitLab.com")
- if fogbugz_import_enabled? - if fogbugz_import_enabled?
%div %div
= link_to new_import_fogbugz_path(namespace_id: namespace_id), class: 'gl-button btn-default btn import_fogbugz js-import-project-btn', data: { platform: 'fogbugz', **tracking_attrs_data(track_label, 'click_button', 'fogbugz') } do = render Pajamas::ButtonComponent.new(href: new_import_fogbugz_path(namespace_id: namespace_id), icon: 'bug', button_options: { class: 'import_fogbugz js-import-project-btn', data: { platform: 'fogbugz', **tracking_attrs_data(track_label, 'click_button', 'fogbugz') } }) do
.gl-button-icon
= sprite_icon('bug')
FogBugz FogBugz
- if gitea_import_enabled? - if gitea_import_enabled?
@ -60,24 +47,18 @@
- if git_import_enabled? - if git_import_enabled?
%div %div
%button.gl-button.btn-default.btn.btn-svg.js-toggle-button.js-import-git-toggle-button.js-import-project-btn{ type: "button", data: { platform: 'repo_url', toggle_open_class: 'active', **tracking_attrs_data(track_label, 'click_button', 'repo_url') } } = render Pajamas::ButtonComponent.new(icon: 'link', button_options: { class: 'js-toggle-button js-import-git-toggle-button js-import-project-btn', data: { platform: 'repo_url', toggle_open_class: 'active', **tracking_attrs_data(track_label, 'click_button', 'repo_url') } }) do
.gl-button-icon
= sprite_icon('link', css_class: 'gl-icon')
= _('Repository by URL') = _('Repository by URL')
- if manifest_import_enabled? - if manifest_import_enabled?
%div %div
= link_to new_import_manifest_path(namespace_id: namespace_id), class: 'gl-button btn-default btn import_manifest js-import-project-btn', data: { platform: 'manifest_file', **tracking_attrs_data(track_label, 'click_button', 'manifest_file') } do = render Pajamas::ButtonComponent.new(href: new_import_manifest_path(namespace_id: namespace_id), icon: 'doc-text', button_options: { class: 'import_manifest js-import-project-btn', data: { platform: 'manifest_file', **tracking_attrs_data(track_label, 'click_button', 'manifest_file') } }) do
.gl-button-icon = _('Manifest file')
= sprite_icon('doc-text')
Manifest file
- if phabricator_import_enabled? - if phabricator_import_enabled?
%div %div
= link_to new_import_phabricator_path(namespace_id: namespace_id), class: 'gl-button btn-default btn import_phabricator js-import-project-btn', data: { platform: 'phabricator', track_label: "#{track_label}", track_action: "click_button", track_property: "phabricator" } do = render Pajamas::ButtonComponent.new(href: new_import_phabricator_path(namespace_id: namespace_id), icon: 'issues', button_options: { class: 'import_phabricator js-import-project-btn', data: { platform: 'phabricator', track_label: "#{track_label}", track_action: "click_button", track_property: "phabricator" } }) do
.gl-button-icon = _('Phabricator tasks')
= custom_icon('issues')
= _("Phabricator Tasks")
.js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') } .js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') }

View File

@ -4,7 +4,7 @@ module Namespaces
class RootStatisticsWorker class RootStatisticsWorker
include ApplicationWorker include ApplicationWorker
data_consistency :always data_consistency :sticky, feature_flag: :root_statistics_worker_read_replica
sidekiq_options retry: 3 sidekiq_options retry: 3

View File

@ -100,7 +100,6 @@
- planning_analytics - planning_analytics
- pods - pods
- portfolio_management - portfolio_management
- privacy_control_center
- product_analytics - product_analytics
- projects - projects
- pubsec_services - pubsec_services

View File

@ -0,0 +1,8 @@
---
name: root_statistics_worker_read_replica
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102516
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/379678
milestone: '15.6'
type: development
group: group::utilization
default_enabled: false

View File

@ -11434,6 +11434,7 @@ CREATE TABLE application_settings (
inactive_projects_min_size_mb integer DEFAULT 0 NOT NULL, inactive_projects_min_size_mb integer DEFAULT 0 NOT NULL,
inactive_projects_send_warning_email_after_months integer DEFAULT 1 NOT NULL, inactive_projects_send_warning_email_after_months integer DEFAULT 1 NOT NULL,
delayed_group_deletion boolean DEFAULT true NOT NULL, delayed_group_deletion boolean DEFAULT true NOT NULL,
maven_package_requests_forwarding boolean DEFAULT true NOT NULL,
arkose_labs_namespace text DEFAULT 'client'::text NOT NULL, arkose_labs_namespace text DEFAULT 'client'::text NOT NULL,
max_export_size integer DEFAULT 0, max_export_size integer DEFAULT 0,
encrypted_slack_app_signing_secret bytea, encrypted_slack_app_signing_secret bytea,
@ -11472,18 +11473,17 @@ CREATE TABLE application_settings (
cube_api_base_url text, cube_api_base_url text,
encrypted_cube_api_key bytea, encrypted_cube_api_key bytea,
encrypted_cube_api_key_iv bytea, encrypted_cube_api_key_iv bytea,
maven_package_requests_forwarding boolean DEFAULT true NOT NULL,
dashboard_limit_enabled boolean DEFAULT false NOT NULL,
dashboard_limit integer DEFAULT 0 NOT NULL,
dashboard_notification_limit integer DEFAULT 0 NOT NULL,
dashboard_enforcement_limit integer DEFAULT 0 NOT NULL,
dashboard_limit_new_namespace_creation_enforcement_date date,
jitsu_host text, jitsu_host text,
jitsu_project_xid text, jitsu_project_xid text,
clickhouse_connection_string text, clickhouse_connection_string text,
jitsu_administrator_email text, jitsu_administrator_email text,
encrypted_jitsu_administrator_password bytea, encrypted_jitsu_administrator_password bytea,
encrypted_jitsu_administrator_password_iv bytea, encrypted_jitsu_administrator_password_iv bytea,
dashboard_limit_enabled boolean DEFAULT false NOT NULL,
dashboard_limit integer DEFAULT 0 NOT NULL,
dashboard_notification_limit integer DEFAULT 0 NOT NULL,
dashboard_enforcement_limit integer DEFAULT 0 NOT NULL,
dashboard_limit_new_namespace_creation_enforcement_date date,
can_create_group boolean DEFAULT true NOT NULL, can_create_group boolean DEFAULT true NOT NULL,
lock_maven_package_requests_forwarding boolean DEFAULT false NOT NULL, lock_maven_package_requests_forwarding boolean DEFAULT false NOT NULL,
lock_pypi_package_requests_forwarding boolean DEFAULT false NOT NULL, lock_pypi_package_requests_forwarding boolean DEFAULT false NOT NULL,
@ -20192,8 +20192,8 @@ CREATE TABLE project_settings (
selective_code_owner_removals boolean DEFAULT false NOT NULL, selective_code_owner_removals boolean DEFAULT false NOT NULL,
issue_branch_template text, issue_branch_template text,
show_diff_preview_in_email boolean DEFAULT true NOT NULL, show_diff_preview_in_email boolean DEFAULT true NOT NULL,
suggested_reviewers_enabled boolean DEFAULT false NOT NULL,
jitsu_key text, jitsu_key text,
suggested_reviewers_enabled boolean DEFAULT false NOT NULL,
only_allow_merge_if_all_status_checks_passed boolean DEFAULT false NOT NULL, only_allow_merge_if_all_status_checks_passed boolean DEFAULT false NOT NULL,
mirror_branch_regex text, mirror_branch_regex text,
CONSTRAINT check_2981f15877 CHECK ((char_length(jitsu_key) <= 100)), CONSTRAINT check_2981f15877 CHECK ((char_length(jitsu_key) <= 100)),
@ -28246,6 +28246,10 @@ CREATE UNIQUE INDEX p_ci_builds_metadata_build_id_partition_id_idx ON ONLY p_ci_
CREATE UNIQUE INDEX index_ci_builds_metadata_on_build_id_partition_id_unique ON ci_builds_metadata USING btree (build_id, partition_id); CREATE UNIQUE INDEX index_ci_builds_metadata_on_build_id_partition_id_unique ON ci_builds_metadata USING btree (build_id, partition_id);
CREATE UNIQUE INDEX p_ci_builds_metadata_id_partition_id_idx ON ONLY p_ci_builds_metadata USING btree (id, partition_id);
CREATE UNIQUE INDEX index_ci_builds_metadata_on_id_partition_id_unique ON ci_builds_metadata USING btree (id, partition_id);
CREATE INDEX p_ci_builds_metadata_project_id_idx ON ONLY p_ci_builds_metadata USING btree (project_id); CREATE INDEX p_ci_builds_metadata_project_id_idx ON ONLY p_ci_builds_metadata USING btree (project_id);
CREATE INDEX index_ci_builds_metadata_on_project_id ON ci_builds_metadata USING btree (project_id); CREATE INDEX index_ci_builds_metadata_on_project_id ON ci_builds_metadata USING btree (project_id);
@ -32484,6 +32488,8 @@ ALTER INDEX p_ci_builds_metadata_build_id_id_idx ATTACH PARTITION index_ci_build
ALTER INDEX p_ci_builds_metadata_build_id_partition_id_idx ATTACH PARTITION index_ci_builds_metadata_on_build_id_partition_id_unique; ALTER INDEX p_ci_builds_metadata_build_id_partition_id_idx ATTACH PARTITION index_ci_builds_metadata_on_build_id_partition_id_unique;
ALTER INDEX p_ci_builds_metadata_id_partition_id_idx ATTACH PARTITION index_ci_builds_metadata_on_id_partition_id_unique;
ALTER INDEX p_ci_builds_metadata_project_id_idx ATTACH PARTITION index_ci_builds_metadata_on_project_id; ALTER INDEX p_ci_builds_metadata_project_id_idx ATTACH PARTITION index_ci_builds_metadata_on_project_id;
CREATE TRIGGER chat_names_loose_fk_trigger AFTER DELETE ON chat_names REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records(); CREATE TRIGGER chat_names_loose_fk_trigger AFTER DELETE ON chat_names REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();

View File

@ -2835,6 +2835,13 @@ deploystacks: [vultr, data]
deploystacks: [vultr, processing] deploystacks: [vultr, processing]
``` ```
**Additional details**:
- `parallel:matrix` jobs add the variable values to the job names to differentiate
the jobs from each other, but [large values can cause names to exceed limits](https://gitlab.com/gitlab-org/gitlab/-/issues/362262):
- Job names must be [255 characters or fewer](../jobs/index.md#job-name-limitations).
- When using [`needs`](#needs), job names must be 128 characters or fewer.
**Related topics**: **Related topics**:
- [Run a one-dimensional matrix of parallel jobs](../jobs/job_control.md#run-a-one-dimensional-matrix-of-parallel-jobs). - [Run a one-dimensional matrix of parallel jobs](../jobs/job_control.md#run-a-one-dimensional-matrix-of-parallel-jobs).

View File

@ -18236,9 +18236,6 @@ msgstr ""
msgid "GitLab will create a branch in your fork and start a merge request." msgid "GitLab will create a branch in your fork and start a merge request."
msgstr "" msgstr ""
msgid "GitLab.com"
msgstr ""
msgid "GitLab.com (SaaS)" msgid "GitLab.com (SaaS)"
msgstr "" msgstr ""
@ -23390,6 +23387,9 @@ msgstr ""
msgid "Job|Retry" msgid "Job|Retry"
msgstr "" msgstr ""
msgid "Job|Run again"
msgstr ""
msgid "Job|Running" msgid "Job|Running"
msgstr "" msgstr ""
@ -24744,6 +24744,9 @@ msgstr ""
msgid "Manifest" msgid "Manifest"
msgstr "" msgstr ""
msgid "Manifest file"
msgstr ""
msgid "Manifest file import" msgid "Manifest file import"
msgstr "" msgstr ""
@ -29450,7 +29453,7 @@ msgstr ""
msgid "Phabricator Server URL" msgid "Phabricator Server URL"
msgstr "" msgstr ""
msgid "Phabricator Tasks" msgid "Phabricator tasks"
msgstr "" msgstr ""
msgid "Phone" msgid "Phone"
@ -47661,12 +47664,27 @@ msgstr ""
msgid "ciReport|Dependency scanning" msgid "ciReport|Dependency scanning"
msgstr "" msgstr ""
msgid "ciReport|Detects known vulnerabilities in your source code's dependencies."
msgstr ""
msgid "ciReport|Detects known vulnerabilities in your source code."
msgstr ""
msgid "ciReport|Detects known vulnerabilities in your web application."
msgstr ""
msgid "ciReport|Detects secrets and credentials vulnerabilities in your source code."
msgstr ""
msgid "ciReport|Download patch to resolve" msgid "ciReport|Download patch to resolve"
msgstr "" msgstr ""
msgid "ciReport|Download the patch to apply it manually" msgid "ciReport|Download the patch to apply it manually"
msgstr "" msgstr ""
msgid "ciReport|Dynamic Application Security Testing (DAST)"
msgstr ""
msgid "ciReport|Dynamic Application Security Testing (DAST) detects known vulnerabilities in your web application." msgid "ciReport|Dynamic Application Security Testing (DAST) detects known vulnerabilities in your web application."
msgstr "" msgstr ""
@ -47786,6 +47804,9 @@ msgstr ""
msgid "ciReport|Solution" msgid "ciReport|Solution"
msgstr "" msgstr ""
msgid "ciReport|Static Application Security Testing (SAST)"
msgstr ""
msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code." msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code."
msgstr "" msgstr ""

View File

@ -213,7 +213,7 @@ RSpec.describe Projects::MergeRequests::DiffsController do
commit: nil, commit: nil,
latest_diff: true, latest_diff: true,
only_context_commits: false, only_context_commits: false,
allow_tree_conflicts: true, merge_conflicts_in_diff: true,
merge_ref_head_diff: false merge_ref_head_diff: false
} }
end end
@ -281,7 +281,7 @@ RSpec.describe Projects::MergeRequests::DiffsController do
commit: nil, commit: nil,
latest_diff: true, latest_diff: true,
only_context_commits: false, only_context_commits: false,
allow_tree_conflicts: true, merge_conflicts_in_diff: true,
merge_ref_head_diff: nil merge_ref_head_diff: nil
} }
end end
@ -303,7 +303,7 @@ RSpec.describe Projects::MergeRequests::DiffsController do
commit: merge_request.diff_head_commit, commit: merge_request.diff_head_commit,
latest_diff: nil, latest_diff: nil,
only_context_commits: false, only_context_commits: false,
allow_tree_conflicts: true, merge_conflicts_in_diff: true,
merge_ref_head_diff: nil merge_ref_head_diff: nil
} }
end end
@ -329,7 +329,7 @@ RSpec.describe Projects::MergeRequests::DiffsController do
commit: nil, commit: nil,
latest_diff: true, latest_diff: true,
only_context_commits: false, only_context_commits: false,
allow_tree_conflicts: false, merge_conflicts_in_diff: false,
merge_ref_head_diff: nil merge_ref_head_diff: nil
} }
end end
@ -488,7 +488,7 @@ RSpec.describe Projects::MergeRequests::DiffsController do
commit: nil, commit: nil,
diff_view: :inline, diff_view: :inline,
merge_ref_head_diff: nil, merge_ref_head_diff: nil,
allow_tree_conflicts: true, merge_conflicts_in_diff: true,
pagination_data: { pagination_data: {
total_pages: nil total_pages: nil
}.merge(pagination_data) }.merge(pagination_data)
@ -616,7 +616,7 @@ RSpec.describe Projects::MergeRequests::DiffsController do
it_behaves_like 'serializes diffs with expected arguments' do it_behaves_like 'serializes diffs with expected arguments' do
let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiffBatch } let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiffBatch }
let(:expected_options) { collection_arguments(total_pages: 20).merge(allow_tree_conflicts: false) } let(:expected_options) { collection_arguments(total_pages: 20).merge(merge_conflicts_in_diff: false) }
end end
it_behaves_like 'successful request' it_behaves_like 'successful request'

View File

@ -726,12 +726,7 @@ RSpec.describe 'Pipeline', :js do
before do before do
schedule.owner.block! schedule.owner.block!
PipelineScheduleWorker.new.perform
begin
PipelineScheduleWorker.new.perform
rescue Ci::CreatePipelineService::CreateError
# Do nothing, assert view code after the Pipeline failed to create.
end
end end
it 'displays the PipelineSchedule in an inactive state' do it 'displays the PipelineSchedule in an inactive state' do

View File

@ -851,12 +851,7 @@ RSpec.describe 'Pipeline', :js do
before do before do
schedule.owner.block! schedule.owner.block!
PipelineScheduleWorker.new.perform
begin
PipelineScheduleWorker.new.perform
rescue Ci::CreatePipelineService::CreateError
# Do nothing, assert view code after the Pipeline failed to create.
end
end end
it 'displays the PipelineSchedule in an inactive state' do it 'displays the PipelineSchedule in an inactive state' do

View File

@ -3,7 +3,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import JobRetryButton from '~/jobs/components/job/sidebar/job_sidebar_retry_button.vue'; import JobRetryButton from '~/jobs/components/job/sidebar/job_sidebar_retry_button.vue';
import LegacySidebarHeader from '~/jobs/components/job/sidebar/legacy_sidebar_header.vue'; import LegacySidebarHeader from '~/jobs/components/job/sidebar/legacy_sidebar_header.vue';
import createStore from '~/jobs/store'; import createStore from '~/jobs/store';
import job from '../../mock_data'; import job, { failedJobStatus } from '../../mock_data';
describe('Legacy Sidebar Header', () => { describe('Legacy Sidebar Header', () => {
let store; let store;
@ -67,6 +67,12 @@ describe('Legacy Sidebar Header', () => {
it('should render the retry button', () => { it('should render the retry button', () => {
expect(findRetryButton().props('href')).toBe(job.retry_path); expect(findRetryButton().props('href')).toBe(job.retry_path);
}); });
it('should have a different label when the job status is passed', () => {
expect(findRetryButton().attributes('title')).toBe(
LegacySidebarHeader.i18n.runAgainJobButtonLabel,
);
});
}); });
describe('when there is no retry path', () => { describe('when there is no retry path', () => {
@ -88,4 +94,16 @@ describe('Legacy Sidebar Header', () => {
expect(findCancelButton().attributes('href')).toBe(job.cancel_path); expect(findCancelButton().attributes('href')).toBe(job.cancel_path);
}); });
}); });
describe('when the job is failed', () => {
describe('retry button', () => {
it('should have a different label when the job status is failed', () => {
createWrapper({ job: { ...job, status: failedJobStatus } });
expect(findRetryButton().attributes('title')).toBe(
LegacySidebarHeader.i18n.retryJobButtonLabel,
);
});
});
});
}); });

View File

@ -1086,6 +1086,29 @@ export default {
has_trace: true, has_trace: true,
}; };
export const failedJobStatus = {
icon: 'status_warning',
text: 'failed',
label: 'failed (allowed to fail)',
group: 'failed-with-warnings',
tooltip: 'failed - (unknown failure) (allowed to fail)',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/454',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/454/retry',
method: 'post',
},
};
export const jobsInStage = { export const jobsInStage = {
name: 'build', name: 'build',
title: 'build: running', title: 'build: running',

View File

@ -36,12 +36,14 @@ describe('~/vue_merge_request_widget/components/widget/widget_content_row.vue',
}, },
slots: { slots: {
header: '<span>this is a header</span>', header: '<span>this is a header</span>',
'header-actions': '<span>this is a header action</span>',
body: '<span>this is a body</span>', body: '<span>this is a body</span>',
}, },
}); });
expect(wrapper.findByText('this is a body').exists()).toBe(true); expect(wrapper.findByText('this is a body').exists()).toBe(true);
expect(wrapper.findByText('this is a header').exists()).toBe(true); expect(wrapper.findByText('this is a header').exists()).toBe(true);
expect(wrapper.findByText('this is a header action').exists()).toBe(true);
}); });
}); });

View File

@ -471,7 +471,6 @@ RSpec.describe DiffHelper do
describe '#conflicts' do describe '#conflicts' do
let(:merge_request) { instance_double(MergeRequest, cannot_be_merged?: true) } let(:merge_request) { instance_double(MergeRequest, cannot_be_merged?: true) }
let(:merge_ref_head_diff) { true }
let(:can_be_resolved_in_ui?) { true } let(:can_be_resolved_in_ui?) { true }
let(:allow_tree_conflicts) { false } let(:allow_tree_conflicts) { false }
let(:files) { [instance_double(Gitlab::Conflict::File, path: 'a')] } let(:files) { [instance_double(Gitlab::Conflict::File, path: 'a')] }
@ -479,7 +478,6 @@ RSpec.describe DiffHelper do
before do before do
allow(helper).to receive(:merge_request).and_return(merge_request) allow(helper).to receive(:merge_request).and_return(merge_request)
allow(helper).to receive(:options).and_return(merge_ref_head_diff: merge_ref_head_diff)
allow_next_instance_of(MergeRequests::Conflicts::ListService, merge_request, allow_tree_conflicts: allow_tree_conflicts) do |svc| allow_next_instance_of(MergeRequests::Conflicts::ListService, merge_request, allow_tree_conflicts: allow_tree_conflicts) do |svc|
allow(svc).to receive(:can_be_resolved_in_ui?).and_return(can_be_resolved_in_ui?) allow(svc).to receive(:can_be_resolved_in_ui?).and_return(can_be_resolved_in_ui?)
@ -496,14 +494,6 @@ RSpec.describe DiffHelper do
expect(helper.conflicts).to eq('a' => files.first) expect(helper.conflicts).to eq('a' => files.first)
end end
context 'when merge_ref_head_diff option is false' do
let(:merge_ref_head_diff) { false }
it 'returns nil' do
expect(helper.conflicts).to be_nil
end
end
context 'when merge request can be merged' do context 'when merge request can be merged' do
let(:merge_request) { instance_double(MergeRequest, cannot_be_merged?: false) } let(:merge_request) { instance_double(MergeRequest, cannot_be_merged?: false) }

View File

@ -38,6 +38,6 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Median do
merge_request2.metrics.update!(merged_at: Time.zone.now) merge_request2.metrics.update!(merged_at: Time.zone.now)
end end
expect(subject).to be_within(0.5).of(7.5.minutes.seconds) expect(subject).to be_within(5.seconds).of(7.5.minutes.seconds)
end end
end end

View File

@ -12,7 +12,7 @@ RSpec.describe 'Jobs/Code-Quality.gitlab-ci.yml' do
let(:default_branch) { 'master' } let(:default_branch) { 'master' }
let(:pipeline_ref) { default_branch } let(:pipeline_ref) { default_branch }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) }
let(:pipeline) { service.execute!(:push).payload } let(:pipeline) { service.execute(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) } let(:build_names) { pipeline.builds.pluck(:name) }
before do before do
@ -62,7 +62,8 @@ RSpec.describe 'Jobs/Code-Quality.gitlab-ci.yml' do
context 'on master' do context 'on master' do
it 'has no jobs' do it 'has no jobs' do
expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError) expect(build_names).to be_empty
expect(pipeline.errors.full_messages).to match_array(["No stages / jobs for this pipeline."])
end end
end end
@ -70,7 +71,8 @@ RSpec.describe 'Jobs/Code-Quality.gitlab-ci.yml' do
let(:pipeline_ref) { 'feature' } let(:pipeline_ref) { 'feature' }
it 'has no jobs' do it 'has no jobs' do
expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError) expect(build_names).to be_empty
expect(pipeline.errors.full_messages).to match_array(["No stages / jobs for this pipeline."])
end end
end end
@ -78,7 +80,8 @@ RSpec.describe 'Jobs/Code-Quality.gitlab-ci.yml' do
let(:pipeline_ref) { 'v1.0.0' } let(:pipeline_ref) { 'v1.0.0' }
it 'has no jobs' do it 'has no jobs' do
expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError) expect(build_names).to be_empty
expect(pipeline.errors.full_messages).to match_array(["No stages / jobs for this pipeline."])
end end
end end
end end

View File

@ -9,10 +9,10 @@ RSpec.describe 'Jobs/SAST-IaC.gitlab-ci.yml' do
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { project.first_owner } let_it_be(:user) { project.first_owner }
let(:default_branch) { 'main' } let(:default_branch) { "master" }
let(:pipeline_ref) { default_branch } let(:pipeline_ref) { default_branch }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) }
let(:pipeline) { service.execute!(:push).payload } let(:pipeline) { service.execute(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) } let(:build_names) { pipeline.builds.pluck(:name) }
before do before do
@ -49,7 +49,8 @@ RSpec.describe 'Jobs/SAST-IaC.gitlab-ci.yml' do
context 'on default branch' do context 'on default branch' do
it 'has no jobs' do it 'has no jobs' do
expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError) expect(build_names).to be_empty
expect(pipeline.errors.full_messages).to match_array(["No stages / jobs for this pipeline."])
end end
end end
@ -57,7 +58,8 @@ RSpec.describe 'Jobs/SAST-IaC.gitlab-ci.yml' do
let(:pipeline_ref) { 'feature' } let(:pipeline_ref) { 'feature' }
it 'has no jobs' do it 'has no jobs' do
expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError) expect(build_names).to be_empty
expect(pipeline.errors.full_messages).to match_array(["No stages / jobs for this pipeline."])
end end
end end
end end

View File

@ -9,10 +9,10 @@ RSpec.describe 'Jobs/SAST-IaC.latest.gitlab-ci.yml' do
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { project.first_owner } let_it_be(:user) { project.first_owner }
let(:default_branch) { 'main' } let(:default_branch) { "master" }
let(:pipeline_ref) { default_branch } let(:pipeline_ref) { default_branch }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) }
let(:pipeline) { service.execute!(:push).payload } let(:pipeline) { service.execute(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) } let(:build_names) { pipeline.builds.pluck(:name) }
before do before do
@ -50,7 +50,8 @@ RSpec.describe 'Jobs/SAST-IaC.latest.gitlab-ci.yml' do
context 'on default branch' do context 'on default branch' do
it 'has no jobs' do it 'has no jobs' do
expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError) expect(build_names).to be_empty
expect(pipeline.errors.full_messages).to match_array(["No stages / jobs for this pipeline."])
end end
end end
@ -58,7 +59,8 @@ RSpec.describe 'Jobs/SAST-IaC.latest.gitlab-ci.yml' do
let(:pipeline_ref) { 'feature' } let(:pipeline_ref) { 'feature' }
it 'has no jobs' do it 'has no jobs' do
expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError) expect(build_names).to be_empty
expect(pipeline.errors.full_messages).to match_array(["No stages / jobs for this pipeline."])
end end
end end
end end

View File

@ -12,7 +12,7 @@ RSpec.describe 'Jobs/Test.gitlab-ci.yml' do
let(:default_branch) { 'master' } let(:default_branch) { 'master' }
let(:pipeline_ref) { default_branch } let(:pipeline_ref) { default_branch }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) }
let(:pipeline) { service.execute!(:push).payload } let(:pipeline) { service.execute(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) } let(:build_names) { pipeline.builds.pluck(:name) }
before do before do
@ -62,7 +62,8 @@ RSpec.describe 'Jobs/Test.gitlab-ci.yml' do
context 'on master' do context 'on master' do
it 'has no jobs' do it 'has no jobs' do
expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError) expect(build_names).to be_empty
expect(pipeline.errors.full_messages).to match_array(["No stages / jobs for this pipeline."])
end end
end end
@ -70,7 +71,8 @@ RSpec.describe 'Jobs/Test.gitlab-ci.yml' do
let(:pipeline_ref) { 'feature' } let(:pipeline_ref) { 'feature' }
it 'has no jobs' do it 'has no jobs' do
expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError) expect(build_names).to be_empty
expect(pipeline.errors.full_messages).to match_array(["No stages / jobs for this pipeline."])
end end
end end
@ -78,7 +80,8 @@ RSpec.describe 'Jobs/Test.gitlab-ci.yml' do
let(:pipeline_ref) { 'v1.0.0' } let(:pipeline_ref) { 'v1.0.0' }
it 'has no jobs' do it 'has no jobs' do
expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError) expect(build_names).to be_empty
expect(pipeline.errors.full_messages).to match_array(["No stages / jobs for this pipeline."])
end end
end end
end end

View File

@ -14,7 +14,7 @@ RSpec.describe 'npm.gitlab-ci.yml' do
let(:pipeline_tag) { 'v1.2.1' } let(:pipeline_tag) { 'v1.2.1' }
let(:pipeline_ref) { pipeline_branch } let(:pipeline_ref) { pipeline_branch }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref ) } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref ) }
let(:pipeline) { service.execute!(:push).payload } let(:pipeline) { service.execute(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) } let(:build_names) { pipeline.builds.pluck(:name) }
def create_branch(name:) def create_branch(name:)
@ -42,7 +42,8 @@ RSpec.describe 'npm.gitlab-ci.yml' do
shared_examples 'no pipeline created' do shared_examples 'no pipeline created' do
it 'does not create a pipeline because the only job (publish) is not created' do it 'does not create a pipeline because the only job (publish) is not created' do
expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError, 'No stages / jobs for this pipeline.') expect(build_names).to be_empty
expect(pipeline.errors.full_messages).to match_array(["No stages / jobs for this pipeline."])
end end
end end

View File

@ -14,7 +14,7 @@ RSpec.describe 'ThemeKit.gitlab-ci.yml' do
let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) } let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) }
let(:user) { project.first_owner } let(:user) { project.first_owner }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) } let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) }
let(:pipeline) { service.execute!(:push).payload } let(:pipeline) { service.execute(:push).payload }
let(:build_names) { pipeline.builds.pluck(:name) } let(:build_names) { pipeline.builds.pluck(:name) }
before do before do
@ -51,9 +51,8 @@ RSpec.describe 'ThemeKit.gitlab-ci.yml' do
end end
it 'has no jobs' do it 'has no jobs' do
expect { pipeline }.to raise_error( expect(build_names).to be_empty
Ci::CreatePipelineService::CreateError, 'No stages / jobs for this pipeline.' expect(pipeline.errors.full_messages).to match_array(["No stages / jobs for this pipeline."])
)
end end
end end
end end

View File

@ -382,5 +382,81 @@ RSpec.describe Gitlab::SidekiqMigrateJobs, :clean_gitlab_redis_queues do
it_behaves_like 'migrating queues' it_behaves_like 'migrating queues'
end end
context 'when multiple workers are in the same queue' do
before do
ExportCsvWorker.sidekiq_options(queue: 'email_receiver') # follows EmailReceiverWorker's queue
ExportCsvWorker.perform_async('fizz')
end
after do
ExportCsvWorker.set_queue
end
context 'when the queue exists in mappings' do
let(:mappings) do
{ 'EmailReceiverWorker' => 'email_receiver', 'AuthorizedProjectUpdate::ProjectRecalculateWorker' => 'default',
'ExportCsvWorker' => 'default' }
end
let(:queues_included_pre_migrate) do
['email_receiver',
'authorized_project_update:authorized_project_update_project_recalculate']
end
let(:queues_excluded_pre_migrate) { ['default'] }
let(:queues_excluded_post_migrate) do
['authorized_project_update:authorized_project_update_project_recalculate']
end
let(:queues_included_post_migrate) { %w[default email_receiver] }
it_behaves_like 'migrating queues'
def post_migrate_checks
# jobs from email_receiver are not migrated at all
jobs = list_jobs('email_receiver')
expect(jobs.length).to eq(3)
sorted = jobs.sort_by { |job| [job["class"], job["args"]] }
expect(sorted[0]).to include('class' => 'EmailReceiverWorker', 'args' => ['bar'], 'queue' => 'email_receiver')
expect(sorted[1]).to include('class' => 'EmailReceiverWorker', 'args' => ['foo'], 'queue' => 'email_receiver')
expect(sorted[2]).to include('class' => 'ExportCsvWorker', 'args' => ['fizz'], 'queue' => 'email_receiver')
end
end
context 'when the queue doesnt exist in mappings' do
let(:mappings) do
{ 'EmailReceiverWorker' => 'default', 'AuthorizedProjectUpdate::ProjectRecalculateWorker' => 'default',
'ExportCsvWorker' => 'default' }
end
let(:queues_included_pre_migrate) do
['email_receiver',
'authorized_project_update:authorized_project_update_project_recalculate']
end
let(:queues_excluded_pre_migrate) { ['default'] }
let(:queues_excluded_post_migrate) do
['email_receiver', 'authorized_project_update:authorized_project_update_project_recalculate']
end
let(:queues_included_post_migrate) { ['default'] }
it_behaves_like 'migrating queues'
def post_migrate_checks
# jobs from email_receiver are all migrated
jobs = list_jobs('email_receiver')
expect(jobs.length).to eq(0)
jobs = list_jobs('default')
expect(jobs.length).to eq(4)
sorted = jobs.sort_by { |job| [job["class"], job["args"]] }
expect(sorted[0]).to include('class' => 'AuthorizedProjectUpdate::ProjectRecalculateWorker',
'queue' => 'default')
expect(sorted[1]).to include('class' => 'EmailReceiverWorker', 'args' => ['bar'], 'queue' => 'default')
expect(sorted[2]).to include('class' => 'EmailReceiverWorker', 'args' => ['foo'], 'queue' => 'default')
expect(sorted[3]).to include('class' => 'ExportCsvWorker', 'args' => ['fizz'], 'queue' => 'default')
end
end
end
end end
end end

View File

@ -3,142 +3,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Integrations::Slack do RSpec.describe Integrations::Slack do
it_behaves_like Integrations::SlackMattermostNotifier, "Slack" it_behaves_like Integrations::SlackMattermostNotifier, 'Slack'
it_behaves_like Integrations::BaseSlackNotification, factory: :integrations_slack
describe '#execute' do
let_it_be(:project) { create(:project, :repository, :wiki_repo) }
let_it_be(:slack_integration) { create(:integrations_slack, branches_to_be_notified: 'all', project: project) }
before do
stub_request(:post, slack_integration.webhook)
end
it 'uses only known events', :aggregate_failures do
described_class::SUPPORTED_EVENTS_FOR_USAGE_LOG.each do |action|
expect(Gitlab::UsageDataCounters::HLLRedisCounter.known_event?("i_ecosystem_slack_service_#{action}_notification")).to be true
end
end
context 'hook data includes a user object' do
let_it_be(:user) { create_default(:user) }
shared_examples 'increases the usage data counter' do |event_name|
subject(:execute) { slack_integration.execute(data) }
it 'increases the usage data counter' do
expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(event_name, values: user.id).and_call_original
execute
end
it_behaves_like 'Snowplow event tracking' do
let(:feature_flag_name) { :route_hll_to_snowplow_phase2 }
let(:category) { 'Integrations::Slack' }
let(:action) { 'perform_integrations_action' }
let(:namespace) { project.namespace }
let(:label) { 'redis_hll_counters.ecosystem.ecosystem_total_unique_counts_monthly' }
let(:property) { event_name }
end
end
context 'event is not supported for usage log' do
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) }
it 'does not increase the usage data counter' do
expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event).with('i_ecosystem_slack_service_pipeline_notification', values: user.id)
slack_integration.execute(data)
end
end
context 'issue notification' do
let_it_be(:issue) { create(:issue, project: project) }
let(:data) { issue.to_hook_data(user) }
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_issue_notification'
end
context 'push notification' do
let(:data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_push_notification'
end
context 'deployment notification' do
let_it_be(:deployment) { create(:deployment, project: project, user: user) }
let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.current) }
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_deployment_notification'
end
context 'wiki_page notification' do
let(:wiki_page) { create(:wiki_page, wiki: project.wiki, project: project, message: 'user created page: Awesome wiki_page') }
let(:data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create') }
before do
# Skip this method that is not relevant to this test to prevent having
# to update project which is frozen
allow(project.wiki).to receive(:after_wiki_activity)
end
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_wiki_page_notification'
end
context 'merge_request notification' do
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
let(:data) { merge_request.to_hook_data(user) }
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_merge_request_notification'
end
context 'note notification' do
let_it_be(:issue_note) { create(:note_on_issue, project: project, note: 'issue note') }
let(:data) { Gitlab::DataBuilder::Note.build(issue_note, user) }
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_note_notification'
end
context 'tag_push notification' do
let(:oldrev) { Gitlab::Git::BLANK_SHA }
let(:newrev) { '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b' } # gitlab-test: git rev-parse refs/tags/v1.1.0
let(:ref) { 'refs/tags/v1.1.0' }
let(:data) { Git::TagHooksService.new(project, user, change: { oldrev: oldrev, newrev: newrev, ref: ref }).send(:push_data) }
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_tag_push_notification'
end
context 'confidential note notification' do
let_it_be(:confidential_issue_note) { create(:note_on_issue, project: project, note: 'issue note', confidential: true) }
let(:data) { Gitlab::DataBuilder::Note.build(confidential_issue_note, user) }
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_confidential_note_notification'
end
context 'confidential issue notification' do
let_it_be(:issue) { create(:issue, project: project, confidential: true) }
let(:data) { issue.to_hook_data(user) }
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_confidential_issue_notification'
end
end
context 'hook data does not include a user' do
let(:data) { Gitlab::DataBuilder::Pipeline.build(create(:ci_pipeline, project: project)) }
it 'does not increase the usage data counter' do
expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
slack_integration.execute(data)
end
end
end
end end

View File

@ -5138,17 +5138,7 @@ RSpec.describe MergeRequest, factory_default: :keep do
end end
it 'returns false' do it 'returns false' do
expect(merge_request.diffable_merge_ref?).to eq(true) expect(merge_request.diffable_merge_ref?).to eq(false)
end
context 'display_merge_conflicts_in_diff is disabled' do
before do
stub_feature_flags(display_merge_conflicts_in_diff: false)
end
it 'returns false' do
expect(merge_request.diffable_merge_ref?).to eq(false)
end
end end
end end
end end

View File

@ -35,7 +35,7 @@ RSpec.describe 'Merge Requests Context Commit Diffs' do
commit: nil, commit: nil,
diff_view: :inline, diff_view: :inline,
merge_ref_head_diff: nil, merge_ref_head_diff: nil,
allow_tree_conflicts: true, merge_conflicts_in_diff: true,
pagination_data: { pagination_data: {
total_pages: nil total_pages: nil
}.merge(pagination_data) }.merge(pagination_data)

View File

@ -33,7 +33,7 @@ RSpec.describe 'Merge Requests Diffs' do
commit: nil, commit: nil,
diff_view: :inline, diff_view: :inline,
merge_ref_head_diff: nil, merge_ref_head_diff: nil,
allow_tree_conflicts: true, merge_conflicts_in_diff: true,
pagination_data: { pagination_data: {
total_pages: nil total_pages: nil
}.merge(pagination_data) }.merge(pagination_data)
@ -128,7 +128,7 @@ RSpec.describe 'Merge Requests Diffs' do
context 'with disabled display_merge_conflicts_in_diff feature' do context 'with disabled display_merge_conflicts_in_diff feature' do
let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiffBatch } let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiffBatch }
let(:expected_options) { collection_arguments(total_pages: 20).merge(allow_tree_conflicts: false) } let(:expected_options) { collection_arguments(total_pages: 20).merge(merge_conflicts_in_diff: false) }
before do before do
stub_feature_flags(display_merge_conflicts_in_diff: false) stub_feature_flags(display_merge_conflicts_in_diff: false)

View File

@ -80,47 +80,13 @@ RSpec.describe DiffFileEntity do
end end
end end
describe '#is_fully_expanded' do
context 'file with a conflict' do
let(:options) { { conflicts: { diff_file.new_path => double(diff_lines_for_serializer: [], conflict_type: :both_modified) } } }
it 'returns false' do
expect(diff_file).not_to receive(:fully_expanded?)
expect(subject[:is_fully_expanded]).to eq(false)
end
end
end
describe '#highlighted_diff_lines' do describe '#highlighted_diff_lines' do
context 'file without a conflict' do let(:options) { { conflicts: {} } }
let(:options) { { conflicts: {} } }
it 'calls diff_lines_for_serializer on diff_file' do it 'calls diff_lines_for_serializer on diff_file' do
# #diff_lines_for_serializer gets called in #fully_expanded? as well so we expect twice # #diff_lines_for_serializer gets called in #fully_expanded? as well so we expect twice
expect(diff_file).to receive(:diff_lines_for_serializer).twice.and_return([]) expect(diff_file).to receive(:diff_lines_for_serializer).twice.and_return([])
expect(subject[:highlighted_diff_lines]).to eq([]) expect(subject[:highlighted_diff_lines]).to eq([])
end
end
context 'file with a conflict' do
let(:conflict_file) { instance_double(Gitlab::Conflict::File, conflict_type: :both_modified) }
let(:options) { { conflicts: { diff_file.new_path => conflict_file } } }
it 'calls diff_lines_for_serializer on matching conflict file' do
expect(conflict_file).to receive(:diff_lines_for_serializer).and_return([])
expect(subject[:highlighted_diff_lines]).to eq([])
end
context 'when Gitlab::Git::Conflict::Parser::UnmergeableFile gets raised' do
before do
allow(conflict_file).to receive(:diff_lines_for_serializer).and_raise(Gitlab::Git::Conflict::Parser::UnmergeableFile)
end
it 'falls back to diff_file diff_lines_for_serializer' do
expect(diff_file).to receive(:diff_lines_for_serializer).and_return([])
expect(subject[:highlighted_diff_lines]).to eq([])
end
end
end end
end end

View File

@ -9,13 +9,13 @@ RSpec.describe DiffsEntity do
let(:request) { EntityRequest.new(project: project, current_user: user) } let(:request) { EntityRequest.new(project: project, current_user: user) }
let(:merge_request_diffs) { merge_request.merge_request_diffs } let(:merge_request_diffs) { merge_request.merge_request_diffs }
let(:allow_tree_conflicts) { false } let(:merge_conflicts_in_diff) { false }
let(:options) do let(:options) do
{ {
request: request, request: request,
merge_request: merge_request, merge_request: merge_request,
merge_request_diffs: merge_request_diffs, merge_request_diffs: merge_request_diffs,
allow_tree_conflicts: allow_tree_conflicts merge_conflicts_in_diff: merge_conflicts_in_diff
} }
end end
@ -87,60 +87,39 @@ RSpec.describe DiffsEntity do
end end
end end
context 'when there are conflicts' do describe 'diff_files' do
let(:diff_files) { merge_request_diffs.first.diffs.diff_files } let(:diff_files) { merge_request_diffs.first.diffs.diff_files }
let(:diff_file_with_conflict) { diff_files.to_a.last }
let(:diff_file_without_conflict) { diff_files.to_a[-2] }
let(:resolvable_conflicts) { true } it 'serializes diff files using DiffFileEntity' do
let(:conflict_file) { double(path: diff_file_with_conflict.new_path, conflict_type: :both_modified) } expect(DiffFileEntity)
let(:conflicts) { double(conflicts: double(files: [conflict_file]), can_be_resolved_in_ui?: resolvable_conflicts) } .to receive(:represent)
.with(
diff_files,
hash_including(options.merge(conflicts: nil))
)
let(:merge_ref_head_diff) { true } subject[:diff_files]
let(:options) { super().merge(merge_ref_head_diff: merge_ref_head_diff) }
before do
allow(merge_request).to receive(:cannot_be_merged?).and_return(true)
allow(MergeRequests::Conflicts::ListService).to receive(:new).and_return(conflicts)
end end
it 'conflicts are highlighted' do context 'when merge_conflicts_in_diff is true' do
expect(conflict_file).to receive(:diff_lines_for_serializer) let(:conflict_file) { double(path: diff_files.first.new_path, conflict_type: :both_modified) }
expect(diff_file_with_conflict).not_to receive(:diff_lines_for_serializer) let(:conflicts) { double(conflicts: double(files: [conflict_file]), can_be_resolved_in_ui?: false) }
expect(diff_file_without_conflict).to receive(:diff_lines_for_serializer).twice # for highlighted_diff_lines and is_fully_expanded let(:merge_conflicts_in_diff) { true }
subject before do
end allow(merge_request).to receive(:cannot_be_merged?).and_return(true)
allow(MergeRequests::Conflicts::ListService).to receive(:new).and_return(conflicts)
context 'merge ref head diff is not chosen to be displayed' do
let(:merge_ref_head_diff) { false }
it 'conflicts are not calculated' do
expect(MergeRequests::Conflicts::ListService).not_to receive(:new)
end
end
context 'when conflicts cannot be resolved' do
let(:resolvable_conflicts) { false }
it 'conflicts are not highlighted' do
expect(conflict_file).not_to receive(:diff_lines_for_serializer)
expect(diff_file_with_conflict).to receive(:diff_lines_for_serializer).twice # for highlighted_diff_lines and is_fully_expanded
expect(diff_file_without_conflict).to receive(:diff_lines_for_serializer).twice # for highlighted_diff_lines and is_fully_expanded
subject
end end
context 'when allow_tree_conflicts is set to true' do it 'serializes diff files with conflicts' do
let(:allow_tree_conflicts) { true } expect(DiffFileEntity)
.to receive(:represent)
.with(
diff_files,
hash_including(options.merge(conflicts: { conflict_file.path => conflict_file }))
)
it 'conflicts are still highlighted' do subject[:diff_files]
expect(conflict_file).to receive(:diff_lines_for_serializer)
expect(diff_file_with_conflict).not_to receive(:diff_lines_for_serializer)
expect(diff_file_without_conflict).to receive(:diff_lines_for_serializer).twice # for highlighted_diff_lines and is_fully_expanded
subject
end
end end
end end
end end

View File

@ -9,6 +9,7 @@ RSpec.describe DiffsMetadataEntity do
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
let(:merge_request_diffs) { merge_request.merge_request_diffs } let(:merge_request_diffs) { merge_request.merge_request_diffs }
let(:merge_request_diff) { merge_request_diffs.last } let(:merge_request_diff) { merge_request_diffs.last }
let(:merge_conflicts_in_diff) { false }
let(:options) { {} } let(:options) { {} }
let(:entity) do let(:entity) do
@ -17,7 +18,8 @@ RSpec.describe DiffsMetadataEntity do
options.merge( options.merge(
request: request, request: request,
merge_request: merge_request, merge_request: merge_request,
merge_request_diffs: merge_request_diffs merge_request_diffs: merge_request_diffs,
merge_conflicts_in_diff: merge_conflicts_in_diff
) )
) )
end end
@ -54,49 +56,36 @@ RSpec.describe DiffsMetadataEntity do
end end
end end
it 'returns diff files metadata' do it 'serializes diff files metadata using DiffFileMetadataEntity' do
payload = DiffFileMetadataEntity.represent(raw_diff_files).as_json expect(DiffFileMetadataEntity)
.to receive(:represent)
.with(
raw_diff_files,
hash_including(options.merge(conflicts: nil))
)
expect(subject[:diff_files]).to eq(payload) subject[:diff_files]
end end
context 'when merge_ref_head_diff and allow_tree_conflicts options are set' do context 'when merge_conflicts_in_diff is true' do
let(:conflict_file) { double(path: raw_diff_files.first.new_path, conflict_type: :both_modified) } let(:conflict_file) { double(path: raw_diff_files.first.new_path, conflict_type: :both_modified) }
let(:conflicts) { double(conflicts: double(files: [conflict_file]), can_be_resolved_in_ui?: false) } let(:conflicts) { double(conflicts: double(files: [conflict_file]), can_be_resolved_in_ui?: false) }
let(:merge_conflicts_in_diff) { true }
before do before do
allow(merge_request).to receive(:cannot_be_merged?).and_return(true) allow(merge_request).to receive(:cannot_be_merged?).and_return(true)
allow(MergeRequests::Conflicts::ListService).to receive(:new).and_return(conflicts) allow(MergeRequests::Conflicts::ListService).to receive(:new).and_return(conflicts)
end end
context 'when merge_ref_head_diff is true and allow_tree_conflicts is false' do it 'serializes diff files with conflicts' do
let(:options) { { merge_ref_head_diff: true, allow_tree_conflicts: false } } expect(DiffFileMetadataEntity)
.to receive(:represent)
.with(
raw_diff_files,
hash_including(options.merge(conflicts: { conflict_file.path => conflict_file }))
)
it 'returns diff files metadata without conflicts' do subject[:diff_files]
payload = DiffFileMetadataEntity.represent(raw_diff_files).as_json
expect(subject[:diff_files]).to eq(payload)
end
end
context 'when merge_ref_head_diff is false and allow_tree_conflicts is true' do
let(:options) { { merge_ref_head_diff: false, allow_tree_conflicts: true } }
it 'returns diff files metadata without conflicts' do
payload = DiffFileMetadataEntity.represent(raw_diff_files).as_json
expect(subject[:diff_files]).to eq(payload)
end
end
context 'when merge_ref_head_diff and allow_tree_conflicts are true' do
let(:options) { { merge_ref_head_diff: true, allow_tree_conflicts: true } }
it 'returns diff files metadata with conflicts' do
payload = DiffFileMetadataEntity.represent(raw_diff_files, conflicts: { conflict_file.path => conflict_file }).as_json
expect(subject[:diff_files]).to eq(payload)
end
end end
end end
end end

View File

@ -7,13 +7,13 @@ RSpec.describe PaginatedDiffEntity do
let(:request) { double('request', current_user: user) } let(:request) { double('request', current_user: user) }
let(:merge_request) { create(:merge_request) } let(:merge_request) { create(:merge_request) }
let(:diff_batch) { merge_request.merge_request_diff.diffs_in_batch(2, 3, diff_options: nil) } let(:diff_batch) { merge_request.merge_request_diff.diffs_in_batch(2, 3, diff_options: nil) }
let(:allow_tree_conflicts) { false } let(:merge_conflicts_in_diff) { false }
let(:options) do let(:options) do
{ {
request: request, request: request,
merge_request: merge_request, merge_request: merge_request,
pagination_data: diff_batch.pagination_data, pagination_data: diff_batch.pagination_data,
allow_tree_conflicts: allow_tree_conflicts merge_conflicts_in_diff: merge_conflicts_in_diff
} }
end end
@ -29,61 +29,39 @@ RSpec.describe PaginatedDiffEntity do
expect(subject[:pagination]).to eq(total_pages: 20) expect(subject[:pagination]).to eq(total_pages: 20)
end end
context 'when there are conflicts' do describe 'diff_files' do
let(:diff_batch) { merge_request.merge_request_diff.diffs_in_batch(7, 3, diff_options: nil) } let(:diff_files) { diff_batch.diff_files(sorted: true) }
let(:diff_files) { diff_batch.diff_files.to_a }
let(:diff_file_with_conflict) { diff_files.last }
let(:diff_file_without_conflict) { diff_files.first }
let(:resolvable_conflicts) { true } it 'serializes diff files using DiffFileEntity' do
let(:conflict_file) { double(path: diff_file_with_conflict.new_path, conflict_type: :both_modified) } expect(DiffFileEntity)
let(:conflicts) { double(conflicts: double(files: [conflict_file]), can_be_resolved_in_ui?: resolvable_conflicts) } .to receive(:represent)
.with(
diff_files,
hash_including(options.merge(conflicts: nil))
)
let(:merge_ref_head_diff) { true } subject[:diff_files]
let(:options) { super().merge(merge_ref_head_diff: merge_ref_head_diff) }
before do
allow(merge_request).to receive(:cannot_be_merged?).and_return(true)
allow(MergeRequests::Conflicts::ListService).to receive(:new).and_return(conflicts)
end end
it 'conflicts are highlighted' do context 'when merge_conflicts_in_diff is true' do
expect(conflict_file).to receive(:diff_lines_for_serializer) let(:conflict_file) { double(path: diff_files.first.new_path, conflict_type: :both_modified) }
expect(diff_file_with_conflict).not_to receive(:diff_lines_for_serializer) let(:conflicts) { double(conflicts: double(files: [conflict_file]), can_be_resolved_in_ui?: false) }
expect(diff_file_without_conflict).to receive(:diff_lines_for_serializer).twice # for highlighted_diff_lines and is_fully_expanded let(:merge_conflicts_in_diff) { true }
subject before do
end allow(merge_request).to receive(:cannot_be_merged?).and_return(true)
allow(MergeRequests::Conflicts::ListService).to receive(:new).and_return(conflicts)
context 'merge ref head diff is not chosen to be displayed' do
let(:merge_ref_head_diff) { false }
it 'conflicts are not calculated' do
expect(MergeRequests::Conflicts::ListService).not_to receive(:new)
end
end
context 'when conflicts cannot be resolved' do
let(:resolvable_conflicts) { false }
it 'conflicts are not highlighted' do
expect(conflict_file).not_to receive(:diff_lines_for_serializer)
expect(diff_file_with_conflict).to receive(:diff_lines_for_serializer).twice # for highlighted_diff_lines and is_fully_expanded
expect(diff_file_without_conflict).to receive(:diff_lines_for_serializer).twice # for highlighted_diff_lines and is_fully_expanded
subject
end end
context 'when allow_tree_conflicts is set to true' do it 'serializes diff files with conflicts' do
let(:allow_tree_conflicts) { true } expect(DiffFileEntity)
.to receive(:represent)
.with(
diff_files,
hash_including(options.merge(conflicts: { conflict_file.path => conflict_file }))
)
it 'conflicts are still highlighted' do subject[:diff_files]
expect(conflict_file).to receive(:diff_lines_for_serializer)
expect(diff_file_with_conflict).not_to receive(:diff_lines_for_serializer)
expect(diff_file_without_conflict).to receive(:diff_lines_for_serializer).twice # for highlighted_diff_lines and is_fully_expanded
subject
end
end end
end end
end end

View File

@ -190,14 +190,6 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar
target_branch: 'conflict-start') target_branch: 'conflict-start')
end end
it 'does not change the merge ref HEAD' do
expect(merge_request.merge_ref_head).to be_nil
subject
expect(merge_request.reload.merge_ref_head).not_to be_nil
end
it 'returns ServiceResponse.error and keeps merge status as cannot_be_merged' do it 'returns ServiceResponse.error and keeps merge status as cannot_be_merged' do
result = subject result = subject
@ -351,27 +343,5 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar
end end
end end
end end
context 'merge with conflicts' do
it 'calls MergeToRefService with true allow_conflicts param' do
expect(MergeRequests::MergeToRefService).to receive(:new)
.with(project: project, current_user: merge_request.author, params: { allow_conflicts: true }).and_call_original
subject
end
context 'when display_merge_conflicts_in_diff is disabled' do
before do
stub_feature_flags(display_merge_conflicts_in_diff: false)
end
it 'calls MergeToRefService with false allow_conflicts param' do
expect(MergeRequests::MergeToRefService).to receive(:new)
.with(project: project, current_user: merge_request.author, params: { allow_conflicts: false }).and_call_original
subject
end
end
end
end end
end end

View File

@ -0,0 +1,150 @@
# frozen_string_literal: true
RSpec.shared_examples Integrations::BaseSlackNotification do |factory:|
describe '#execute' do
let_it_be(:project) { create(:project, :repository, :wiki_repo) }
let_it_be(:integration) { create(factory, branches_to_be_notified: 'all', project: project) }
before do
stub_request(:post, integration.webhook)
end
it 'uses only known events', :aggregate_failures do
described_class::SUPPORTED_EVENTS_FOR_USAGE_LOG.each do |action|
expect(
Gitlab::UsageDataCounters::HLLRedisCounter.known_event?("i_ecosystem_slack_service_#{action}_notification")
).to be true
end
end
context 'when hook data includes a user object' do
let_it_be(:user) { create_default(:user) }
shared_examples 'increases the usage data counter' do |event_name|
subject(:execute) { integration.execute(data) }
it 'increases the usage data counter' do
expect(Gitlab::UsageDataCounters::HLLRedisCounter)
.to receive(:track_event).with(event_name, values: user.id).and_call_original
execute
end
it_behaves_like 'Snowplow event tracking' do
let(:feature_flag_name) { :route_hll_to_snowplow_phase2 }
let(:category) { described_class.to_s }
let(:action) { 'perform_integrations_action' }
let(:namespace) { project.namespace }
let(:label) { 'redis_hll_counters.ecosystem.ecosystem_total_unique_counts_monthly' }
let(:property) { event_name }
end
end
context 'when event is not supported for usage log' do
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) }
it 'does not increase the usage data counter' do
expect(Gitlab::UsageDataCounters::HLLRedisCounter)
.not_to receive(:track_event).with('i_ecosystem_slack_service_pipeline_notification', values: user.id)
integration.execute(data)
end
end
context 'for issue notification' do
let_it_be(:issue) { create(:issue, project: project) }
let(:data) { issue.to_hook_data(user) }
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_issue_notification'
end
context 'for push notification' do
let(:data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_push_notification'
end
context 'for deployment notification' do
let_it_be(:deployment) { create(:deployment, project: project, user: user) }
let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.current) }
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_deployment_notification'
end
context 'for wiki_page notification' do
let_it_be(:wiki_page) do
create(:wiki_page, wiki: project.wiki, message: 'user created page: Awesome wiki_page')
end
let(:data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create') }
before do
# Skip this method that is not relevant to this test to prevent having
# to update project which is frozen
allow(project.wiki).to receive(:after_wiki_activity)
end
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_wiki_page_notification'
end
context 'for merge_request notification' do
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
let(:data) { merge_request.to_hook_data(user) }
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_merge_request_notification'
end
context 'for note notification' do
let_it_be(:issue_note) { create(:note_on_issue, project: project, note: 'issue note') }
let(:data) { Gitlab::DataBuilder::Note.build(issue_note, user) }
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_note_notification'
end
context 'for tag_push notification' do
let(:oldrev) { Gitlab::Git::BLANK_SHA }
let(:newrev) { '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b' } # gitlab-test: git rev-parse refs/tags/v1.1.0
let(:ref) { 'refs/tags/v1.1.0' }
let(:data) do
Git::TagHooksService.new(project, user, change: { oldrev: oldrev, newrev: newrev, ref: ref }).send(:push_data)
end
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_tag_push_notification'
end
context 'for confidential note notification' do
let_it_be(:confidential_issue_note) do
create(:note_on_issue, project: project, note: 'issue note', confidential: true)
end
let(:data) { Gitlab::DataBuilder::Note.build(confidential_issue_note, user) }
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_confidential_note_notification'
end
context 'for confidential issue notification' do
let_it_be(:issue) { create(:issue, project: project, confidential: true) }
let(:data) { issue.to_hook_data(user) }
it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_confidential_issue_notification'
end
end
context 'when hook data does not include a user' do
let(:data) { Gitlab::DataBuilder::Pipeline.build(create(:ci_pipeline, project: project)) }
it 'does not increase the usage data counter' do
expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event)
integration.execute(data)
end
end
end
end

View File

@ -90,6 +90,11 @@ RSpec.describe Namespaces::RootStatisticsWorker, '#perform' do
end end
end end
it_behaves_like 'worker with data consistency',
described_class,
feature_flag: :root_statistics_worker_read_replica,
data_consistency: :sticky
it 'has the `until_executed` deduplicate strategy' do it 'has the `until_executed` deduplicate strategy' do
expect(described_class.get_deduplicate_strategy).to eq(:until_executed) expect(described_class.get_deduplicate_strategy).to eq(:until_executed)
end end