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() {
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: {
...mapActions(['toggleSidebar']),
@ -66,8 +71,8 @@ export default {
<job-sidebar-retry-button
v-if="job.retry_path"
v-gl-tooltip.left
:title="$options.i18n.retryJobButtonLabel"
:aria-label="$options.i18n.retryJobButtonLabel"
:title="buttonTitle"
:aria-label="buttonTitle"
:category="retryButtonCategory"
:href="job.retry_path"
:modal-id="$options.forwardDeploymentFailureModalId"

View File

@ -15,6 +15,7 @@ export const JOB_SIDEBAR_COPY = {
retry: __('Retry'),
retryJobButtonLabel: s__('Job|Retry'),
toggleSidebar: __('Toggle Sidebar'),
runAgainJobButtonLabel: s__('Job|Run again'),
};
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 }"
>
<status-icon v-if="statusIconName" :level="2" :name="widgetName" :icon-name="statusIconName" />
<div>
<slot name="header">
<div v-if="header" class="gl-mb-2">
<strong v-safe-html="generatedHeader" class="gl-display-block"></strong
><span
v-if="generatedSubheader"
v-safe-html="generatedSubheader"
class="gl-display-block"
></span>
<div class="gl-w-full">
<div class="gl-display-flex">
<slot name="header">
<div v-if="header" class="gl-mb-2">
<strong v-safe-html="generatedHeader" class="gl-display-block"></strong
><span
v-if="generatedSubheader"
v-safe-html="generatedSubheader"
class="gl-display-block"
></span>
</div>
</slot>
<div v-if="$scopedSlots['header-actions']" class="gl-ml-auto">
<slot name="header-actions"></slot>
</div>
</slot>
</div>
<div class="gl-display-flex gl-align-items-baseline gl-w-full">
<slot name="body"></slot>
</div>

View File

@ -44,7 +44,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
diff_view: diff_view,
merge_ref_head_diff: render_merge_ref_head_diff?,
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.
@ -57,7 +57,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
params[:page],
params[:per_page],
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])
@ -81,7 +81,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
options = additional_attributes.merge(
only_context_commits: show_only_context_commits?,
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)
@ -110,7 +110,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
options = additional_attributes.merge(
diff_view: "inline",
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

View File

@ -227,7 +227,6 @@ module DiffHelper
end
def conflicts(allow_tree_conflicts: false)
return unless options[:merge_ref_head_diff]
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

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
module Integrations
class Slack < BaseChatNotification
class Slack < BaseSlackNotification
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
'Slack notifications'
end
@ -24,57 +16,9 @@ module Integrations
'slack'
end
def default_channel_placeholder
_('#general, #development')
end
override :webhook_placeholder
def webhook_placeholder
'https://hooks.slack.com/services/…'
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

View File

@ -1134,7 +1134,7 @@ class MergeRequest < ApplicationRecord
# rubocop: enable CodeReuse/ServiceClass
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
# Returns boolean indicating the merge_status should be rechecked in order to

View File

@ -55,17 +55,9 @@ class DiffFileEntity < DiffFileBaseEntity
end
# Used for inline diffs
expose :highlighted_diff_lines, using: DiffLineEntity, if: -> (diff_file, options) { inline_diff_view?(options) && diff_file.text? } do |diff_file|
highlighted_diff_lines_for(diff_file, options)
end
expose :diff_lines_for_serializer, as: :highlighted_diff_lines, using: DiffLineEntity, if: -> (diff_file, options) { inline_diff_view?(options) && diff_file.text? }
expose :is_fully_expanded do |diff_file|
if conflict_file(options, diff_file)
false
else
diff_file.fully_expanded?
end
end
expose :fully_expanded?, as: :is_fully_expanded
# Used for parallel diffs
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.
options.fetch(:diff_view, :inline).to_sym
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
DiffFileEntity.prepend_mod

View File

@ -74,7 +74,7 @@ class DiffsEntity < Grape::Entity
options.merge(
submodule_links: submodule_links,
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

View File

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

View File

@ -17,7 +17,7 @@ class PaginatedDiffEntity < Grape::Entity
options.merge(
submodule_links: submodule_links,
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

View File

@ -156,8 +156,7 @@ module MergeRequests
end
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: params).execute(merge_request)
result = MergeRequests::MergeToRefService.new(project: project, current_user: merge_request.author, params: {}).execute(merge_request)
result[:status] == :success
end

View File

@ -8,19 +8,15 @@
- 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_sort(grouped_emojis).each do |emoji, awards|
%button.gl-button.btn.btn-default.award-control.js-emoji-btn.has-tooltip{ type: "button",
class: [award_state_class(awardable, awards, current_user)],
data: { title: award_user_list(awards, current_user) } }
= 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
= emoji_icon(emoji)
%span.award-control-text.js-counter
= awards.count
- if can?(current_user, :award_emoji, awardable)
.award-menu-holder.js-award-holder
%button.gl-button.btn.btn-default.award-control.has-tooltip.js-add-award{ type: 'button',
'aria-label': _('Add reaction'),
data: { title: _('Add reaction') } }
%span{ class: "award-control-icon award-control-icon-neutral gl-icon" }= sprite_icon('slight-smile')
%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')
= 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
= sprite_icon('slight-smile', css_class: 'award-control-icon-neutral gl-icon gl-mr-0!')
= sprite_icon('smiley', css_class: 'award-control-icon-positive gl-icon')
= sprite_icon('smile', css_class: 'award-control-icon-super-positive gl-icon')
= yield

View File

@ -10,45 +10,32 @@
.import-buttons
- if gitlab_project_import_enabled?
.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
.gl-button-icon
= sprite_icon('tanuki')
= _("GitLab export")
= 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
= _('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?
%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
.gl-button-icon
= sprite_icon('github')
= 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
GitHub
- if bitbucket_import_enabled?
%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?}",
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')
= 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
Bitbucket Cloud
- if bitbucket_server_import_enabled?
%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
.gl-button-icon
= sprite_icon('bitbucket')
= 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
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?
%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
.gl-button-icon
= sprite_icon('bug')
= 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
FogBugz
- if gitea_import_enabled?
@ -60,24 +47,18 @@
- if git_import_enabled?
%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') } }
.gl-button-icon
= sprite_icon('link', css_class: 'gl-icon')
= 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
= _('Repository by URL')
- if manifest_import_enabled?
%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
.gl-button-icon
= sprite_icon('doc-text')
Manifest file
= 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
= _('Manifest file')
- if phabricator_import_enabled?
%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
.gl-button-icon
= custom_icon('issues')
= _("Phabricator Tasks")
= 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
= _('Phabricator tasks')
.js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') }

View File

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

View File

@ -100,7 +100,6 @@
- planning_analytics
- pods
- portfolio_management
- privacy_control_center
- product_analytics
- projects
- 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_send_warning_email_after_months integer DEFAULT 1 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,
max_export_size integer DEFAULT 0,
encrypted_slack_app_signing_secret bytea,
@ -11472,18 +11473,17 @@ CREATE TABLE application_settings (
cube_api_base_url text,
encrypted_cube_api_key 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_project_xid text,
clickhouse_connection_string text,
jitsu_administrator_email text,
encrypted_jitsu_administrator_password 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,
lock_maven_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,
issue_branch_template text,
show_diff_preview_in_email boolean DEFAULT true NOT NULL,
suggested_reviewers_enabled boolean DEFAULT false NOT NULL,
jitsu_key text,
suggested_reviewers_enabled boolean DEFAULT false NOT NULL,
only_allow_merge_if_all_status_checks_passed boolean DEFAULT false NOT NULL,
mirror_branch_regex text,
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 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 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_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;
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]
```
**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**:
- [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."
msgstr ""
msgid "GitLab.com"
msgstr ""
msgid "GitLab.com (SaaS)"
msgstr ""
@ -23390,6 +23387,9 @@ msgstr ""
msgid "Job|Retry"
msgstr ""
msgid "Job|Run again"
msgstr ""
msgid "Job|Running"
msgstr ""
@ -24744,6 +24744,9 @@ msgstr ""
msgid "Manifest"
msgstr ""
msgid "Manifest file"
msgstr ""
msgid "Manifest file import"
msgstr ""
@ -29450,7 +29453,7 @@ msgstr ""
msgid "Phabricator Server URL"
msgstr ""
msgid "Phabricator Tasks"
msgid "Phabricator tasks"
msgstr ""
msgid "Phone"
@ -47661,12 +47664,27 @@ msgstr ""
msgid "ciReport|Dependency scanning"
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"
msgstr ""
msgid "ciReport|Download the patch to apply it manually"
msgstr ""
msgid "ciReport|Dynamic Application Security Testing (DAST)"
msgstr ""
msgid "ciReport|Dynamic Application Security Testing (DAST) detects known vulnerabilities in your web application."
msgstr ""
@ -47786,6 +47804,9 @@ msgstr ""
msgid "ciReport|Solution"
msgstr ""
msgid "ciReport|Static Application Security Testing (SAST)"
msgstr ""
msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code."
msgstr ""

View File

@ -213,7 +213,7 @@ RSpec.describe Projects::MergeRequests::DiffsController do
commit: nil,
latest_diff: true,
only_context_commits: false,
allow_tree_conflicts: true,
merge_conflicts_in_diff: true,
merge_ref_head_diff: false
}
end
@ -281,7 +281,7 @@ RSpec.describe Projects::MergeRequests::DiffsController do
commit: nil,
latest_diff: true,
only_context_commits: false,
allow_tree_conflicts: true,
merge_conflicts_in_diff: true,
merge_ref_head_diff: nil
}
end
@ -303,7 +303,7 @@ RSpec.describe Projects::MergeRequests::DiffsController do
commit: merge_request.diff_head_commit,
latest_diff: nil,
only_context_commits: false,
allow_tree_conflicts: true,
merge_conflicts_in_diff: true,
merge_ref_head_diff: nil
}
end
@ -329,7 +329,7 @@ RSpec.describe Projects::MergeRequests::DiffsController do
commit: nil,
latest_diff: true,
only_context_commits: false,
allow_tree_conflicts: false,
merge_conflicts_in_diff: false,
merge_ref_head_diff: nil
}
end
@ -488,7 +488,7 @@ RSpec.describe Projects::MergeRequests::DiffsController do
commit: nil,
diff_view: :inline,
merge_ref_head_diff: nil,
allow_tree_conflicts: true,
merge_conflicts_in_diff: true,
pagination_data: {
total_pages: nil
}.merge(pagination_data)
@ -616,7 +616,7 @@ RSpec.describe Projects::MergeRequests::DiffsController do
it_behaves_like 'serializes diffs with expected arguments' do
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
it_behaves_like 'successful request'

View File

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

View File

@ -851,12 +851,7 @@ RSpec.describe 'Pipeline', :js do
before do
schedule.owner.block!
begin
PipelineScheduleWorker.new.perform
rescue Ci::CreatePipelineService::CreateError
# Do nothing, assert view code after the Pipeline failed to create.
end
PipelineScheduleWorker.new.perform
end
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 LegacySidebarHeader from '~/jobs/components/job/sidebar/legacy_sidebar_header.vue';
import createStore from '~/jobs/store';
import job from '../../mock_data';
import job, { failedJobStatus } from '../../mock_data';
describe('Legacy Sidebar Header', () => {
let store;
@ -67,6 +67,12 @@ describe('Legacy Sidebar Header', () => {
it('should render the retry button', () => {
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', () => {
@ -88,4 +94,16 @@ describe('Legacy Sidebar Header', () => {
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,
};
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 = {
name: 'build',
title: 'build: running',

View File

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

View File

@ -471,7 +471,6 @@ RSpec.describe DiffHelper do
describe '#conflicts' do
let(:merge_request) { instance_double(MergeRequest, cannot_be_merged?: true) }
let(:merge_ref_head_diff) { true }
let(:can_be_resolved_in_ui?) { true }
let(:allow_tree_conflicts) { false }
let(:files) { [instance_double(Gitlab::Conflict::File, path: 'a')] }
@ -479,7 +478,6 @@ RSpec.describe DiffHelper do
before do
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(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)
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
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)
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

View File

@ -12,7 +12,7 @@ RSpec.describe 'Jobs/Code-Quality.gitlab-ci.yml' do
let(:default_branch) { 'master' }
let(:pipeline_ref) { default_branch }
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) }
before do
@ -62,7 +62,8 @@ RSpec.describe 'Jobs/Code-Quality.gitlab-ci.yml' do
context 'on master' 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
@ -70,7 +71,8 @@ RSpec.describe 'Jobs/Code-Quality.gitlab-ci.yml' do
let(:pipeline_ref) { 'feature' }
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
@ -78,7 +80,8 @@ RSpec.describe 'Jobs/Code-Quality.gitlab-ci.yml' do
let(:pipeline_ref) { 'v1.0.0' }
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

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(:user) { project.first_owner }
let(:default_branch) { 'main' }
let(:default_branch) { "master" }
let(:pipeline_ref) { default_branch }
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) }
before do
@ -49,7 +49,8 @@ RSpec.describe 'Jobs/SAST-IaC.gitlab-ci.yml' do
context 'on default branch' 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
@ -57,7 +58,8 @@ RSpec.describe 'Jobs/SAST-IaC.gitlab-ci.yml' do
let(:pipeline_ref) { 'feature' }
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

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(:user) { project.first_owner }
let(:default_branch) { 'main' }
let(:default_branch) { "master" }
let(:pipeline_ref) { default_branch }
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) }
before do
@ -50,7 +50,8 @@ RSpec.describe 'Jobs/SAST-IaC.latest.gitlab-ci.yml' do
context 'on default branch' 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
@ -58,7 +59,8 @@ RSpec.describe 'Jobs/SAST-IaC.latest.gitlab-ci.yml' do
let(:pipeline_ref) { 'feature' }
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

View File

@ -12,7 +12,7 @@ RSpec.describe 'Jobs/Test.gitlab-ci.yml' do
let(:default_branch) { 'master' }
let(:pipeline_ref) { default_branch }
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) }
before do
@ -62,7 +62,8 @@ RSpec.describe 'Jobs/Test.gitlab-ci.yml' do
context 'on master' 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
@ -70,7 +71,8 @@ RSpec.describe 'Jobs/Test.gitlab-ci.yml' do
let(:pipeline_ref) { 'feature' }
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
@ -78,7 +80,8 @@ RSpec.describe 'Jobs/Test.gitlab-ci.yml' do
let(:pipeline_ref) { 'v1.0.0' }
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

View File

@ -14,7 +14,7 @@ RSpec.describe 'npm.gitlab-ci.yml' do
let(:pipeline_tag) { 'v1.2.1' }
let(:pipeline_ref) { pipeline_branch }
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) }
def create_branch(name:)
@ -42,7 +42,8 @@ RSpec.describe 'npm.gitlab-ci.yml' do
shared_examples 'no pipeline 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

View File

@ -14,7 +14,7 @@ RSpec.describe 'ThemeKit.gitlab-ci.yml' do
let(:project) { create(:project, :custom_repo, files: { 'README.md' => '' }) }
let(:user) { project.first_owner }
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) }
before do
@ -51,9 +51,8 @@ RSpec.describe 'ThemeKit.gitlab-ci.yml' do
end
it 'has no jobs' 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

View File

@ -382,5 +382,81 @@ RSpec.describe Gitlab::SidekiqMigrateJobs, :clean_gitlab_redis_queues do
it_behaves_like 'migrating queues'
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

View File

@ -3,142 +3,6 @@
require 'spec_helper'
RSpec.describe Integrations::Slack do
it_behaves_like Integrations::SlackMattermostNotifier, "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
it_behaves_like Integrations::SlackMattermostNotifier, 'Slack'
it_behaves_like Integrations::BaseSlackNotification, factory: :integrations_slack
end

View File

@ -5138,17 +5138,7 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
it 'returns false' do
expect(merge_request.diffable_merge_ref?).to eq(true)
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
expect(merge_request.diffable_merge_ref?).to eq(false)
end
end
end

View File

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

View File

@ -33,7 +33,7 @@ RSpec.describe 'Merge Requests Diffs' do
commit: nil,
diff_view: :inline,
merge_ref_head_diff: nil,
allow_tree_conflicts: true,
merge_conflicts_in_diff: true,
pagination_data: {
total_pages: nil
}.merge(pagination_data)
@ -128,7 +128,7 @@ RSpec.describe 'Merge Requests Diffs' do
context 'with disabled display_merge_conflicts_in_diff feature' do
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
stub_feature_flags(display_merge_conflicts_in_diff: false)

View File

@ -80,47 +80,13 @@ RSpec.describe DiffFileEntity do
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
context 'file without a conflict' do
let(:options) { { conflicts: {} } }
let(:options) { { conflicts: {} } }
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
expect(diff_file).to receive(:diff_lines_for_serializer).twice.and_return([])
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
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
expect(diff_file).to receive(:diff_lines_for_serializer).twice.and_return([])
expect(subject[:highlighted_diff_lines]).to eq([])
end
end

View File

@ -9,13 +9,13 @@ RSpec.describe DiffsEntity do
let(:request) { EntityRequest.new(project: project, current_user: user) }
let(:merge_request_diffs) { merge_request.merge_request_diffs }
let(:allow_tree_conflicts) { false }
let(:merge_conflicts_in_diff) { false }
let(:options) do
{
request: request,
merge_request: merge_request,
merge_request_diffs: merge_request_diffs,
allow_tree_conflicts: allow_tree_conflicts
merge_conflicts_in_diff: merge_conflicts_in_diff
}
end
@ -87,60 +87,39 @@ RSpec.describe DiffsEntity do
end
end
context 'when there are conflicts' do
describe 'diff_files' do
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 }
let(:conflict_file) { double(path: diff_file_with_conflict.new_path, conflict_type: :both_modified) }
let(:conflicts) { double(conflicts: double(files: [conflict_file]), can_be_resolved_in_ui?: resolvable_conflicts) }
it 'serializes diff files using DiffFileEntity' do
expect(DiffFileEntity)
.to receive(:represent)
.with(
diff_files,
hash_including(options.merge(conflicts: nil))
)
let(:merge_ref_head_diff) { true }
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)
subject[:diff_files]
end
it 'conflicts are highlighted' do
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
context 'when merge_conflicts_in_diff is true' do
let(:conflict_file) { double(path: diff_files.first.new_path, conflict_type: :both_modified) }
let(:conflicts) { double(conflicts: double(files: [conflict_file]), can_be_resolved_in_ui?: false) }
let(:merge_conflicts_in_diff) { true }
subject
end
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
before do
allow(merge_request).to receive(:cannot_be_merged?).and_return(true)
allow(MergeRequests::Conflicts::ListService).to receive(:new).and_return(conflicts)
end
context 'when allow_tree_conflicts is set to true' do
let(:allow_tree_conflicts) { true }
it 'serializes diff files with conflicts' do
expect(DiffFileEntity)
.to receive(:represent)
.with(
diff_files,
hash_including(options.merge(conflicts: { conflict_file.path => conflict_file }))
)
it 'conflicts are still highlighted' do
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
subject[:diff_files]
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_diffs) { merge_request.merge_request_diffs }
let(:merge_request_diff) { merge_request_diffs.last }
let(:merge_conflicts_in_diff) { false }
let(:options) { {} }
let(:entity) do
@ -17,7 +18,8 @@ RSpec.describe DiffsMetadataEntity do
options.merge(
request: 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
@ -54,49 +56,36 @@ RSpec.describe DiffsMetadataEntity do
end
end
it 'returns diff files metadata' do
payload = DiffFileMetadataEntity.represent(raw_diff_files).as_json
it 'serializes diff files metadata using DiffFileMetadataEntity' do
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
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(:conflicts) { double(conflicts: double(files: [conflict_file]), can_be_resolved_in_ui?: false) }
let(:merge_conflicts_in_diff) { true }
before do
allow(merge_request).to receive(:cannot_be_merged?).and_return(true)
allow(MergeRequests::Conflicts::ListService).to receive(:new).and_return(conflicts)
end
context 'when merge_ref_head_diff is true and allow_tree_conflicts is false' do
let(:options) { { merge_ref_head_diff: true, allow_tree_conflicts: false } }
it 'serializes diff files with conflicts' do
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
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
subject[:diff_files]
end
end
end

View File

@ -7,13 +7,13 @@ RSpec.describe PaginatedDiffEntity do
let(:request) { double('request', current_user: user) }
let(:merge_request) { create(:merge_request) }
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
{
request: request,
merge_request: merge_request,
pagination_data: diff_batch.pagination_data,
allow_tree_conflicts: allow_tree_conflicts
merge_conflicts_in_diff: merge_conflicts_in_diff
}
end
@ -29,61 +29,39 @@ RSpec.describe PaginatedDiffEntity do
expect(subject[:pagination]).to eq(total_pages: 20)
end
context 'when there are conflicts' do
let(:diff_batch) { merge_request.merge_request_diff.diffs_in_batch(7, 3, diff_options: nil) }
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 }
describe 'diff_files' do
let(:diff_files) { diff_batch.diff_files(sorted: true) }
let(:resolvable_conflicts) { true }
let(:conflict_file) { double(path: diff_file_with_conflict.new_path, conflict_type: :both_modified) }
let(:conflicts) { double(conflicts: double(files: [conflict_file]), can_be_resolved_in_ui?: resolvable_conflicts) }
it 'serializes diff files using DiffFileEntity' do
expect(DiffFileEntity)
.to receive(:represent)
.with(
diff_files,
hash_including(options.merge(conflicts: nil))
)
let(:merge_ref_head_diff) { true }
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)
subject[:diff_files]
end
it 'conflicts are highlighted' do
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
context 'when merge_conflicts_in_diff is true' do
let(:conflict_file) { double(path: diff_files.first.new_path, conflict_type: :both_modified) }
let(:conflicts) { double(conflicts: double(files: [conflict_file]), can_be_resolved_in_ui?: false) }
let(:merge_conflicts_in_diff) { true }
subject
end
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
before do
allow(merge_request).to receive(:cannot_be_merged?).and_return(true)
allow(MergeRequests::Conflicts::ListService).to receive(:new).and_return(conflicts)
end
context 'when allow_tree_conflicts is set to true' do
let(:allow_tree_conflicts) { true }
it 'serializes diff files with conflicts' do
expect(DiffFileEntity)
.to receive(:represent)
.with(
diff_files,
hash_including(options.merge(conflicts: { conflict_file.path => conflict_file }))
)
it 'conflicts are still highlighted' do
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
subject[:diff_files]
end
end
end

View File

@ -190,14 +190,6 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar
target_branch: 'conflict-start')
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
result = subject
@ -351,27 +343,5 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar
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

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
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
expect(described_class.get_deduplicate_strategy).to eq(:until_executed)
end