summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/jobs/components/job/sidebar/legacy_sidebar_header.vue9
-rw-r--r--app/assets/javascripts/jobs/constants.js1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue25
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb8
-rw-r--r--app/helpers/diff_helper.rb1
-rw-r--r--app/models/integrations/base_slack_notification.rb62
-rw-r--r--app/models/integrations/slack.rb60
-rw-r--r--app/models/merge_request.rb2
-rw-r--r--app/serializers/diff_file_entity.rb21
-rw-r--r--app/serializers/diffs_entity.rb2
-rw-r--r--app/serializers/diffs_metadata_entity.rb2
-rw-r--r--app/serializers/paginated_diff_entity.rb2
-rw-r--r--app/services/merge_requests/mergeability_check_service.rb3
-rw-r--r--app/views/award_emoji/_awards_block.html.haml14
-rw-r--r--app/views/projects/_import_project_pane.html.haml53
-rw-r--r--app/workers/namespaces/root_statistics_worker.rb2
-rw-r--r--config/feature_categories.yml1
-rw-r--r--config/feature_flags/development/root_statistics_worker_read_replica.yml8
-rw-r--r--db/structure.sql20
-rw-r--r--doc/ci/yaml/index.md7
-rw-r--r--locale/gitlab.pot29
-rw-r--r--spec/controllers/projects/merge_requests/diffs_controller_spec.rb12
-rw-r--r--spec/features/projects/pipelines/legacy_pipeline_spec.rb7
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb7
-rw-r--r--spec/frontend/jobs/components/job/legacy_sidebar_header_spec.js20
-rw-r--r--spec/frontend/jobs/mock_data.js23
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js2
-rw-r--r--spec/helpers/diff_helper_spec.rb10
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb11
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb11
-rw-r--r--spec/lib/gitlab/ci/templates/npm_spec.rb5
-rw-r--r--spec/lib/gitlab/ci/templates/themekit_gitlab_ci_yaml_spec.rb7
-rw-r--r--spec/lib/gitlab/sidekiq_migrate_jobs_spec.rb76
-rw-r--r--spec/models/integrations/slack_spec.rb140
-rw-r--r--spec/models/merge_request_spec.rb12
-rw-r--r--spec/requests/projects/merge_requests/context_commit_diffs_spec.rb2
-rw-r--r--spec/requests/projects/merge_requests/diffs_spec.rb4
-rw-r--r--spec/serializers/diff_file_entity_spec.rb44
-rw-r--r--spec/serializers/diffs_entity_spec.rb73
-rw-r--r--spec/serializers/diffs_metadata_entity_spec.rb53
-rw-r--r--spec/serializers/paginated_diff_entity_spec.rb76
-rw-r--r--spec/services/merge_requests/mergeability_check_service_spec.rb30
-rw-r--r--spec/support/shared_examples/models/concerns/integrations/base_slack_notification_shared_examples.rb150
-rw-r--r--spec/workers/namespaces/root_statistics_worker_spec.rb5
47 files changed, 575 insertions, 559 deletions
diff --git a/app/assets/javascripts/jobs/components/job/sidebar/legacy_sidebar_header.vue b/app/assets/javascripts/jobs/components/job/sidebar/legacy_sidebar_header.vue
index 263b2d141c9..64b497c3550 100644
--- a/app/assets/javascripts/jobs/components/job/sidebar/legacy_sidebar_header.vue
+++ b/app/assets/javascripts/jobs/components/job/sidebar/legacy_sidebar_header.vue
@@ -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"
diff --git a/app/assets/javascripts/jobs/constants.js b/app/assets/javascripts/jobs/constants.js
index 50ee7bd20dd..e9475994e8b 100644
--- a/app/assets/javascripts/jobs/constants.js
+++ b/app/assets/javascripts/jobs/constants.js
@@ -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 = {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue
index ee81f0950a8..81cd3ef3ae1 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget_content_row.vue
@@ -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>
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
index f9a11ddb1db..c88dbc70ed5 100644
--- a/app/controllers/projects/merge_requests/diffs_controller.rb
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -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
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index b8329dada81..aca811ca5b6 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -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
diff --git a/app/models/integrations/base_slack_notification.rb b/app/models/integrations/base_slack_notification.rb
new file mode 100644
index 00000000000..cb785afdcfe
--- /dev/null
+++ b/app/models/integrations/base_slack_notification.rb
@@ -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
diff --git a/app/models/integrations/slack.rb b/app/models/integrations/slack.rb
index 834013e18f1..ffca0d40f69 100644
--- a/app/models/integrations/slack.rb
+++ b/app/models/integrations/slack.rb
@@ -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
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 220dd104335..f7b57c5a442 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -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
diff --git a/app/serializers/diff_file_entity.rb b/app/serializers/diff_file_entity.rb
index 9f8628fe849..aa43b9861d3 100644
--- a/app/serializers/diff_file_entity.rb
+++ b/app/serializers/diff_file_entity.rb
@@ -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
diff --git a/app/serializers/diffs_entity.rb b/app/serializers/diffs_entity.rb
index c818fcd6215..759d1e0f10a 100644
--- a/app/serializers/diffs_entity.rb
+++ b/app/serializers/diffs_entity.rb
@@ -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
diff --git a/app/serializers/diffs_metadata_entity.rb b/app/serializers/diffs_metadata_entity.rb
index 8c226130f6e..ace5105dda5 100644
--- a/app/serializers/diffs_metadata_entity.rb
+++ b/app/serializers/diffs_metadata_entity.rb
@@ -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
diff --git a/app/serializers/paginated_diff_entity.rb b/app/serializers/paginated_diff_entity.rb
index c656cff9dd7..b79a0937659 100644
--- a/app/serializers/paginated_diff_entity.rb
+++ b/app/serializers/paginated_diff_entity.rb
@@ -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
diff --git a/app/services/merge_requests/mergeability_check_service.rb b/app/services/merge_requests/mergeability_check_service.rb
index 1ce44f465cd..2a3f417a33b 100644
--- a/app/services/merge_requests/mergeability_check_service.rb
+++ b/app/services/merge_requests/mergeability_check_service.rb
@@ -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
diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml
index 5062599c261..6bdf1f7aae1 100644
--- a/app/views/award_emoji/_awards_block.html.haml
+++ b/app/views/award_emoji/_awards_block.html.haml
@@ -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
diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml
index 42cdc1d6989..afc7fb3d8b6 100644
--- a/app/views/projects/_import_project_pane.html.haml
+++ b/app/views/projects/_import_project_pane.html.haml
@@ -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') }
diff --git a/app/workers/namespaces/root_statistics_worker.rb b/app/workers/namespaces/root_statistics_worker.rb
index 157a779dda6..e3aa8a1f779 100644
--- a/app/workers/namespaces/root_statistics_worker.rb
+++ b/app/workers/namespaces/root_statistics_worker.rb
@@ -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
diff --git a/config/feature_categories.yml b/config/feature_categories.yml
index b30525da291..94a50dc416e 100644
--- a/config/feature_categories.yml
+++ b/config/feature_categories.yml
@@ -100,7 +100,6 @@
- planning_analytics
- pods
- portfolio_management
-- privacy_control_center
- product_analytics
- projects
- pubsec_services
diff --git a/config/feature_flags/development/root_statistics_worker_read_replica.yml b/config/feature_flags/development/root_statistics_worker_read_replica.yml
new file mode 100644
index 00000000000..516bead1ee7
--- /dev/null
+++ b/config/feature_flags/development/root_statistics_worker_read_replica.yml
@@ -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
diff --git a/db/structure.sql b/db/structure.sql
index 8aef35f9cb8..e1955b44f97 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -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();
diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md
index a32ba9751c4..7c446635096 100644
--- a/doc/ci/yaml/index.md
+++ b/doc/ci/yaml/index.md
@@ -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).
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 4d73fcd48fa..ef67a5be10c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
index 367781c0e76..613d82efd06 100644
--- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
@@ -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'
diff --git a/spec/features/projects/pipelines/legacy_pipeline_spec.rb b/spec/features/projects/pipelines/legacy_pipeline_spec.rb
index d93c951791d..c4fc194f0cd 100644
--- a/spec/features/projects/pipelines/legacy_pipeline_spec.rb
+++ b/spec/features/projects/pipelines/legacy_pipeline_spec.rb
@@ -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
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 9a99f16b669..66916aea8d7 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -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
diff --git a/spec/frontend/jobs/components/job/legacy_sidebar_header_spec.js b/spec/frontend/jobs/components/job/legacy_sidebar_header_spec.js
index cb32ca9d3dc..95eb10118ee 100644
--- a/spec/frontend/jobs/components/job/legacy_sidebar_header_spec.js
+++ b/spec/frontend/jobs/components/job/legacy_sidebar_header_spec.js
@@ -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,
+ );
+ });
+ });
+ });
});
diff --git a/spec/frontend/jobs/mock_data.js b/spec/frontend/jobs/mock_data.js
index 1dfd43ad21a..a7fe6d5a626 100644
--- a/spec/frontend/jobs/mock_data.js
+++ b/spec/frontend/jobs/mock_data.js
@@ -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',
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js
index 9eddd091ad0..f885db86ea1 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/widget/widget_content_row_spec.js
@@ -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);
});
});
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 93efce6b58b..1d5752ef284 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -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) }
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
index b4aa843bcd7..258f4a0d019 100644
--- a/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
+++ b/spec/lib/gitlab/analytics/cycle_analytics/median_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb
index d88d9782021..16c5d7a4b6d 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/code_quality_gitlab_ci_yaml_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb
index 85516d0bbb0..8a5aea7c0f0 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb
index 5ff179b6fee..d540b035f81 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_latest_gitlab_ci_yaml_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb
index a92a8397e96..7cf0cf3ed33 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/test_gitlab_ci_yaml_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/ci/templates/npm_spec.rb b/spec/lib/gitlab/ci/templates/npm_spec.rb
index d86a3a67823..55fd4675f11 100644
--- a/spec/lib/gitlab/ci/templates/npm_spec.rb
+++ b/spec/lib/gitlab/ci/templates/npm_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/ci/templates/themekit_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/themekit_gitlab_ci_yaml_spec.rb
index 4708108f404..157fd39f1cc 100644
--- a/spec/lib/gitlab/ci/templates/themekit_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/themekit_gitlab_ci_yaml_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/sidekiq_migrate_jobs_spec.rb b/spec/lib/gitlab/sidekiq_migrate_jobs_spec.rb
index db6ae1fc45a..c66e36c5621 100644
--- a/spec/lib/gitlab/sidekiq_migrate_jobs_spec.rb
+++ b/spec/lib/gitlab/sidekiq_migrate_jobs_spec.rb
@@ -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
diff --git a/spec/models/integrations/slack_spec.rb b/spec/models/integrations/slack_spec.rb
index affec7864e9..a12bc7f4831 100644
--- a/spec/models/integrations/slack_spec.rb
+++ b/spec/models/integrations/slack_spec.rb
@@ -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
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 665e62b82b9..8fd223d241a 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -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
diff --git a/spec/requests/projects/merge_requests/context_commit_diffs_spec.rb b/spec/requests/projects/merge_requests/context_commit_diffs_spec.rb
index c859e91e21a..ec65e8cf11e 100644
--- a/spec/requests/projects/merge_requests/context_commit_diffs_spec.rb
+++ b/spec/requests/projects/merge_requests/context_commit_diffs_spec.rb
@@ -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)
diff --git a/spec/requests/projects/merge_requests/diffs_spec.rb b/spec/requests/projects/merge_requests/diffs_spec.rb
index 57548a3c83f..12990b54617 100644
--- a/spec/requests/projects/merge_requests/diffs_spec.rb
+++ b/spec/requests/projects/merge_requests/diffs_spec.rb
@@ -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)
diff --git a/spec/serializers/diff_file_entity_spec.rb b/spec/serializers/diff_file_entity_spec.rb
index 48099cb1fdf..fbb45162136 100644
--- a/spec/serializers/diff_file_entity_spec.rb
+++ b/spec/serializers/diff_file_entity_spec.rb
@@ -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
diff --git a/spec/serializers/diffs_entity_spec.rb b/spec/serializers/diffs_entity_spec.rb
index 72777bde30c..ba40d538ccb 100644
--- a/spec/serializers/diffs_entity_spec.rb
+++ b/spec/serializers/diffs_entity_spec.rb
@@ -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)
- 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
-
- 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
+ subject[:diff_files]
end
- context 'when conflicts cannot be resolved' do
- let(:resolvable_conflicts) { false }
+ 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 }
- 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 '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
+ 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 }))
+ )
- subject
- end
+ subject[:diff_files]
end
end
end
diff --git a/spec/serializers/diffs_metadata_entity_spec.rb b/spec/serializers/diffs_metadata_entity_spec.rb
index 0e3d808aaac..04db576ffb5 100644
--- a/spec/serializers/diffs_metadata_entity_spec.rb
+++ b/spec/serializers/diffs_metadata_entity_spec.rb
@@ -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
diff --git a/spec/serializers/paginated_diff_entity_spec.rb b/spec/serializers/paginated_diff_entity_spec.rb
index 9d4456c11d6..3d77beb9abc 100644
--- a/spec/serializers/paginated_diff_entity_spec.rb
+++ b/spec/serializers/paginated_diff_entity_spec.rb
@@ -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)
- 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
-
- 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
+ subject[:diff_files]
end
- context 'when conflicts cannot be resolved' do
- let(:resolvable_conflicts) { false }
+ 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 }
- 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 '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
+ 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 }))
+ )
- subject
- end
+ subject[:diff_files]
end
end
end
diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb
index c24b83e21a6..ee23238314e 100644
--- a/spec/services/merge_requests/mergeability_check_service_spec.rb
+++ b/spec/services/merge_requests/mergeability_check_service_spec.rb
@@ -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
diff --git a/spec/support/shared_examples/models/concerns/integrations/base_slack_notification_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/base_slack_notification_shared_examples.rb
new file mode 100644
index 00000000000..f0581333b28
--- /dev/null
+++ b/spec/support/shared_examples/models/concerns/integrations/base_slack_notification_shared_examples.rb
@@ -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
diff --git a/spec/workers/namespaces/root_statistics_worker_spec.rb b/spec/workers/namespaces/root_statistics_worker_spec.rb
index f28a0815025..30854415405 100644
--- a/spec/workers/namespaces/root_statistics_worker_spec.rb
+++ b/spec/workers/namespaces/root_statistics_worker_spec.rb
@@ -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