summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/filtered_search/recent_searches_storage_keys.js2
-rw-r--r--app/assets/javascripts/pages/projects/project_members/index.js64
-rw-r--r--app/assets/javascripts/projects/members/constants.js1
-rw-r--r--app/assets/javascripts/projects/members/utils.js8
-rw-r--r--app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue15
-rw-r--r--app/controllers/projects/project_members_controller.rb4
-rw-r--r--app/models/ci/build.rb45
-rw-r--r--app/models/ci/processable.rb56
-rw-r--r--app/models/ci/resource.rb6
-rw-r--r--app/models/ci/resource_group.rb10
-rw-r--r--app/models/commit.rb2
-rw-r--r--app/models/commit_status.rb10
-rw-r--r--app/models/readme_blob.rb17
-rw-r--r--app/models/repository.rb31
-rw-r--r--app/services/alert_management/process_prometheus_alert_service.rb118
-rw-r--r--app/services/ci/resource_groups/assign_resource_from_resource_group_service.rb4
-rw-r--r--app/services/concerns/alert_management/alert_processing.rb127
-rw-r--r--app/services/issuable_base_service.rb24
-rw-r--r--app/services/merge_requests/build_service.rb48
-rw-r--r--app/services/projects/alerting/notify_service.rb94
-rw-r--r--app/views/projects/project_members/index.html.haml49
-rw-r--r--changelogs/unreleased/223618-project-labels-api-return-404-label-not-found-if-label-name-contai.yml5
-rw-r--r--changelogs/unreleased/281677_space_in_filename.yml5
-rw-r--r--changelogs/unreleased/64320-refactor-mr-services.yml5
-rw-r--r--changelogs/unreleased/cngo-migrate-subscription-toggle-to-gitlab-ui.yml5
-rw-r--r--changelogs/unreleased/remove-banzai-commit-full-title.yml5
-rw-r--r--config/feature_flags/development/vue_project_members_list.yml8
-rw-r--r--doc/administration/instance_limits.md9
-rw-r--r--doc/analytics/README.md8
-rw-r--r--doc/api/members.md7
-rw-r--r--doc/ci/build_artifacts/README.md8
-rw-r--r--doc/ci/permissions/README.md8
-rw-r--r--doc/license/README.md8
-rw-r--r--doc/university/glossary/README.md5
-rw-r--r--doc/university/high-availability/aws/README.md8
-rw-r--r--doc/university/process/README.md5
-rw-r--r--doc/university/training/end-user/README.md5
-rw-r--r--doc/user/project/merge_requests/code_quality.md10
-rw-r--r--lib/api/group_labels.rb2
-rw-r--r--lib/api/labels.rb6
-rw-r--r--lib/api/subscriptions.rb5
-rw-r--r--lib/banzai/filter/truncate_source_filter.rb4
-rw-r--r--lib/gitlab/ci/config/entry/job.rb6
-rw-r--r--lib/gitlab/ci/config/entry/processable.rb8
-rw-r--r--lib/gitlab/ci/pipeline/seed/build/resource_group.rb10
-rw-r--r--lib/gitlab/search/query.rb18
-rw-r--r--locale/gitlab.pot6
-rwxr-xr-xscripts/lint-doc.sh2
-rw-r--r--spec/factories/ci/resource.rb2
-rw-r--r--spec/features/admin/admin_projects_spec.rb2
-rw-r--r--spec/features/boards/sidebar_spec.rb8
-rw-r--r--spec/features/issues/user_toggles_subscription_spec.rb6
-rw-r--r--spec/features/merge_request/user_manages_subscription_spec.rb2
-rw-r--r--spec/features/projects/members/anonymous_user_sees_members_spec.rb2
-rw-r--r--spec/features/projects/members/group_members_spec.rb2
-rw-r--r--spec/features/projects/members/groups_with_access_list_spec.rb2
-rw-r--r--spec/features/projects/members/invite_group_spec.rb1
-rw-r--r--spec/features/projects/members/list_spec.rb8
-rw-r--r--spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb2
-rw-r--r--spec/features/projects/members/sorting_spec.rb2
-rw-r--r--spec/features/projects/members/tabs_spec.rb1
-rw-r--r--spec/features/projects/settings/user_manages_project_members_spec.rb2
-rw-r--r--spec/frontend/projects/members/utils_spec.js14
-rw-r--r--spec/frontend/sidebar/subscriptions_spec.js25
-rw-r--r--spec/lib/banzai/filter/truncate_source_filter_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/pre_process_pipeline_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/processable_spec.rb29
-rw-r--r--spec/lib/gitlab/file_finder_spec.rb8
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb7
-rw-r--r--spec/lib/gitlab/search/query_spec.rb18
-rw-r--r--spec/models/ci/build_spec.rb54
-rw-r--r--spec/models/ci/pipeline_spec.rb2
-rw-r--r--spec/models/ci/processable_spec.rb54
-rw-r--r--spec/models/ci/resource_group_spec.rb12
-rw-r--r--spec/models/ci/resource_spec.rb2
-rw-r--r--spec/models/commit_spec.rb13
-rw-r--r--spec/models/commit_status_spec.rb17
-rw-r--r--spec/models/readme_blob_spec.rb17
-rw-r--r--spec/models/repository_spec.rb18
-rw-r--r--spec/requests/api/group_labels_spec.rb54
-rw-r--r--spec/requests/api/labels_spec.rb59
-rw-r--r--spec/services/alert_management/process_prometheus_alert_service_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb25
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb4
-rw-r--r--spec/services/merge_requests/push_options_handler_service_spec.rb45
85 files changed, 795 insertions, 646 deletions
diff --git a/app/assets/javascripts/filtered_search/recent_searches_storage_keys.js b/app/assets/javascripts/filtered_search/recent_searches_storage_keys.js
index 54d49821d92..446a0e5eb24 100644
--- a/app/assets/javascripts/filtered_search/recent_searches_storage_keys.js
+++ b/app/assets/javascripts/filtered_search/recent_searches_storage_keys.js
@@ -3,4 +3,6 @@ export default {
merge_requests: 'merge-request-recent-searches',
group_members: 'group-members-recent-searches',
group_invited_members: 'group-invited-members-recent-searches',
+ project_members: 'project-members-recent-searches',
+ project_group_links: 'project-group-links-recent-searches',
};
diff --git a/app/assets/javascripts/pages/projects/project_members/index.js b/app/assets/javascripts/pages/projects/project_members/index.js
index 3e0a48ee6a2..f029b26fa78 100644
--- a/app/assets/javascripts/pages/projects/project_members/index.js
+++ b/app/assets/javascripts/pages/projects/project_members/index.js
@@ -6,6 +6,8 @@ import groupsSelect from '~/groups_select';
import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
+import { __ } from '~/locale';
+import { deprecatedCreateFlash as flash } from '~/flash';
function mountRemoveMemberModal() {
const el = document.querySelector('.js-remove-member-modal');
@@ -32,3 +34,65 @@ document.addEventListener('DOMContentLoaded', () => {
new Members(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new
});
+
+if (window.gon.features.vueProjectMembersList) {
+ const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions'];
+
+ Promise.all([
+ import('~/members/index'),
+ import('~/members/utils'),
+ import('~/projects/members/utils'),
+ import('~/locale'),
+ ])
+ .then(
+ ([
+ { initMembersApp },
+ { groupLinkRequestFormatter },
+ { projectMemberRequestFormatter },
+ { s__ },
+ ]) => {
+ initMembersApp(document.querySelector('.js-project-members-list'), {
+ tableFields: SHARED_FIELDS.concat(['source', 'granted']),
+ tableAttrs: { tr: { 'data-qa-selector': 'member_row' } },
+ tableSortableFields: ['account', 'granted', 'maxRole', 'lastSignIn'],
+ requestFormatter: projectMemberRequestFormatter,
+ filteredSearchBar: {
+ show: true,
+ tokens: ['with_inherited_permissions'],
+ searchParam: 'search',
+ placeholder: s__('Members|Filter members'),
+ recentSearchesStorageKey: 'project_members',
+ },
+ });
+
+ initMembersApp(document.querySelector('.js-project-group-links-list'), {
+ tableFields: SHARED_FIELDS.concat('granted'),
+ tableAttrs: {
+ table: { 'data-qa-selector': 'groups_list' },
+ tr: { 'data-qa-selector': 'group_row' },
+ },
+ requestFormatter: groupLinkRequestFormatter,
+ filteredSearchBar: {
+ show: true,
+ tokens: [],
+ searchParam: 'search_groups',
+ placeholder: s__('Members|Search groups'),
+ recentSearchesStorageKey: 'project_group_links',
+ },
+ });
+
+ initMembersApp(document.querySelector('.js-project-invited-members-list'), {
+ tableFields: SHARED_FIELDS.concat('invited'),
+ requestFormatter: projectMemberRequestFormatter,
+ });
+
+ initMembersApp(document.querySelector('.js-project-access-requests-list'), {
+ tableFields: SHARED_FIELDS.concat('requested'),
+ requestFormatter: projectMemberRequestFormatter,
+ });
+ },
+ )
+ .catch(() => {
+ flash(__('An error occurred while loading the members, please try again.'));
+ });
+}
diff --git a/app/assets/javascripts/projects/members/constants.js b/app/assets/javascripts/projects/members/constants.js
new file mode 100644
index 00000000000..a69a64fe882
--- /dev/null
+++ b/app/assets/javascripts/projects/members/constants.js
@@ -0,0 +1 @@
+export const PROJECT_MEMBER_BASE_PROPERTY_NAME = 'project_member';
diff --git a/app/assets/javascripts/projects/members/utils.js b/app/assets/javascripts/projects/members/utils.js
new file mode 100644
index 00000000000..0dd8de58223
--- /dev/null
+++ b/app/assets/javascripts/projects/members/utils.js
@@ -0,0 +1,8 @@
+import { baseRequestFormatter } from '~/members/utils';
+import { PROJECT_MEMBER_BASE_PROPERTY_NAME } from './constants';
+import { MEMBER_ACCESS_LEVEL_PROPERTY_NAME } from '~/members/constants';
+
+export const projectMemberRequestFormatter = baseRequestFormatter(
+ PROJECT_MEMBER_BASE_PROPERTY_NAME,
+ MEMBER_ACCESS_LEVEL_PROPERTY_NAME,
+);
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
index 6d21936791c..9b06c20a6f3 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
@@ -1,8 +1,7 @@
<script>
-import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
+import { GlIcon, GlToggle, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import Tracking from '~/tracking';
-import toggleButton from '~/vue_shared/components/toggle_button.vue';
import eventHub from '../../event_hub';
const ICON_ON = 'notifications';
@@ -16,7 +15,7 @@ export default {
},
components: {
GlIcon,
- toggleButton,
+ GlToggle,
},
mixins: [Tracking.mixin({ label: 'right_sidebar' })],
props: {
@@ -106,7 +105,7 @@ export default {
</script>
<template>
- <div>
+ <div class="gl-display-flex gl-justify-content-space-between">
<span
ref="tooltip"
v-gl-tooltip.viewport.left
@@ -116,13 +115,13 @@ export default {
>
<gl-icon :name="notificationIcon" :size="16" class="sidebar-item-icon is-active" />
</span>
- <span class="issuable-header-text hide-collapsed float-left"> {{ notificationText }} </span>
- <toggle-button
+ <span class="hide-collapsed" data-testid="subscription-title"> {{ notificationText }} </span>
+ <gl-toggle
v-if="!projectEmailsDisabled"
- ref="toggleButton"
:is-loading="showLoadingState"
:value="subscribed"
- class="float-right hide-collapsed js-issuable-subscribe-button"
+ class="hide-collapsed"
+ data-testid="subscription-toggle"
@change="toggleSubscription"
/>
</div>
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index 5972b29a298..463b989c493 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -8,6 +8,10 @@ class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize
before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access]
+ before_action do
+ push_frontend_feature_flag(:vue_project_members_list, @project)
+ end
+
feature_category :authentication_and_authorization
def index
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 11c4f5fafc5..b44aead4296 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -20,7 +20,6 @@ module Ci
belongs_to :runner
belongs_to :trigger_request
belongs_to :erased_by, class_name: 'User'
- belongs_to :resource_group, class_name: 'Ci::ResourceGroup', inverse_of: :builds
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
RUNNER_FEATURES = {
@@ -38,7 +37,6 @@ module Ci
DEGRADATION_THRESHOLD_VARIABLE_NAME = 'DEGRADATION_THRESHOLD'
has_one :deployment, as: :deployable, class_name: 'Deployment'
- has_one :resource, class_name: 'Ci::Resource', inverse_of: :build
has_one :pending_state, class_name: 'Ci::BuildPendingState', inverse_of: :build
has_many :trace_sections, class_name: 'Ci::BuildTraceSection'
has_many :trace_chunks, class_name: 'Ci::BuildTraceChunk', foreign_key: :build_id, inverse_of: :build
@@ -236,21 +234,14 @@ module Ci
state_machine :status do
event :enqueue do
- transition [:created, :skipped, :manual, :scheduled] => :waiting_for_resource, if: :requires_resource?
transition [:created, :skipped, :manual, :scheduled] => :preparing, if: :any_unmet_prerequisites?
end
event :enqueue_scheduled do
- transition scheduled: :waiting_for_resource, if: :requires_resource?
transition scheduled: :preparing, if: :any_unmet_prerequisites?
transition scheduled: :pending
end
- event :enqueue_waiting_for_resource do
- transition waiting_for_resource: :preparing, if: :any_unmet_prerequisites?
- transition waiting_for_resource: :pending
- end
-
event :enqueue_preparing do
transition preparing: :pending
end
@@ -279,23 +270,6 @@ module Ci
build.scheduled_at = build.options_scheduled_at
end
- before_transition any => :waiting_for_resource do |build|
- build.waiting_for_resource_at = Time.current
- end
-
- before_transition on: :enqueue_waiting_for_resource do |build|
- next unless build.requires_resource?
-
- build.resource_group.assign_resource_to(build) # If false is returned, it stops the transition
- end
-
- after_transition any => :waiting_for_resource do |build|
- build.run_after_commit do
- Ci::ResourceGroups::AssignResourceFromResourceGroupWorker
- .perform_async(build.resource_group_id)
- end
- end
-
before_transition on: :enqueue_preparing do |build|
!build.any_unmet_prerequisites? # If false is returned, it stops the transition
end
@@ -328,16 +302,6 @@ module Ci
end
end
- after_transition any => ::Ci::Build.completed_statuses do |build|
- next unless build.resource_group_id.present?
- next unless build.resource_group.release_resource_from(build)
-
- build.run_after_commit do
- Ci::ResourceGroups::AssignResourceFromResourceGroupWorker
- .perform_async(build.resource_group_id)
- end
- end
-
after_transition any => [:success, :failed, :canceled] do |build|
build.run_after_commit do
build.run_status_commit_hooks!
@@ -467,6 +431,11 @@ module Ci
pipeline.builds.retried.where(name: self.name).count
end
+ override :all_met_to_become_pending?
+ def all_met_to_become_pending?
+ super && !any_unmet_prerequisites?
+ end
+
def any_unmet_prerequisites?
prerequisites.present?
end
@@ -501,10 +470,6 @@ module Ci
end
end
- def requires_resource?
- self.resource_group_id.present?
- end
-
def has_environment?
environment.present?
end
diff --git a/app/models/ci/processable.rb b/app/models/ci/processable.rb
index 6aaf6ac530b..fae65ed0632 100644
--- a/app/models/ci/processable.rb
+++ b/app/models/ci/processable.rb
@@ -3,6 +3,11 @@
module Ci
class Processable < ::CommitStatus
include Gitlab::Utils::StrongMemoize
+ extend ::Gitlab::Utils::Override
+
+ has_one :resource, class_name: 'Ci::Resource', foreign_key: 'build_id', inverse_of: :processable
+
+ belongs_to :resource_group, class_name: 'Ci::ResourceGroup', inverse_of: :processables
accepts_nested_attributes_for :needs
@@ -20,6 +25,48 @@ module Ci
where('NOT EXISTS (?)', needs)
end
+ state_machine :status do
+ event :enqueue do
+ transition [:created, :skipped, :manual, :scheduled] => :waiting_for_resource, if: :with_resource_group?
+ end
+
+ event :enqueue_scheduled do
+ transition scheduled: :waiting_for_resource, if: :with_resource_group?
+ end
+
+ event :enqueue_waiting_for_resource do
+ transition waiting_for_resource: :preparing, if: :any_unmet_prerequisites?
+ transition waiting_for_resource: :pending
+ end
+
+ before_transition any => :waiting_for_resource do |processable|
+ processable.waiting_for_resource_at = Time.current
+ end
+
+ before_transition on: :enqueue_waiting_for_resource do |processable|
+ next unless processable.with_resource_group?
+
+ processable.resource_group.assign_resource_to(processable)
+ end
+
+ after_transition any => :waiting_for_resource do |processable|
+ processable.run_after_commit do
+ Ci::ResourceGroups::AssignResourceFromResourceGroupWorker
+ .perform_async(processable.resource_group_id)
+ end
+ end
+
+ after_transition any => ::Ci::Processable.completed_statuses do |processable|
+ next unless processable.with_resource_group?
+ next unless processable.resource_group.release_resource_from(processable)
+
+ processable.run_after_commit do
+ Ci::ResourceGroups::AssignResourceFromResourceGroupWorker
+ .perform_async(processable.resource_group_id)
+ end
+ end
+ end
+
def self.select_with_aggregated_needs(project)
aggregated_needs_names = Ci::BuildNeed
.scoped_build
@@ -77,6 +124,15 @@ module Ci
raise NotImplementedError
end
+ override :all_met_to_become_pending?
+ def all_met_to_become_pending?
+ super && !with_resource_group?
+ end
+
+ def with_resource_group?
+ self.resource_group_id.present?
+ end
+
# Overriding scheduling_type enum's method for nil `scheduling_type`s
def scheduling_type_dag?
scheduling_type.nil? ? find_legacy_scheduling_type == :dag : super
diff --git a/app/models/ci/resource.rb b/app/models/ci/resource.rb
index ee5b6546165..e0e1fab642d 100644
--- a/app/models/ci/resource.rb
+++ b/app/models/ci/resource.rb
@@ -5,9 +5,9 @@ module Ci
extend Gitlab::Ci::Model
belongs_to :resource_group, class_name: 'Ci::ResourceGroup', inverse_of: :resources
- belongs_to :build, class_name: 'Ci::Build', inverse_of: :resource
+ belongs_to :processable, class_name: 'Ci::Processable', foreign_key: 'build_id', inverse_of: :resource
- scope :free, -> { where(build: nil) }
- scope :retained_by, -> (build) { where(build: build) }
+ scope :free, -> { where(processable: nil) }
+ scope :retained_by, -> (processable) { where(processable: processable) }
end
end
diff --git a/app/models/ci/resource_group.rb b/app/models/ci/resource_group.rb
index eb18f3da0bf..85fbe03e1c9 100644
--- a/app/models/ci/resource_group.rb
+++ b/app/models/ci/resource_group.rb
@@ -7,7 +7,7 @@ module Ci
belongs_to :project, inverse_of: :resource_groups
has_many :resources, class_name: 'Ci::Resource', inverse_of: :resource_group
- has_many :builds, class_name: 'Ci::Build', inverse_of: :resource_group
+ has_many :processables, class_name: 'Ci::Processable', inverse_of: :resource_group
validates :key,
length: { maximum: 255 },
@@ -19,12 +19,12 @@ module Ci
##
# NOTE: This is concurrency-safe method that the subquery in the `UPDATE`
# works as explicit locking.
- def assign_resource_to(build)
- resources.free.limit(1).update_all(build_id: build.id) > 0
+ def assign_resource_to(processable)
+ resources.free.limit(1).update_all(build_id: processable.id) > 0
end
- def release_resource_from(build)
- resources.retained_by(build).update_all(build_id: nil) > 0
+ def release_resource_from(processable)
+ resources.retained_by(processable).update_all(build_id: nil) > 0
end
private
diff --git a/app/models/commit.rb b/app/models/commit.rb
index edce9ad293e..bf168aaacc5 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -36,7 +36,7 @@ class Commit
LINK_EXTENSION_PATTERN = /(patch)/.freeze
cache_markdown_field :title, pipeline: :single_line
- cache_markdown_field :full_title, pipeline: :single_line
+ cache_markdown_field :full_title, pipeline: :single_line, limit: 1.kilobyte
cache_markdown_field :description, pipeline: :commit_description, limit: 1.megabyte
class << self
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index a399ffc32de..e0c2b308247 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -255,15 +255,7 @@ class CommitStatus < ApplicationRecord
end
def all_met_to_become_pending?
- !any_unmet_prerequisites? && !requires_resource?
- end
-
- def any_unmet_prerequisites?
- false
- end
-
- def requires_resource?
- false
+ true
end
def auto_canceled?
diff --git a/app/models/readme_blob.rb b/app/models/readme_blob.rb
deleted file mode 100644
index 695b4e3ffe3..00000000000
--- a/app/models/readme_blob.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-class ReadmeBlob < SimpleDelegator
- include BlobActiveModel
-
- attr_reader :repository
-
- def initialize(blob, repository)
- @repository = repository
-
- super(blob)
- end
-
- def rendered_markup
- repository.rendered_readme
- end
-end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index ef204170732..ba13b67d101 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -39,7 +39,7 @@ class Repository
#
# For example, for entry `:commit_count` there's a method called `commit_count` which
# stores its data in the `commit_count` cache key.
- CACHED_METHODS = %i(size commit_count rendered_readme readme_path contribution_guide
+ CACHED_METHODS = %i(size commit_count readme_path contribution_guide
changelog license_blob license_key gitignore
gitlab_ci_yml branch_names tag_names branch_count
tag_count avatar exists? root_ref merged_branch_names
@@ -53,7 +53,7 @@ class Repository
# changed. This Hash maps file types (as returned by Gitlab::FileDetector) to
# the corresponding methods to call for refreshing caches.
METHOD_CACHES_FOR_FILE_TYPES = {
- readme: %i(rendered_readme readme_path),
+ readme: %i(readme_path),
changelog: :changelog,
license: %i(license_blob license_key license),
contributing: :contribution_guide,
@@ -498,23 +498,7 @@ class Repository
end
def blob_at(sha, path)
- blob = Blob.decorate(raw_repository.blob_at(sha, path), container)
-
- # Don't attempt to return a special result if there is no blob at all
- return unless blob
-
- # Don't attempt to return a special result if this can't be a README
- return blob unless Gitlab::FileDetector.type_of(blob.name) == :readme
-
- # Don't attempt to return a special result unless we're looking at HEAD
- return blob unless head_commit&.sha == sha
-
- case path
- when head_tree&.readme_path
- ReadmeBlob.new(blob, self)
- else
- blob
- end
+ Blob.decorate(raw_repository.blob_at(sha, path), container)
rescue Gitlab::Git::Repository::NoRepository
nil
end
@@ -612,15 +596,6 @@ class Repository
end
cache_method :readme_path
- def rendered_readme
- return unless readme
-
- context = { project: project }
-
- MarkupHelper.markup_unsafe(readme.name, readme.data, context)
- end
- cache_method :rendered_readme
-
def contribution_guide
file_on_head(:contributing)
end
diff --git a/app/services/alert_management/process_prometheus_alert_service.rb b/app/services/alert_management/process_prometheus_alert_service.rb
index 753162bfdbf..545c5581f72 100644
--- a/app/services/alert_management/process_prometheus_alert_service.rb
+++ b/app/services/alert_management/process_prometheus_alert_service.rb
@@ -2,9 +2,8 @@
module AlertManagement
class ProcessPrometheusAlertService
- include BaseServiceUtility
- include Gitlab::Utils::StrongMemoize
- include ::IncidentManagement::Settings
+ extend ::Gitlab::Utils::Override
+ include ::AlertManagement::AlertProcessing
def initialize(project, payload)
@project = project
@@ -14,11 +13,10 @@ module AlertManagement
def execute
return bad_request unless incoming_payload.has_required_attributes?
- process_alert_management_alert
+ process_alert
return bad_request unless alert.persisted?
- process_incident_issues if process_issues?
- send_alert_email if send_email?
+ complete_post_processing_tasks
ServiceResponse.success
end
@@ -27,110 +25,31 @@ module AlertManagement
attr_reader :project, :payload
- def process_alert_management_alert
- if incoming_payload.resolved?
- process_resolved_alert_management_alert
- else
- process_firing_alert_management_alert
- end
- end
-
- def process_firing_alert_management_alert
- if alert.persisted?
- alert.register_new_event!
- reset_alert_management_alert_status
- else
- create_alert_management_alert
- end
- end
+ override :process_new_alert
+ def process_new_alert
+ return if resolving_alert?
- def reset_alert_management_alert_status
- return if alert.trigger
-
- logger.warn(
- message: 'Unable to update AlertManagement::Alert status to triggered',
- project_id: project.id,
- alert_id: alert.id
- )
+ super
end
- def create_alert_management_alert
- if alert.save
- alert.execute_services
- SystemNoteService.create_new_alert(alert, Gitlab::AlertManagement::Payload::MONITORING_TOOLS[:prometheus])
- return
- end
+ override :process_firing_alert
+ def process_firing_alert
+ super
- logger.warn(
- message: 'Unable to create AlertManagement::Alert',
- project_id: project.id,
- alert_errors: alert.errors.messages
- )
+ reset_alert_status
end
- def process_resolved_alert_management_alert
- return unless alert.persisted?
- return unless auto_close_incident?
-
- if alert.resolve(incoming_payload.ends_at)
- close_issue(alert.issue)
- return
- end
+ def reset_alert_status
+ return if alert.trigger
logger.warn(
- message: 'Unable to update AlertManagement::Alert status to resolved',
+ message: 'Unable to update AlertManagement::Alert status to triggered',
project_id: project.id,
alert_id: alert.id
)
end
- def close_issue(issue)
- return if issue.blank? || issue.closed?
-
- Issues::CloseService
- .new(project, User.alert_bot)
- .execute(issue, system_note: false)
-
- SystemNoteService.auto_resolve_prometheus_alert(issue, project, User.alert_bot) if issue.reset.closed?
- end
-
- def process_incident_issues
- return if alert.issue || alert.resolved?
-
- IncidentManagement::ProcessAlertWorker.perform_async(nil, nil, alert.id)
- end
-
- def send_alert_email
- notification_service
- .async
- .prometheus_alerts_fired(project, [alert])
- end
-
- def logger
- @logger ||= Gitlab::AppLogger
- end
-
- def alert
- strong_memoize(:alert) do
- existing_alert || new_alert
- end
- end
-
- def existing_alert
- strong_memoize(:existing_alert) do
- AlertManagement::Alert.not_resolved.for_fingerprint(project, incoming_payload.gitlab_fingerprint).first
- end
- end
-
- def new_alert
- strong_memoize(:new_alert) do
- AlertManagement::Alert.new(
- **incoming_payload.alert_params,
- ended_at: nil
- )
- end
- end
-
+ override :incoming_payload
def incoming_payload
strong_memoize(:incoming_payload) do
Gitlab::AlertManagement::Payload.parse(
@@ -141,6 +60,11 @@ module AlertManagement
end
end
+ override :resolving_alert?
+ def resolving_alert?
+ incoming_payload.resolved?
+ end
+
def bad_request
ServiceResponse.error(message: 'Bad Request', http_status: :bad_request)
end
diff --git a/app/services/ci/resource_groups/assign_resource_from_resource_group_service.rb b/app/services/ci/resource_groups/assign_resource_from_resource_group_service.rb
index a4bcca8e8b3..9e3e6de3928 100644
--- a/app/services/ci/resource_groups/assign_resource_from_resource_group_service.rb
+++ b/app/services/ci/resource_groups/assign_resource_from_resource_group_service.rb
@@ -7,8 +7,8 @@ module Ci
def execute(resource_group)
free_resources = resource_group.resources.free.count
- resource_group.builds.waiting_for_resource.take(free_resources).each do |build|
- build.enqueue_waiting_for_resource
+ resource_group.processables.waiting_for_resource.take(free_resources).each do |processable|
+ processable.enqueue_waiting_for_resource
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/concerns/alert_management/alert_processing.rb b/app/services/concerns/alert_management/alert_processing.rb
new file mode 100644
index 00000000000..4143a4668f5
--- /dev/null
+++ b/app/services/concerns/alert_management/alert_processing.rb
@@ -0,0 +1,127 @@
+# frozen_string_literal: true
+
+module AlertManagement
+ # Module to support the processing of new alert payloads
+ # from various sources. Payloads may be for new alerts,
+ # existing alerts, or acting as a resolving alert.
+ #
+ # Performs processing-related tasks, such as creating system
+ # notes, creating or resolving related issues, and notifying
+ # stakeholders of the alert.
+ #
+ # Requires #project [Project] and #payload [Hash] methods
+ # to be defined.
+ module AlertProcessing
+ include BaseServiceUtility
+ include Gitlab::Utils::StrongMemoize
+ include ::IncidentManagement::Settings
+
+ # Updates or creates alert from payload for project
+ # including system notes
+ def process_alert
+ if alert.persisted?
+ process_existing_alert
+ else
+ process_new_alert
+ end
+ end
+
+ # Creates or closes issue for alert and notifies stakeholders
+ def complete_post_processing_tasks
+ process_incident_issues if process_issues?
+ send_alert_email if send_email?
+ end
+
+ def process_existing_alert
+ if resolving_alert?
+ process_resolved_alert
+ else
+ process_firing_alert
+ end
+ end
+
+ def process_resolved_alert
+ return unless auto_close_incident?
+ return close_issue(alert.issue) if alert.resolve(incoming_payload.ends_at)
+
+ logger.warn(
+ message: 'Unable to update AlertManagement::Alert status to resolved',
+ project_id: project.id,
+ alert_id: alert.id
+ )
+ end
+
+ def process_firing_alert
+ alert.register_new_event!
+ end
+
+ def close_issue(issue)
+ return if issue.blank? || issue.closed?
+
+ ::Issues::CloseService
+ .new(project, User.alert_bot)
+ .execute(issue, system_note: false)
+
+ SystemNoteService.auto_resolve_prometheus_alert(issue, project, User.alert_bot) if issue.reset.closed?
+ end
+
+ def process_new_alert
+ if alert.save
+ alert.execute_services
+ SystemNoteService.create_new_alert(alert, alert_source)
+ else
+ logger.warn(
+ message: "Unable to create AlertManagement::Alert from #{alert_source}",
+ project_id: project.id,
+ alert_errors: alert.errors.messages
+ )
+ end
+ end
+
+ def process_incident_issues
+ return if alert.issue || alert.resolved?
+
+ ::IncidentManagement::ProcessAlertWorker.perform_async(nil, nil, alert.id)
+ end
+
+ def send_alert_email
+ notification_service
+ .async
+ .prometheus_alerts_fired(project, [alert])
+ end
+
+ def incoming_payload
+ strong_memoize(:incoming_payload) do
+ Gitlab::AlertManagement::Payload.parse(project, payload.to_h)
+ end
+ end
+
+ def alert
+ strong_memoize(:alert) do
+ find_existing_alert || build_new_alert
+ end
+ end
+
+ def find_existing_alert
+ return unless incoming_payload.gitlab_fingerprint
+
+ AlertManagement::Alert.not_resolved.for_fingerprint(project, incoming_payload.gitlab_fingerprint).first
+ end
+
+ def build_new_alert
+ AlertManagement::Alert.new(**incoming_payload.alert_params, ended_at: nil)
+ end
+
+ def resolving_alert?
+ incoming_payload.ends_at.present?
+ end
+
+ def alert_source
+ alert.monitoring_tool
+ end
+
+ def logger
+ @logger ||= Gitlab::AppLogger
+ end
+ end
+end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 6d41d449683..7c508237c8d 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -190,11 +190,7 @@ class IssuableBaseService < BaseService
change_additional_attributes(issuable)
old_associations = associations_before_update(issuable)
- label_ids = process_label_ids(params, existing_label_ids: issuable.label_ids)
- if labels_changing?(issuable.label_ids, label_ids)
- params[:label_ids] = label_ids
- issuable.touch
- end
+ assign_requested_labels(issuable)
if issuable.changed? || params.present?
issuable.assign_attributes(params)
@@ -297,10 +293,6 @@ class IssuableBaseService < BaseService
update_task(issuable)
end
- def labels_changing?(old_label_ids, new_label_ids)
- old_label_ids.sort != new_label_ids.sort
- end
-
def has_title_or_description_changed?(issuable)
issuable.title_changed? || issuable.description_changed?
end
@@ -349,6 +341,20 @@ class IssuableBaseService < BaseService
end
# rubocop: enable CodeReuse/ActiveRecord
+ def assign_requested_labels(issuable)
+ label_ids = process_label_ids(params, existing_label_ids: issuable.label_ids)
+ return unless ids_changing?(issuable.label_ids, label_ids)
+
+ params[:label_ids] = label_ids
+ issuable.touch
+ end
+
+ # Arrays of ids are used, but we should really use sets of ids, so
+ # let's have an helper to properly check if some ids are changing
+ def ids_changing?(old_array, new_array)
+ old_array.sort != new_array.sort
+ end
+
def toggle_award(issuable)
award = params.delete(:emoji_award)
AwardEmojis::ToggleService.new(issuable, award, current_user).execute if award
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 80991657688..12c901aa1a1 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -16,30 +16,17 @@ module MergeRequests
merge_request.source_project = find_source_project
merge_request.target_project = find_target_project
- # Source project sets the default source branch removal setting
- merge_request.merge_params['force_remove_source_branch'] =
- if params.key?(:force_remove_source_branch)
- params.delete(:force_remove_source_branch)
- else
- merge_request.source_project.remove_source_branch_after_merge?
- end
+ # Force remove the source branch?
+ merge_request.merge_params['force_remove_source_branch'] = force_remove_source_branch
+ # Only assign merge requests params that are allowed
self.params = assign_allowed_merge_params(merge_request, params)
+ # Filter out params that are either not allowed or invalid
filter_params(merge_request)
- # merge_request.assign_attributes(...) below is a Rails
- # method that only work if all the params it is passed have
- # corresponding fields in the database. As there are no fields
- # in the database for :add_label_ids and :remove_label_ids, we
- # need to remove them from the params before the call to
- # merge_request.assign_attributes(...)
- #
- # IssuableBaseService#process_label_ids takes care
- # of the removal.
- params[:label_ids] = process_label_ids(params, extra_label_ids: merge_request.label_ids.to_a)
-
- merge_request.assign_attributes(params.to_h.compact)
+ # Filter out :add_label_ids and :remove_label_ids params
+ filter_label_id_params
merge_request.compare_commits = []
set_merge_request_target_branch
@@ -74,6 +61,29 @@ module MergeRequests
:errors,
to: :merge_request
+ def force_remove_source_branch
+ if params.key?(:force_remove_source_branch)
+ params.delete(:force_remove_source_branch)
+ else
+ merge_request.source_project.remove_source_branch_after_merge?
+ end
+ end
+
+ def filter_label_id_params
+ # merge_request.assign_attributes(...) below is a Rails
+ # method that only work if all the params it is passed have
+ # corresponding fields in the database. As there are no fields
+ # in the database for :add_label_ids and :remove_label_ids, we
+ # need to remove them from the params before the call to
+ # merge_request.assign_attributes(...)
+ #
+ # IssuableBaseService#process_label_ids takes care
+ # of the removal.
+ params[:label_ids] = process_label_ids(params, extra_label_ids: merge_request.label_ids.to_a)
+
+ merge_request.assign_attributes(params.to_h.compact)
+ end
+
def find_source_project
source_project = project_from_params(:source_project)
return source_project if source_project.present? && can?(current_user, :create_merge_request_from, source_project)
diff --git a/app/services/projects/alerting/notify_service.rb b/app/services/projects/alerting/notify_service.rb
index 014fb0e3ed3..2ba64b73699 100644
--- a/app/services/projects/alerting/notify_service.rb
+++ b/app/services/projects/alerting/notify_service.rb
@@ -3,9 +3,8 @@
module Projects
module Alerting
class NotifyService
- include BaseServiceUtility
- include Gitlab::Utils::StrongMemoize
- include ::IncidentManagement::Settings
+ extend ::Gitlab::Utils::Override
+ include ::AlertManagement::AlertProcessing
def initialize(project, payload)
@project = project
@@ -22,8 +21,7 @@ module Projects
process_alert
return bad_request unless alert.persisted?
- process_incident_issues if process_issues?
- send_alert_email if send_email?
+ complete_post_processing_tasks
ServiceResponse.success
end
@@ -32,93 +30,15 @@ module Projects
attr_reader :project, :payload, :integration
- def process_alert
- if alert.persisted?
- process_existing_alert
- else
- create_alert
- end
- end
-
- def process_existing_alert
- if incoming_payload.ends_at.present?
- process_resolved_alert
- else
- alert.register_new_event!
- end
-
- alert
- end
-
- def process_resolved_alert
- return unless auto_close_incident?
-
- if alert.resolve(incoming_payload.ends_at)
- close_issue(alert.issue)
- end
-
- alert
- end
-
- def close_issue(issue)
- return if issue.blank? || issue.closed?
-
- ::Issues::CloseService
- .new(project, User.alert_bot)
- .execute(issue, system_note: false)
-
- SystemNoteService.auto_resolve_prometheus_alert(issue, project, User.alert_bot) if issue.reset.closed?
- end
-
- def create_alert
- return unless alert.save
-
- alert.execute_services
- SystemNoteService.create_new_alert(alert, notification_source)
- end
-
- def process_incident_issues
- return if alert.issue || alert.resolved?
-
- ::IncidentManagement::ProcessAlertWorker.perform_async(nil, nil, alert.id)
- end
-
- def send_alert_email
- notification_service
- .async
- .prometheus_alerts_fired(project, [alert])
- end
-
- def alert
- strong_memoize(:alert) do
- existing_alert || new_alert
- end
- end
-
- def existing_alert
- return unless incoming_payload.gitlab_fingerprint
-
- AlertManagement::Alert.not_resolved.for_fingerprint(project, incoming_payload.gitlab_fingerprint).first
- end
-
- def new_alert
- AlertManagement::Alert.new(**incoming_payload.alert_params, ended_at: nil)
- end
-
- def incoming_payload
- strong_memoize(:incoming_payload) do
- Gitlab::AlertManagement::Payload.parse(project, payload.to_h)
- end
+ def valid_payload_size?
+ Gitlab::Utils::DeepSize.new(payload).valid?
end
- def notification_source
+ override :alert_source
+ def alert_source
alert.monitoring_tool || integration&.name || 'Generic Alert Endpoint'
end
- def valid_payload_size?
- Gitlab::Utils::DeepSize.new(payload).valid?
- end
-
def active_integration?
integration&.active?
end
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 3da012d1335..b3c209d564b 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,5 +1,6 @@
- page_title _("Members")
- group = @project.group
+- vue_project_members_list_enabled = Feature.enabled?(:vue_project_members_list, @project)
.js-remove-member-modal
.row.gl-mt-3
@@ -74,24 +75,44 @@
%span.badge.badge-pill= @requesters.count
.tab-content
#tab-members.tab-pane{ class: ('active' unless groups_tab_active?) }
- = render 'projects/project_members/team', project: @project, group: group, members: @project_members, current_user_is_group_owner: current_user_is_group_owner?(@project)
+ - if vue_project_members_list_enabled
+ .js-project-members-list{ data: project_members_list_data_attributes(@project, @project_members) }
+ .loading
+ .spinner.spinner-md
+ - else
+ = render 'projects/project_members/team', project: @project, group: group, members: @project_members, current_user_is_group_owner: current_user_is_group_owner?(@project)
= paginate @project_members, theme: "gitlab", params: { search_groups: nil }
- if show_groups?(@group_links)
#tab-groups.tab-pane{ class: ('active' if groups_tab_active?) }
- = render 'projects/project_members/groups', group_links: @group_links
+ - if vue_project_members_list_enabled
+ .js-project-group-links-list{ data: project_group_links_list_data_attributes(@project, @group_links) }
+ .loading
+ .spinner.spinner-md
+ - else
+ = render 'projects/project_members/groups', group_links: @group_links
- if show_invited_members?(@project, @invited_members)
#tab-invited-members.tab-pane
- .card.card-without-border
- = render 'shared/members/tab_pane/header' do
- = render 'shared/members/tab_pane/title' do
- = html_escape(_('Members invited to %{strong_start}%{project_name}%{strong_end}')) % { project_name: @project.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
- %ul.content-list.members-list
- = render partial: 'shared/members/member', collection: @invited_members, as: :member, locals: { membership_source: @project, group: group, current_user_is_group_owner: current_user_is_group_owner?(@project) }
+ - if vue_project_members_list_enabled
+ .js-project-invited-members-list{ data: project_members_list_data_attributes(@project, @invited_members) }
+ .loading
+ .spinner.spinner-md
+ - else
+ .card.card-without-border
+ = render 'shared/members/tab_pane/header' do
+ = render 'shared/members/tab_pane/title' do
+ = html_escape(_('Members invited to %{strong_start}%{project_name}%{strong_end}')) % { project_name: @project.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
+ %ul.content-list.members-list
+ = render partial: 'shared/members/member', collection: @invited_members, as: :member, locals: { membership_source: @project, group: group, current_user_is_group_owner: current_user_is_group_owner?(@project) }
- if show_access_requests?(@project, @requesters)
#tab-access-requests.tab-pane
- .card.card-without-border
- = render 'shared/members/tab_pane/header' do
- = render 'shared/members/tab_pane/title' do
- = html_escape(_('Users requesting access to %{strong_start}%{project_name}%{strong_end}')) % { project_name: @project.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
- %ul.content-list.members-list
- = render partial: 'shared/members/member', collection: @requesters, as: :member, locals: { membership_source: @project, group: group }
+ - if vue_project_members_list_enabled
+ .js-project-access-requests-list{ data: project_members_list_data_attributes(@project, @requesters) }
+ .loading
+ .spinner.spinner-md
+ - else
+ .card.card-without-border
+ = render 'shared/members/tab_pane/header' do
+ = render 'shared/members/tab_pane/title' do
+ = html_escape(_('Users requesting access to %{strong_start}%{project_name}%{strong_end}')) % { project_name: @project.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
+ %ul.content-list.members-list
+ = render partial: 'shared/members/member', collection: @requesters, as: :member, locals: { membership_source: @project, group: group }
diff --git a/changelogs/unreleased/223618-project-labels-api-return-404-label-not-found-if-label-name-contai.yml b/changelogs/unreleased/223618-project-labels-api-return-404-label-not-found-if-label-name-contai.yml
new file mode 100644
index 00000000000..1b580214c19
--- /dev/null
+++ b/changelogs/unreleased/223618-project-labels-api-return-404-label-not-found-if-label-name-contai.yml
@@ -0,0 +1,5 @@
+---
+title: Allow dots in label names through REST API
+merge_request: 52591
+author:
+type: fixed
diff --git a/changelogs/unreleased/281677_space_in_filename.yml b/changelogs/unreleased/281677_space_in_filename.yml
new file mode 100644
index 00000000000..30ae5bcfa0d
--- /dev/null
+++ b/changelogs/unreleased/281677_space_in_filename.yml
@@ -0,0 +1,5 @@
+---
+title: Improve search filter by taking space in file path into account
+merge_request: 52392
+author:
+type: fixed
diff --git a/changelogs/unreleased/64320-refactor-mr-services.yml b/changelogs/unreleased/64320-refactor-mr-services.yml
new file mode 100644
index 00000000000..18f59c36400
--- /dev/null
+++ b/changelogs/unreleased/64320-refactor-mr-services.yml
@@ -0,0 +1,5 @@
+---
+title: Code extraction - refactoring of MR services classes
+merge_request: 49827
+author:
+type: changed
diff --git a/changelogs/unreleased/cngo-migrate-subscription-toggle-to-gitlab-ui.yml b/changelogs/unreleased/cngo-migrate-subscription-toggle-to-gitlab-ui.yml
new file mode 100644
index 00000000000..1bc12d43d8b
--- /dev/null
+++ b/changelogs/unreleased/cngo-migrate-subscription-toggle-to-gitlab-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate toggle button in subscription to GitLab UI component
+merge_request: 52717
+author:
+type: changed
diff --git a/changelogs/unreleased/remove-banzai-commit-full-title.yml b/changelogs/unreleased/remove-banzai-commit-full-title.yml
new file mode 100644
index 00000000000..2447d03eb30
--- /dev/null
+++ b/changelogs/unreleased/remove-banzai-commit-full-title.yml
@@ -0,0 +1,5 @@
+---
+title: Introduce a rendering limit for commit titles
+merge_request: 52904
+author:
+type: performance
diff --git a/config/feature_flags/development/vue_project_members_list.yml b/config/feature_flags/development/vue_project_members_list.yml
new file mode 100644
index 00000000000..37eb9167ddd
--- /dev/null
+++ b/config/feature_flags/development/vue_project_members_list.yml
@@ -0,0 +1,8 @@
+---
+name: vue_project_members_list
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52148
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/299954
+milestone: '13.9'
+type: development
+group: group::access
+default_enabled: false
diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md
index 0eede5f3ca5..882928a3628 100644
--- a/doc/administration/instance_limits.md
+++ b/doc/administration/instance_limits.md
@@ -127,6 +127,15 @@ It's possible that this limit will be changed to a lower number in the future.
- **Max size:** ~1 million characters / ~1 MB
+## Size of commit titles and descriptions
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/292039) in GitLab 13.9
+
+Commits with arbitrarily large messages may be pushed to GitLab, but when
+displaying commits, titles (the first line of the commit message) will be
+limited to 1KiB, and descriptions (the rest of the message) will be limited to
+1MiB.
+
## Number of issues in the milestone overview
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/39453) in GitLab 12.10.
diff --git a/doc/analytics/README.md b/doc/analytics/README.md
deleted file mode 100644
index 7c732f48ba8..00000000000
--- a/doc/analytics/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-redirect_to: '../user/group/index.md#user-contribution-analysis'
----
-
-This document was moved to [another location](../user/group/index.md#user-contribution-analysis)
-
-<!-- This redirect file can be deleted after February 1, 2021. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
diff --git a/doc/api/members.md b/doc/api/members.md
index 87d8fc64bad..bf2bf8c1220 100644
--- a/doc/api/members.md
+++ b/doc/api/members.md
@@ -284,6 +284,7 @@ Example response:
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon",
"web_url": "http://192.168.1.8:3000/root",
+ "last_activity_on": "2021-01-27"
},
{
"id": 2,
@@ -292,7 +293,8 @@ Example response:
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon",
"web_url": "http://192.168.1.8:3000/root",
- "email": "john@example.com"
+ "email": "john@example.com",
+ "last_activity_on": "2021-01-25"
},
{
"id": 3,
@@ -300,7 +302,8 @@ Example response:
"name": "Foo bar",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon",
- "web_url": "http://192.168.1.8:3000/root"
+ "web_url": "http://192.168.1.8:3000/root",
+ "last_activity_on": "2021-01-20"
}
]
```
diff --git a/doc/ci/build_artifacts/README.md b/doc/ci/build_artifacts/README.md
deleted file mode 100644
index 1d676a24b04..00000000000
--- a/doc/ci/build_artifacts/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-redirect_to: '../pipelines/job_artifacts.md'
----
-
-This document was moved to [pipelines/job_artifacts.md](../pipelines/job_artifacts.md).
-
-<!-- This redirect file can be deleted after February 1, 2021. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
diff --git a/doc/ci/permissions/README.md b/doc/ci/permissions/README.md
deleted file mode 100644
index 897d7b6ce51..00000000000
--- a/doc/ci/permissions/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-redirect_to: '../../user/permissions.md#gitlab-cicd-permissions'
----
-
-This document was moved to [user/permissions.md](../../user/permissions.md#gitlab-cicd-permissions).
-
-<!-- This redirect file can be deleted after February 1, 2021. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
diff --git a/doc/license/README.md b/doc/license/README.md
deleted file mode 100644
index f0ff27c315e..00000000000
--- a/doc/license/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-redirect_to: '../user/admin_area/license.md'
----
-
-This document was moved to [another location](../user/admin_area/license.md).
-
-<!-- This redirect file can be deleted after February 1, 2021. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md
deleted file mode 100644
index c0251229916..00000000000
--- a/doc/university/glossary/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-redirect_to: 'https://docs.gitlab.com'
----
-
-Visit our [documentation page](https://docs.gitlab.com) for information about GitLab.
diff --git a/doc/university/high-availability/aws/README.md b/doc/university/high-availability/aws/README.md
deleted file mode 100644
index cfaeea8f5c2..00000000000
--- a/doc/university/high-availability/aws/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-redirect_to: '../../../install/aws/index.md'
----
-
-This document was moved to [another location](../../../install/aws/index.md).
-
-<!-- This redirect file can be deleted after February 1, 2021. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
diff --git a/doc/university/process/README.md b/doc/university/process/README.md
deleted file mode 100644
index c0251229916..00000000000
--- a/doc/university/process/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-redirect_to: 'https://docs.gitlab.com'
----
-
-Visit our [documentation page](https://docs.gitlab.com) for information about GitLab.
diff --git a/doc/university/training/end-user/README.md b/doc/university/training/end-user/README.md
deleted file mode 100644
index c0251229916..00000000000
--- a/doc/university/training/end-user/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-redirect_to: 'https://docs.gitlab.com'
----
-
-Visit our [documentation page](https://docs.gitlab.com) for information about GitLab.
diff --git a/doc/user/project/merge_requests/code_quality.md b/doc/user/project/merge_requests/code_quality.md
index 51c2f803079..0fb53884ea2 100644
--- a/doc/user/project/merge_requests/code_quality.md
+++ b/doc/user/project/merge_requests/code_quality.md
@@ -411,7 +411,7 @@ plugins:
enabled: true
```
-This adds SonarJava to the `plugins:` section of the [default `.codeclimate.yml`](https://gitlab.com/gitlab-org/ci-cd/codequality/-/blob/master/codeclimate_defaults/.codeclimate.yml)
+This adds SonarJava to the `plugins:` section of the [default `.codeclimate.yml`](https://gitlab.com/gitlab-org/ci-cd/codequality/-/blob/master/codeclimate_defaults/.codeclimate.yml.template)
included in your project.
Changes to the `plugins:` section do not affect the `exclude_patterns` section of the
@@ -428,7 +428,7 @@ Here's [an example project](https://gitlab.com/jheimbuck_gl/jh_java_example_proj
A common issue is that the terms `Code Quality` (GitLab specific) and `Code Climate`
(Engine used by GitLab) are very similar. You must add a **`.codeclimate.yml`** file
to change the default configuration, **not** a `.codequality.yml` file. If you use
-the wrong filename, the [default `.codeclimate.yml`](https://gitlab.com/gitlab-org/ci-cd/codequality/-/blob/master/codeclimate_defaults/.codeclimate.yml)
+the wrong filename, the [default `.codeclimate.yml`](https://gitlab.com/gitlab-org/ci-cd/codequality/-/blob/master/codeclimate_defaults/.codeclimate.yml.template)
is still used.
### No Code Quality report is displayed in a Merge Request
@@ -444,15 +444,15 @@ This can be due to multiple reasons:
- The [`artifacts:expire_in`](../../../ci/yaml/README.md#artifactsexpire_in) CI/CD
setting can cause the Code Quality artifact(s) to expire faster than desired.
- If you use the [`REPORT_STDOUT` environment variable](https://gitlab.com/gitlab-org/ci-cd/codequality#environment-variables), no report file is generated and nothing displays in the merge request.
-- Large `codeclimate.json` files (esp. >10 MB) are [known to prevent the report from being displayed](https://gitlab.com/gitlab-org/gitlab/-/issues/2737).
+- Large `gl-code-quality-report.json` files (esp. >10 MB) are [known to prevent the report from being displayed](https://gitlab.com/gitlab-org/gitlab/-/issues/2737).
As a work-around, try removing [properties](https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#data-types)
that are [ignored by GitLab](#implementing-a-custom-tool). You can:
- Configure the Code Quality tool to not output those types.
- Use `sed`, `awk` or similar commands in the `.gitlab-ci.yml` script to
- edit the `codeclimate.json` before the job completes.
+ edit the `gl-code-quality-report.json` before the job completes.
### Only a single Code Quality report is displayed, but more are defined
GitLab only uses the Code Quality artifact from the latest created job (with the largest job ID).
If multiple jobs in a pipeline generate a code quality artifact, those of earlier jobs are ignored.
-To avoid confusion, configure only one job to generate a `codeclimate.json`.
+To avoid confusion, configure only one job to generate a `gl-code-quality-report.json`.
diff --git a/lib/api/group_labels.rb b/lib/api/group_labels.rb
index 7fbf4445116..bea538441ee 100644
--- a/lib/api/group_labels.rb
+++ b/lib/api/group_labels.rb
@@ -12,7 +12,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a group'
end
- resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ resource :groups, requirements: ::API::Labels::LABEL_ENDPOINT_REQUIREMENTS do
desc 'Get all labels of the group' do
detail 'This feature was added in GitLab 11.8'
success Entities::GroupLabel
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index c9f29865664..aa3746dae42 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -9,10 +9,14 @@ module API
feature_category :issue_tracking
+ LABEL_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
+ name: API::NO_SLASH_URL_PART_REGEX,
+ label_id: API::NO_SLASH_URL_PART_REGEX)
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ resource :projects, requirements: LABEL_ENDPOINT_REQUIREMENTS do
desc 'Get all labels of the project' do
success Entities::ProjectLabel
end
diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb
index 914bab52929..87dc1358a51 100644
--- a/lib/api/subscriptions.rb
+++ b/lib/api/subscriptions.rb
@@ -6,6 +6,9 @@ module API
before { authenticate! }
+ SUBSCRIBE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
+ subscribable_id: API::NO_SLASH_URL_PART_REGEX)
+
subscribables = [
{
type: 'merge_requests',
@@ -44,7 +47,7 @@ module API
requires :id, type: String, desc: "The #{source_type} ID"
requires :subscribable_id, type: String, desc: 'The ID of a resource'
end
- resource source_type.pluralize, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ resource source_type.pluralize, requirements: SUBSCRIBE_ENDPOINT_REQUIREMENTS do
desc 'Subscribe to a resource' do
success subscribable[:entity]
end
diff --git a/lib/banzai/filter/truncate_source_filter.rb b/lib/banzai/filter/truncate_source_filter.rb
index c903b83d868..44f88b253d9 100644
--- a/lib/banzai/filter/truncate_source_filter.rb
+++ b/lib/banzai/filter/truncate_source_filter.rb
@@ -6,7 +6,9 @@ module Banzai
def call
return text unless context.key?(:limit)
- text.truncate_bytes(context[:limit])
+ # Use three dots instead of the ellipsis Unicode character because
+ # some clients show the raw Unicode value in the merge commit.
+ text.truncate_bytes(context[:limit], omission: '...')
end
end
end
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 9c65dee1092..a20b802be58 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -14,7 +14,7 @@ module Gitlab
ALLOWED_KEYS = %i[tags script type image services start_in artifacts
cache dependencies before_script after_script
environment coverage retry parallel interruptible timeout
- resource_group release secrets].freeze
+ release secrets].freeze
REQUIRED_BY_NEEDS = %i[stage].freeze
@@ -30,7 +30,6 @@ module Gitlab
}
validates :dependencies, array_of_strings: true
- validates :resource_group, type: String
validates :allow_failure, hash_or_boolean: true
end
@@ -124,7 +123,7 @@ module Gitlab
attributes :script, :tags, :when, :dependencies,
:needs, :retry, :parallel, :start_in,
- :interruptible, :timeout, :resource_group,
+ :interruptible, :timeout,
:release, :allow_failure
def self.matching?(name, config)
@@ -174,7 +173,6 @@ module Gitlab
ignore: ignored?,
allow_failure_criteria: allow_failure_criteria,
needs: needs_defined? ? needs_value : nil,
- resource_group: resource_group,
scheduling_type: needs_defined? ? :dag : :stage
).compact
end
diff --git a/lib/gitlab/ci/config/entry/processable.rb b/lib/gitlab/ci/config/entry/processable.rb
index 5ef8cfbddb7..9584d19bdec 100644
--- a/lib/gitlab/ci/config/entry/processable.rb
+++ b/lib/gitlab/ci/config/entry/processable.rb
@@ -15,7 +15,7 @@ module Gitlab
include ::Gitlab::Config::Entry::Inheritable
PROCESSABLE_ALLOWED_KEYS = %i[extends stage only except rules variables
- inherit allow_failure when needs].freeze
+ inherit allow_failure when needs resource_group].freeze
included do
validations do
@@ -32,6 +32,7 @@ module Gitlab
with_options allow_nil: true do
validates :extends, array_of_strings_or_string: true
validates :rules, array_of_hashes: true
+ validates :resource_group, type: String
end
end
@@ -64,7 +65,7 @@ module Gitlab
inherit: false,
default: {}
- attributes :extends, :rules
+ attributes :extends, :rules, :resource_group
end
def compose!(deps = nil)
@@ -125,7 +126,8 @@ module Gitlab
rules: rules_value,
variables: root_and_job_variables_value,
only: only_value,
- except: except_value }.compact
+ except: except_value,
+ resource_group: resource_group }.compact
end
def root_and_job_variables_value
diff --git a/lib/gitlab/ci/pipeline/seed/build/resource_group.rb b/lib/gitlab/ci/pipeline/seed/build/resource_group.rb
index c0641d9ff0a..794bd06be25 100644
--- a/lib/gitlab/ci/pipeline/seed/build/resource_group.rb
+++ b/lib/gitlab/ci/pipeline/seed/build/resource_group.rb
@@ -8,17 +8,17 @@ module Gitlab
class ResourceGroup < Seed::Base
include Gitlab::Utils::StrongMemoize
- attr_reader :build, :resource_group_key
+ attr_reader :processable, :resource_group_key
- def initialize(build, resource_group_key)
- @build = build
+ def initialize(processable, resource_group_key)
+ @processable = processable
@resource_group_key = resource_group_key
end
def to_resource
return unless resource_group_key.present?
- resource_group = build.project.resource_groups
+ resource_group = processable.project.resource_groups
.safe_find_or_create_by(key: expanded_resource_group_key)
resource_group if resource_group.persisted?
@@ -28,7 +28,7 @@ module Gitlab
def expanded_resource_group_key
strong_memoize(:expanded_resource_group_key) do
- ExpandVariables.expand(resource_group_key, -> { build.simple_variables })
+ ExpandVariables.expand(resource_group_key, -> { processable.simple_variables })
end
end
end
diff --git a/lib/gitlab/search/query.rb b/lib/gitlab/search/query.rb
index 5b1f9400bc7..c0420126ada 100644
--- a/lib/gitlab/search/query.rb
+++ b/lib/gitlab/search/query.rb
@@ -5,6 +5,9 @@ module Gitlab
class Query < SimpleDelegator
include EncodingHelper
+ QUOTES_REGEXP = %r{\A"|"\Z}.freeze
+ TOKEN_WITH_QUOTES_REGEXP = %r{\s(?=(?:[^"]|"[^"]*")*$)}.freeze
+
def initialize(query, filter_opts = {}, &block)
@raw_query = query.dup
@filters = []
@@ -35,22 +38,24 @@ module Gitlab
def extract_filters
fragments = []
+ query_tokens = parse_raw_query
filters = @filters.each_with_object([]) do |filter, parsed_filters|
- match = @raw_query.split.find { |part| part =~ /\A-?#{filter[:name]}:/ }
+ match = query_tokens.find { |part| part =~ /\A-?#{filter[:name]}:/ }
+
next unless match
input = match.split(':')[1..-1].join
next if input.empty?
filter[:negated] = match.start_with?("-")
- filter[:value] = parse_filter(filter, input)
+ filter[:value] = parse_filter(filter, input.gsub(QUOTES_REGEXP, ''))
filter[:regex_value] = Regexp.escape(filter[:value]).gsub('\*', '.*?')
fragments << match
parsed_filters << filter
end
- query = (@raw_query.split - fragments).join(' ')
+ query = (query_tokens - fragments).join(' ')
query = '*' if query.empty?
[query, filters]
@@ -61,6 +66,13 @@ module Gitlab
@filter_options[:encode_binary] ? encode_binary(result) : result
end
+
+ def parse_raw_query
+ # Positive lookahead for any non-quote char or even number of quotes
+ # for example '"search term" path:"foo bar.txt"' would break into
+ # ["search term", "path:\"foo bar.txt\""]
+ @raw_query.split(TOKEN_WITH_QUOTES_REGEXP).reject(&:empty?)
+ end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 4d10c490aee..3a7b465db8b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3281,6 +3281,9 @@ msgstr ""
msgid "An error occurred while loading the file. Please try again later."
msgstr ""
+msgid "An error occurred while loading the members, please try again."
+msgstr ""
+
msgid "An error occurred while loading the merge request changes."
msgstr ""
@@ -18101,6 +18104,9 @@ msgstr ""
msgid "Members|Role updated successfully."
msgstr ""
+msgid "Members|Search groups"
+msgstr ""
+
msgid "Members|Search invited"
msgstr ""
diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh
index 23e7cb6c455..d6dde5744d7 100755
--- a/scripts/lint-doc.sh
+++ b/scripts/lint-doc.sh
@@ -57,7 +57,7 @@ fi
# Do not use 'README.md', instead use 'index.md'
# Number of 'README.md's as of 2020-10-13
-NUMBER_READMES=36
+NUMBER_READMES=28
FIND_READMES=$(find doc/ -name "README.md" | wc -l)
echo '=> Checking for new README.md files...'
echo
diff --git a/spec/factories/ci/resource.rb b/spec/factories/ci/resource.rb
index 515329506e5..dec26013a25 100644
--- a/spec/factories/ci/resource.rb
+++ b/spec/factories/ci/resource.rb
@@ -5,7 +5,7 @@ FactoryBot.define do
resource_group factory: :ci_resource_group
trait(:retained) do
- build factory: :ci_build
+ processable factory: :ci_build
end
end
end
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index 46f2b2c4910..74e6aac8845 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -10,6 +10,8 @@ RSpec.describe "Admin::Projects" do
let(:current_user) { create(:admin) }
before do
+ stub_feature_flags(vue_project_members_list: false)
+
sign_in(current_user)
gitlab_enable_admin_mode_sign_in(current_user)
end
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 2af5b787a78..cf7554b3646 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -411,10 +411,10 @@ RSpec.describe 'Issue Boards', :js do
wait_for_requests
page.within('.subscriptions') do
- find('.js-issuable-subscribe-button button:not(.is-checked)').click
+ find('[data-testid="subscription-toggle"] button:not(.is-checked)').click
wait_for_requests
- expect(page).to have_css('.js-issuable-subscribe-button button.is-checked')
+ expect(page).to have_css('[data-testid="subscription-toggle"] button.is-checked')
end
end
@@ -427,10 +427,10 @@ RSpec.describe 'Issue Boards', :js do
wait_for_requests
page.within('.subscriptions') do
- find('.js-issuable-subscribe-button button.is-checked').click
+ find('[data-testid="subscription-toggle"] button.is-checked').click
wait_for_requests
- expect(page).to have_css('.js-issuable-subscribe-button button:not(.is-checked)')
+ expect(page).to have_css('[data-testid="subscription-toggle"] button:not(.is-checked)')
end
end
end
diff --git a/spec/features/issues/user_toggles_subscription_spec.rb b/spec/features/issues/user_toggles_subscription_spec.rb
index 971c8a3b431..d91c187c840 100644
--- a/spec/features/issues/user_toggles_subscription_spec.rb
+++ b/spec/features/issues/user_toggles_subscription_spec.rb
@@ -15,13 +15,13 @@ RSpec.describe "User toggles subscription", :js do
end
it "unsubscribes from issue" do
- subscription_button = find(".js-issuable-subscribe-button")
+ subscription_button = find('[data-testid="subscription-toggle"]')
# Check we're subscribed.
expect(subscription_button).to have_css("button.is-checked")
# Toggle subscription.
- find(".js-issuable-subscribe-button button").click
+ find('[data-testid="subscription-toggle"]').click
wait_for_requests
# Check we're unsubscribed.
@@ -33,7 +33,7 @@ RSpec.describe "User toggles subscription", :js do
it 'is disabled' do
expect(page).to have_content('Notifications have been disabled by the project or group owner')
- expect(page).not_to have_selector('.js-issuable-subscribe-button')
+ expect(page).not_to have_selector('[data-testid="subscription-toggle"]')
end
end
end
diff --git a/spec/features/merge_request/user_manages_subscription_spec.rb b/spec/features/merge_request/user_manages_subscription_spec.rb
index 9ed5b67fa0e..3cdb22000f6 100644
--- a/spec/features/merge_request/user_manages_subscription_spec.rb
+++ b/spec/features/merge_request/user_manages_subscription_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe 'User manages subscription', :js do
end
it 'toggles subscription' do
- page.within('.js-issuable-subscribe-button') do
+ page.within('[data-testid="subscription-toggle"]') do
wait_for_requests
expect(page).to have_css 'button:not(.is-checked)'
diff --git a/spec/features/projects/members/anonymous_user_sees_members_spec.rb b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
index 3b0f00c5494..c0849cc7330 100644
--- a/spec/features/projects/members/anonymous_user_sees_members_spec.rb
+++ b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
@@ -8,6 +8,8 @@ RSpec.describe 'Projects > Members > Anonymous user sees members' do
let(:project) { create(:project, :public) }
before do
+ stub_feature_flags(vue_project_members_list: false)
+
project.add_maintainer(user)
create(:project_group_link, project: project, group: group)
end
diff --git a/spec/features/projects/members/group_members_spec.rb b/spec/features/projects/members/group_members_spec.rb
index aa15f04bf24..b6a5fbf5584 100644
--- a/spec/features/projects/members/group_members_spec.rb
+++ b/spec/features/projects/members/group_members_spec.rb
@@ -13,6 +13,8 @@ RSpec.describe 'Projects members', :js do
let(:group_requester) { create(:user) }
before do
+ stub_feature_flags(vue_project_members_list: false)
+
project.add_developer(developer)
group.add_owner(user)
sign_in(user)
diff --git a/spec/features/projects/members/groups_with_access_list_spec.rb b/spec/features/projects/members/groups_with_access_list_spec.rb
index 686d86b1783..de27692b535 100644
--- a/spec/features/projects/members/groups_with_access_list_spec.rb
+++ b/spec/features/projects/members/groups_with_access_list_spec.rb
@@ -11,6 +11,8 @@ RSpec.describe 'Projects > Members > Groups with access list', :js do
let!(:group_link) { create(:project_group_link, project: project, group: group, **additional_link_attrs) }
before do
+ stub_feature_flags(vue_project_members_list: false)
+
travel_to Time.now.utc.beginning_of_day
project.add_maintainer(user)
diff --git a/spec/features/projects/members/invite_group_spec.rb b/spec/features/projects/members/invite_group_spec.rb
index 07620929418..8e956c4a7cd 100644
--- a/spec/features/projects/members/invite_group_spec.rb
+++ b/spec/features/projects/members/invite_group_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe 'Project > Members > Invite group', :js do
before do
stub_feature_flags(invite_members_group_modal: false)
+ stub_feature_flags(vue_project_members_list: false)
end
describe 'Share with group lock' do
diff --git a/spec/features/projects/members/list_spec.rb b/spec/features/projects/members/list_spec.rb
index 62115f2dce6..a62ccfab244 100644
--- a/spec/features/projects/members/list_spec.rb
+++ b/spec/features/projects/members/list_spec.rb
@@ -13,10 +13,18 @@ RSpec.describe 'Project members list' do
before do
stub_feature_flags(invite_members_group_modal: false)
+ stub_feature_flags(vue_project_members_list: false)
+
sign_in(user1)
group.add_owner(user1)
end
+ it 'pushes `vue_project_members_list` feature flag to the frontend' do
+ visit_members_page
+
+ expect(page).to have_pushed_frontend_feature_flags(vueProjectMembersList: false)
+ end
+
it 'show members from project and group' do
project.add_developer(user2)
diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
index e995af0d670..e7970b28e37 100644
--- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
+++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
@@ -11,6 +11,8 @@ RSpec.describe 'Projects > Members > Maintainer adds member with expiration date
let(:new_member) { create(:user) }
before do
+ stub_feature_flags(vue_project_members_list: false)
+
travel_to Time.now.utc.beginning_of_day
project.add_maintainer(maintainer)
diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb
index be27cbc0d66..9b9f1f26d66 100644
--- a/spec/features/projects/members/sorting_spec.rb
+++ b/spec/features/projects/members/sorting_spec.rb
@@ -8,6 +8,8 @@ RSpec.describe 'Projects > Members > Sorting' do
let(:project) { create(:project, namespace: maintainer.namespace, creator: maintainer) }
before do
+ stub_feature_flags(vue_project_members_list: false)
+
create(:project_member, :developer, user: developer, project: project, created_at: 3.days.ago)
sign_in(maintainer)
diff --git a/spec/features/projects/members/tabs_spec.rb b/spec/features/projects/members/tabs_spec.rb
index bdcf02c82a4..7c72ba31029 100644
--- a/spec/features/projects/members/tabs_spec.rb
+++ b/spec/features/projects/members/tabs_spec.rb
@@ -20,6 +20,7 @@ RSpec.describe 'Projects > Members > Tabs' do
end
before do
+ stub_feature_flags(vue_project_members_list: false)
allow(Kaminari.config).to receive(:default_per_page).and_return(1)
sign_in(user)
diff --git a/spec/features/projects/settings/user_manages_project_members_spec.rb b/spec/features/projects/settings/user_manages_project_members_spec.rb
index 58855af4a4a..a4abdf9f571 100644
--- a/spec/features/projects/settings/user_manages_project_members_spec.rb
+++ b/spec/features/projects/settings/user_manages_project_members_spec.rb
@@ -11,6 +11,8 @@ RSpec.describe 'Projects > Settings > User manages project members' do
let(:user_mike) { create(:user, name: 'Mike') }
before do
+ stub_feature_flags(vue_project_members_list: false)
+
project.add_maintainer(user)
project.add_developer(user_dmitriy)
sign_in(user)
diff --git a/spec/frontend/projects/members/utils_spec.js b/spec/frontend/projects/members/utils_spec.js
new file mode 100644
index 00000000000..813e8455e85
--- /dev/null
+++ b/spec/frontend/projects/members/utils_spec.js
@@ -0,0 +1,14 @@
+import { projectMemberRequestFormatter } from '~/projects/members/utils';
+
+describe('project member utils', () => {
+ describe('projectMemberRequestFormatter', () => {
+ it('returns expected format', () => {
+ expect(
+ projectMemberRequestFormatter({
+ accessLevel: 50,
+ expires_at: '2020-10-16',
+ }),
+ ).toEqual({ project_member: { access_level: 50, expires_at: '2020-10-16' } });
+ });
+ });
+});
diff --git a/spec/frontend/sidebar/subscriptions_spec.js b/spec/frontend/sidebar/subscriptions_spec.js
index 043ffd972da..e7ae59e26cf 100644
--- a/spec/frontend/sidebar/subscriptions_spec.js
+++ b/spec/frontend/sidebar/subscriptions_spec.js
@@ -1,17 +1,20 @@
+import { GlToggle } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import Subscriptions from '~/sidebar/components/subscriptions/subscriptions.vue';
import eventHub from '~/sidebar/event_hub';
-import ToggleButton from '~/vue_shared/components/toggle_button.vue';
describe('Subscriptions', () => {
let wrapper;
- const findToggleButton = () => wrapper.find(ToggleButton);
+ const findToggleButton = () => wrapper.findComponent(GlToggle);
const mountComponent = (propsData) =>
- shallowMount(Subscriptions, {
- propsData,
- });
+ extendedWrapper(
+ shallowMount(Subscriptions, {
+ propsData,
+ }),
+ );
afterEach(() => {
wrapper.destroy();
@@ -24,7 +27,7 @@ describe('Subscriptions', () => {
subscribed: undefined,
});
- expect(findToggleButton().attributes('isloading')).toBe('true');
+ expect(findToggleButton().props('isLoading')).toBe(true);
});
it('is toggled "off" when currently not subscribed', () => {
@@ -32,7 +35,7 @@ describe('Subscriptions', () => {
subscribed: false,
});
- expect(findToggleButton().attributes('value')).toBeFalsy();
+ expect(findToggleButton().props('value')).toBe(false);
});
it('is toggled "on" when currently subscribed', () => {
@@ -40,7 +43,7 @@ describe('Subscriptions', () => {
subscribed: true,
});
- expect(findToggleButton().attributes('value')).toBe('true');
+ expect(findToggleButton().props('value')).toBe(true);
});
it('toggleSubscription method emits `toggleSubscription` event on eventHub and Component', () => {
@@ -93,14 +96,16 @@ describe('Subscriptions', () => {
});
it('sets the correct display text', () => {
- expect(wrapper.find('.issuable-header-text').text()).toContain(subscribeDisabledDescription);
+ expect(wrapper.findByTestId('subscription-title').text()).toContain(
+ subscribeDisabledDescription,
+ );
expect(wrapper.find({ ref: 'tooltip' }).attributes('title')).toBe(
subscribeDisabledDescription,
);
});
it('does not render the toggle button', () => {
- expect(wrapper.find('.js-issuable-subscribe-button').exists()).toBe(false);
+ expect(findToggleButton().exists()).toBe(false);
});
});
});
diff --git a/spec/lib/banzai/filter/truncate_source_filter_spec.rb b/spec/lib/banzai/filter/truncate_source_filter_spec.rb
index b0c6d91daa8..d5eb8b738b1 100644
--- a/spec/lib/banzai/filter/truncate_source_filter_spec.rb
+++ b/spec/lib/banzai/filter/truncate_source_filter_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Banzai::Filter::TruncateSourceFilter do
it 'truncates UTF-8 text by bytes, on a character boundary' do
utf8_text = '日本語の文字が大きい'
- truncated = '日…'
+ truncated = '日...'
expect(filter(utf8_text, limit: truncated.bytesize)).to eq(truncated)
expect(filter(utf8_text, limit: utf8_text.bytesize)).to eq(utf8_text)
diff --git a/spec/lib/banzai/pipeline/pre_process_pipeline_spec.rb b/spec/lib/banzai/pipeline/pre_process_pipeline_spec.rb
index f0498f41b61..c628d8d5b41 100644
--- a/spec/lib/banzai/pipeline/pre_process_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/pre_process_pipeline_spec.rb
@@ -30,6 +30,6 @@ RSpec.describe Banzai::Pipeline::PreProcessPipeline do
result = described_class.call(text, limit: 12)
- expect(result[:output]).to eq('foo foo f…')
+ expect(result[:output]).to eq('foo foo f...')
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
index aadf94365c6..04e80450263 100644
--- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
@@ -73,6 +73,15 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
end
end
+ context 'when resource_group key is not a string' do
+ let(:config) { { resource_group: 123 } }
+
+ it 'returns error about wrong value type' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include "job resource group should be a string"
+ end
+ end
+
context 'when it uses both "when:" and "rules:"' do
let(:config) do
{
@@ -340,6 +349,26 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
end
end
+ context 'with resource group' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:resource_group, :result) do
+ 'iOS' | 'iOS'
+ 'review/$CI_COMMIT_REF_NAME' | 'review/$CI_COMMIT_REF_NAME'
+ nil | nil
+ end
+
+ with_them do
+ let(:config) { { script: 'ls', resource_group: resource_group }.compact }
+
+ it do
+ entry.compose!(deps)
+
+ expect(entry.resource_group).to eq(result)
+ end
+ end
+ end
+
context 'with inheritance' do
context 'of variables' do
let(:config) do
diff --git a/spec/lib/gitlab/file_finder_spec.rb b/spec/lib/gitlab/file_finder_spec.rb
index 8d6df62b3f6..0b5303f22b4 100644
--- a/spec/lib/gitlab/file_finder_spec.rb
+++ b/spec/lib/gitlab/file_finder_spec.rb
@@ -53,6 +53,14 @@ RSpec.describe Gitlab::FileFinder do
end
end
+ context 'with white space in the path' do
+ it 'filters by path correctly' do
+ results = subject.find('directory path:"with space/README.md"')
+
+ expect(results.count).to eq(1)
+ end
+ end
+
it 'does not cause N+1 query' do
expect(Gitlab::GitalyClient).to receive(:call).at_most(10).times.and_call_original
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
index 4c57665b41f..625dcf11546 100644
--- a/spec/lib/gitlab/repository_cache_adapter_spec.rb
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -292,12 +292,11 @@ RSpec.describe Gitlab::RepositoryCacheAdapter do
describe '#expire_method_caches' do
it 'expires the caches of the given methods' do
- expect(cache).to receive(:expire).with(:rendered_readme)
expect(cache).to receive(:expire).with(:branch_names)
- expect(redis_set_cache).to receive(:expire).with(:rendered_readme, :branch_names)
- expect(redis_hash_cache).to receive(:delete).with(:rendered_readme, :branch_names)
+ expect(redis_set_cache).to receive(:expire).with(:branch_names)
+ expect(redis_hash_cache).to receive(:delete).with(:branch_names)
- repository.expire_method_caches(%i(rendered_readme branch_names))
+ repository.expire_method_caches(%i(branch_names))
end
it 'does not expire caches for non-existent methods' do
diff --git a/spec/lib/gitlab/search/query_spec.rb b/spec/lib/gitlab/search/query_spec.rb
index dd2f23a7e47..234b683ba1f 100644
--- a/spec/lib/gitlab/search/query_spec.rb
+++ b/spec/lib/gitlab/search/query_spec.rb
@@ -46,4 +46,22 @@ RSpec.describe Gitlab::Search::Query do
expect(subject.filters).to all(include(negated: true))
end
end
+
+ context 'with filter value in quotes' do
+ let(:query) { '"foo bar" name:"my test script.txt"' }
+
+ it 'does not break the filter value in quotes' do
+ expect(subject.term).to eq('"foo bar"')
+ expect(subject.filters[0]).to include(name: :name, negated: false, value: "MY TEST SCRIPT.TXT")
+ end
+ end
+
+ context 'with extra white spaces between the query words' do
+ let(:query) { ' foo = bar name:"my test.txt"' }
+
+ it 'removes the extra whitespace between tokens' do
+ expect(subject.term).to eq('foo = bar')
+ expect(subject.filters[0]).to include(name: :name, negated: false, value: "MY TEST.TXT")
+ end
+ end
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index e3ee533688a..3d06fd6db4f 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1185,60 +1185,6 @@ RSpec.describe Ci::Build do
end
end
- describe 'state transition with resource group' do
- let(:resource_group) { create(:ci_resource_group, project: project) }
-
- context 'when build status is created' do
- let(:build) { create(:ci_build, :created, project: project, resource_group: resource_group) }
-
- it 'is waiting for resource when build is enqueued' do
- expect(Ci::ResourceGroups::AssignResourceFromResourceGroupWorker).to receive(:perform_async).with(resource_group.id)
-
- expect { build.enqueue! }.to change { build.status }.from('created').to('waiting_for_resource')
-
- expect(build.waiting_for_resource_at).not_to be_nil
- end
-
- context 'when build is waiting for resource' do
- before do
- build.update_column(:status, 'waiting_for_resource')
- end
-
- it 'is enqueued when build requests resource' do
- expect { build.enqueue_waiting_for_resource! }.to change { build.status }.from('waiting_for_resource').to('pending')
- end
-
- it 'releases a resource when build finished' do
- expect(build.resource_group).to receive(:release_resource_from).with(build).and_call_original
- expect(Ci::ResourceGroups::AssignResourceFromResourceGroupWorker).to receive(:perform_async).with(build.resource_group_id)
-
- build.enqueue_waiting_for_resource!
- build.success!
- end
-
- context 'when build has prerequisites' do
- before do
- allow(build).to receive(:any_unmet_prerequisites?) { true }
- end
-
- it 'is preparing when build is enqueued' do
- expect { build.enqueue_waiting_for_resource! }.to change { build.status }.from('waiting_for_resource').to('preparing')
- end
- end
-
- context 'when there are no available resources' do
- before do
- resource_group.assign_resource_to(create(:ci_build))
- end
-
- it 'stays as waiting for resource when build requests resource' do
- expect { build.enqueue_waiting_for_resource }.not_to change { build.status }
- end
- end
- end
- end
- end
-
describe '#on_stop' do
subject { build.on_stop }
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 3b155517a31..0fb47b7195c 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -2321,7 +2321,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
context 'on waiting for resource' do
before do
- allow(build).to receive(:requires_resource?) { true }
+ allow(build).to receive(:with_resource_group?) { true }
allow(Ci::ResourceGroups::AssignResourceFromResourceGroupWorker).to receive(:perform_async)
build.enqueue
diff --git a/spec/models/ci/processable_spec.rb b/spec/models/ci/processable_spec.rb
index 35764e2bbbe..6290f4aef16 100644
--- a/spec/models/ci/processable_spec.rb
+++ b/spec/models/ci/processable_spec.rb
@@ -122,4 +122,58 @@ RSpec.describe Ci::Processable do
it { is_expected.to be_empty }
end
end
+
+ describe 'state transition with resource group' do
+ let(:resource_group) { create(:ci_resource_group, project: project) }
+
+ context 'when build status is created' do
+ let(:build) { create(:ci_build, :created, project: project, resource_group: resource_group) }
+
+ it 'is waiting for resource when build is enqueued' do
+ expect(Ci::ResourceGroups::AssignResourceFromResourceGroupWorker).to receive(:perform_async).with(resource_group.id)
+
+ expect { build.enqueue! }.to change { build.status }.from('created').to('waiting_for_resource')
+
+ expect(build.waiting_for_resource_at).not_to be_nil
+ end
+
+ context 'when build is waiting for resource' do
+ before do
+ build.update_column(:status, 'waiting_for_resource')
+ end
+
+ it 'is enqueued when build requests resource' do
+ expect { build.enqueue_waiting_for_resource! }.to change { build.status }.from('waiting_for_resource').to('pending')
+ end
+
+ it 'releases a resource when build finished' do
+ expect(build.resource_group).to receive(:release_resource_from).with(build).and_call_original
+ expect(Ci::ResourceGroups::AssignResourceFromResourceGroupWorker).to receive(:perform_async).with(build.resource_group_id)
+
+ build.enqueue_waiting_for_resource!
+ build.success!
+ end
+
+ context 'when build has prerequisites' do
+ before do
+ allow(build).to receive(:any_unmet_prerequisites?) { true }
+ end
+
+ it 'is preparing when build is enqueued' do
+ expect { build.enqueue_waiting_for_resource! }.to change { build.status }.from('waiting_for_resource').to('preparing')
+ end
+ end
+
+ context 'when there are no available resources' do
+ before do
+ resource_group.assign_resource_to(create(:ci_build))
+ end
+
+ it 'stays as waiting for resource when build requests resource' do
+ expect { build.enqueue_waiting_for_resource }.not_to change { build.status }
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/ci/resource_group_spec.rb b/spec/models/ci/resource_group_spec.rb
index 9f72d1a82e5..50a786419f2 100644
--- a/spec/models/ci/resource_group_spec.rb
+++ b/spec/models/ci/resource_group_spec.rb
@@ -32,12 +32,12 @@ RSpec.describe Ci::ResourceGroup do
let(:build) { create(:ci_build) }
let(:resource_group) { create(:ci_resource_group) }
- it 'retains resource for the build' do
- expect(resource_group.resources.first.build).to be_nil
+ it 'retains resource for the processable' do
+ expect(resource_group.resources.first.processable).to be_nil
is_expected.to eq(true)
- expect(resource_group.resources.first.build).to eq(build)
+ expect(resource_group.resources.first.processable).to eq(build)
end
context 'when there are no free resources' do
@@ -51,7 +51,7 @@ RSpec.describe Ci::ResourceGroup do
end
context 'when the build has already retained a resource' do
- let!(:another_resource) { create(:ci_resource, resource_group: resource_group, build: build) }
+ let!(:another_resource) { create(:ci_resource, resource_group: resource_group, processable: build) }
it 'fails to retain resource' do
expect { subject }.to raise_error(ActiveRecord::RecordNotUnique)
@@ -71,11 +71,11 @@ RSpec.describe Ci::ResourceGroup do
end
it 'releases resource from the build' do
- expect(resource_group.resources.first.build).to eq(build)
+ expect(resource_group.resources.first.processable).to eq(build)
is_expected.to eq(true)
- expect(resource_group.resources.first.build).to be_nil
+ expect(resource_group.resources.first.processable).to be_nil
end
end
diff --git a/spec/models/ci/resource_spec.rb b/spec/models/ci/resource_spec.rb
index 90f26ef2b31..5574f6f82b2 100644
--- a/spec/models/ci/resource_spec.rb
+++ b/spec/models/ci/resource_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe Ci::Resource do
subject { described_class.retained_by(build) }
let(:build) { create(:ci_build) }
- let!(:resource) { create(:ci_resource, build: build) }
+ let!(:resource) { create(:ci_resource, processable: build) }
it 'returns retained resources' do
is_expected.to eq([resource])
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index acbabee9383..a5f02b61132 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -400,6 +400,19 @@ eos
allow(commit).to receive(:safe_message).and_return(message + "\n" + message)
expect(commit.full_title).to eq(message)
end
+
+ it 'truncates html representation if more than 1KiB' do
+ # Commit title is over 2KiB on a single line
+ huge_commit_title = ('panic ' * 350) + 'trailing text'
+
+ allow(commit).to receive(:safe_message).and_return(huge_commit_title)
+
+ commit.refresh_markdown_cache
+ full_title_html = commit.full_title_html
+
+ expect(full_title_html.bytesize).to be < 2.kilobytes
+ expect(full_title_html).not_to include('trailing text')
+ end
end
describe 'description' do
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 532f68c2f18..52f6b4f2586 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -725,22 +725,6 @@ RSpec.describe CommitStatus do
let(:commit_status) { create(:commit_status) }
it { is_expected.to eq(true) }
-
- context 'when build requires a resource' do
- before do
- allow(commit_status).to receive(:requires_resource?) { true }
- end
-
- it { is_expected.to eq(false) }
- end
-
- context 'when build has a prerequisite' do
- before do
- allow(commit_status).to receive(:any_unmet_prerequisites?) { true }
- end
-
- it { is_expected.to eq(false) }
- end
end
describe '#enqueue' do
@@ -748,7 +732,6 @@ RSpec.describe CommitStatus do
before do
allow(Time).to receive(:now).and_return(current_time)
- expect(commit_status.any_unmet_prerequisites?).to eq false
end
shared_examples 'commit status enqueued' do
diff --git a/spec/models/readme_blob_spec.rb b/spec/models/readme_blob_spec.rb
deleted file mode 100644
index 95622d55254..00000000000
--- a/spec/models/readme_blob_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe ReadmeBlob do
- include FakeBlobHelpers
-
- describe 'policy' do
- let(:project) { build(:project, :repository) }
-
- subject { described_class.new(fake_blob(path: 'README.md'), project.repository) }
-
- it 'works with policy' do
- expect(Ability.allowed?(project.creator, :read_blob, subject)).to be_truthy
- end
- end
-end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index eb84623fb35..c339046d4c2 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -483,12 +483,6 @@ RSpec.describe Repository do
it { is_expected.to be_an_instance_of(::Blob) }
end
- context 'readme blob on HEAD' do
- subject { repository.blob_at(repository.head_commit.sha, 'README.md') }
-
- it { is_expected.to be_an_instance_of(::ReadmeBlob) }
- end
-
context 'readme blob not on HEAD' do
subject { repository.blob_at(repository.find_branch('feature').target, 'README.md') }
@@ -1938,7 +1932,6 @@ RSpec.describe Repository do
expect(repository).to receive(:expire_method_caches).with([
:size,
:commit_count,
- :rendered_readme,
:readme_path,
:contribution_guide,
:changelog,
@@ -2314,14 +2307,6 @@ RSpec.describe Repository do
expect(repository.readme).to be_nil
end
end
-
- context 'when a README exists' do
- let(:project) { create(:project, :repository) }
-
- it 'returns the README' do
- expect(repository.readme).to be_an_instance_of(ReadmeBlob)
- end
- end
end
end
@@ -2527,9 +2512,8 @@ RSpec.describe Repository do
describe '#refresh_method_caches' do
it 'refreshes the caches of the given types' do
expect(repository).to receive(:expire_method_caches)
- .with(%i(rendered_readme readme_path license_blob license_key license))
+ .with(%i(readme_path license_blob license_key license))
- expect(repository).to receive(:rendered_readme)
expect(repository).to receive(:readme_path)
expect(repository).to receive(:license_blob)
expect(repository).to receive(:license_key)
diff --git a/spec/requests/api/group_labels_spec.rb b/spec/requests/api/group_labels_spec.rb
index 72621e2ce5e..c677e68b285 100644
--- a/spec/requests/api/group_labels_spec.rb
+++ b/spec/requests/api/group_labels_spec.rb
@@ -3,13 +3,19 @@
require 'spec_helper'
RSpec.describe API::GroupLabels do
+ let_it_be(:valid_group_label_title_1) { 'Label foo & bar:subgroup::v.1' }
+ let_it_be(:valid_group_label_title_1_esc) { ERB::Util.url_encode(valid_group_label_title_1) }
+ let_it_be(:valid_group_label_title_2) { 'Bar & foo:subgroup::v.2' }
+ let_it_be(:valid_subgroup_label_title_1) { 'Support label foobar:sub::v.1' }
+ let_it_be(:valid_new_label_title) { 'New & foo:feature::v.3' }
+
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
let!(:group_member) { create(:group_member, group: group, user: user) }
- let!(:group_label1) { create(:group_label, title: 'feature-label', group: group) }
- let!(:group_label2) { create(:group_label, title: 'bug', group: group) }
- let!(:subgroup_label) { create(:group_label, title: 'support-label', group: subgroup) }
+ let!(:group_label1) { create(:group_label, title: valid_group_label_title_1, group: group) }
+ let!(:group_label2) { create(:group_label, title: valid_group_label_title_2, group: group) }
+ let!(:subgroup_label) { create(:group_label, title: valid_subgroup_label_title_1, group: subgroup) }
describe 'GET :id/labels' do
context 'get current group labels' do
@@ -104,7 +110,7 @@ RSpec.describe API::GroupLabels do
describe 'GET :id/labels/:label_id' do
it 'returns a single label for the group' do
- get api("/groups/#{group.id}/labels/#{group_label1.name}", user)
+ get api("/groups/#{group.id}/labels/#{valid_group_label_title_1_esc}", user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['name']).to eq(group_label1.name)
@@ -117,13 +123,13 @@ RSpec.describe API::GroupLabels do
it 'returns created label when all params are given' do
post api("/groups/#{group.id}/labels", user),
params: {
- name: 'Foo',
+ name: valid_new_label_title,
color: '#FFAABB',
description: 'test'
}
expect(response).to have_gitlab_http_status(:created)
- expect(json_response['name']).to eq('Foo')
+ expect(json_response['name']).to eq(valid_new_label_title)
expect(json_response['color']).to eq('#FFAABB')
expect(json_response['description']).to eq('test')
end
@@ -131,12 +137,12 @@ RSpec.describe API::GroupLabels do
it 'returns created label when only required params are given' do
post api("/groups/#{group.id}/labels", user),
params: {
- name: 'Foo & Bar',
+ name: valid_new_label_title,
color: '#FFAABB'
}
expect(response).to have_gitlab_http_status(:created)
- expect(json_response['name']).to eq('Foo & Bar')
+ expect(json_response['name']).to eq(valid_new_label_title)
expect(json_response['color']).to eq('#FFAABB')
expect(json_response['description']).to be_nil
end
@@ -204,7 +210,7 @@ RSpec.describe API::GroupLabels do
describe 'DELETE /groups/:id/labels/:label_id' do
it 'returns 204 for existing label' do
- delete api("/groups/#{group.id}/labels/#{group_label1.name}", user)
+ delete api("/groups/#{group.id}/labels/#{valid_group_label_title_1_esc}", user)
expect(response).to have_gitlab_http_status(:no_content)
end
@@ -228,7 +234,7 @@ RSpec.describe API::GroupLabels do
end
it_behaves_like '412 response' do
- let(:request) { api("/groups/#{group.id}/labels/#{group_label1.name}", user) }
+ let(:request) { api("/groups/#{group.id}/labels/#{valid_group_label_title_1_esc}", user) }
end
end
@@ -237,13 +243,13 @@ RSpec.describe API::GroupLabels do
put api("/groups/#{group.id}/labels", user),
params: {
name: group_label1.name,
- new_name: 'New Label',
+ new_name: valid_new_label_title,
color: '#FFFFFF',
description: 'test'
}
expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['name']).to eq('New Label')
+ expect(json_response['name']).to eq(valid_new_label_title)
expect(json_response['color']).to eq('#FFFFFF')
expect(json_response['description']).to eq('test')
end
@@ -255,11 +261,11 @@ RSpec.describe API::GroupLabels do
put api("/groups/#{subgroup.id}/labels", user),
params: {
name: subgroup_label.name,
- new_name: 'New Label'
+ new_name: valid_new_label_title
}
expect(response).to have_gitlab_http_status(:ok)
- expect(subgroup.labels[0].name).to eq('New Label')
+ expect(subgroup.labels[0].name).to eq(valid_new_label_title)
expect(group_label1.name).to eq(group_label1.title)
end
@@ -267,7 +273,7 @@ RSpec.describe API::GroupLabels do
put api("/groups/#{group.id}/labels", user),
params: {
name: 'not_exists',
- new_name: 'label3'
+ new_name: valid_new_label_title
}
expect(response).to have_gitlab_http_status(:not_found)
@@ -291,15 +297,15 @@ RSpec.describe API::GroupLabels do
describe 'PUT /groups/:id/labels/:label_id' do
it 'returns 200 if name and colors and description are changed' do
- put api("/groups/#{group.id}/labels/#{group_label1.name}", user),
+ put api("/groups/#{group.id}/labels/#{valid_group_label_title_1_esc}", user),
params: {
- new_name: 'New Label',
+ new_name: valid_new_label_title,
color: '#FFFFFF',
description: 'test'
}
expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['name']).to eq('New Label')
+ expect(json_response['name']).to eq(valid_new_label_title)
expect(json_response['color']).to eq('#FFFFFF')
expect(json_response['description']).to eq('test')
end
@@ -310,25 +316,25 @@ RSpec.describe API::GroupLabels do
put api("/groups/#{subgroup.id}/labels/#{subgroup_label.name}", user),
params: {
- new_name: 'New Label'
+ new_name: valid_new_label_title
}
expect(response).to have_gitlab_http_status(:ok)
- expect(subgroup.labels[0].name).to eq('New Label')
+ expect(subgroup.labels[0].name).to eq(valid_new_label_title)
expect(group_label1.name).to eq(group_label1.title)
end
it 'returns 404 if label does not exist' do
put api("/groups/#{group.id}/labels/not_exists", user),
params: {
- new_name: 'label3'
+ new_name: valid_new_label_title
}
expect(response).to have_gitlab_http_status(:not_found)
end
it 'returns 400 if no new parameters given' do
- put api("/groups/#{group.id}/labels/#{group_label1.name}", user)
+ put api("/groups/#{group.id}/labels/#{valid_group_label_title_1_esc}", user)
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('new_name, color, description are missing, '\
@@ -339,7 +345,7 @@ RSpec.describe API::GroupLabels do
describe 'POST /groups/:id/labels/:label_id/subscribe' do
context 'when label_id is a label title' do
it 'subscribes to the label' do
- post api("/groups/#{group.id}/labels/#{group_label1.title}/subscribe", user)
+ post api("/groups/#{group.id}/labels/#{valid_group_label_title_1_esc}/subscribe", user)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(group_label1.title)
@@ -385,7 +391,7 @@ RSpec.describe API::GroupLabels do
context 'when label_id is a label title' do
it 'unsubscribes from the label' do
- post api("/groups/#{group.id}/labels/#{group_label1.title}/unsubscribe", user)
+ post api("/groups/#{group.id}/labels/#{valid_group_label_title_1_esc}/unsubscribe", user)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq(group_label1.title)
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index 6db6de4b533..e3fffd3e3fd 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -10,14 +10,19 @@ RSpec.describe API::Labels do
else
label_id = spec_params[:name] || spec_params[:label_id]
- put api("/projects/#{project.id}/labels/#{label_id}", user),
+ put api("/projects/#{project.id}/labels/#{ERB::Util.url_encode(label_id)}", user),
params: request_params.merge(spec_params.except(:name, :id))
end
end
+ let_it_be(:valid_label_title_1) { 'Label foo & bar:subgroup::v.1' }
+ let_it_be(:valid_label_title_1_esc) { ERB::Util.url_encode(valid_label_title_1) }
+ let_it_be(:valid_label_title_2) { 'Label bar & foo:subgroup::v.2' }
+ let_it_be(:valid_group_label_title_1) { 'Group label foobar:sub::v.1' }
+
let(:user) { create(:user) }
let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
- let!(:label1) { create(:label, description: 'the best label', title: 'label1', project: project) }
+ let!(:label1) { create(:label, description: 'the best label v.1', title: valid_label_title_1, project: project) }
let!(:priority_label) { create(:label, title: 'bug', project: project, priority: 3) }
route_types = [:deprecated, :rest]
@@ -25,10 +30,10 @@ RSpec.describe API::Labels do
shared_examples 'label update API' do
route_types.each do |route_type|
it "returns 200 if name is changed (#{route_type} route)" do
- put_labels_api(route_type, user, spec_params, new_name: 'New Label')
+ put_labels_api(route_type, user, spec_params, new_name: valid_label_title_2)
expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['name']).to eq('New Label')
+ expect(json_response['name']).to eq(valid_label_title_2)
expect(json_response['color']).to eq(label1.color)
end
@@ -77,10 +82,10 @@ RSpec.describe API::Labels do
end
it "returns 200 if name and colors and description are changed (#{route_type} route)" do
- put_labels_api(route_type, user, spec_params, new_name: 'New Label', color: '#FFFFFF', description: 'test')
+ put_labels_api(route_type, user, spec_params, new_name: valid_label_title_2, color: '#FFFFFF', description: 'test')
expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['name']).to eq('New Label')
+ expect(json_response['name']).to eq(valid_label_title_2)
expect(json_response['color']).to eq('#FFFFFF')
expect(json_response['description']).to eq('test')
end
@@ -141,7 +146,7 @@ RSpec.describe API::Labels do
priority: nil
}.merge(spec_params.except(:name, :id))
- put api("/projects/#{project.id}/labels/#{label_id}", user),
+ put api("/projects/#{project.id}/labels/#{ERB::Util.url_encode(label_id)}", user),
params: request_params
expect(response).to have_gitlab_http_status(:ok)
@@ -167,7 +172,7 @@ RSpec.describe API::Labels do
it 'returns 204 for existing label (rest route)' do
label_id = spec_params[:name] || spec_params[:label_id]
- delete api("/projects/#{project.id}/labels/#{label_id}", user), params: spec_params.except(:name, :label_id)
+ delete api("/projects/#{project.id}/labels/#{ERB::Util.url_encode(label_id)}", user), params: spec_params.except(:name, :label_id)
expect(response).to have_gitlab_http_status(:no_content)
end
@@ -179,7 +184,7 @@ RSpec.describe API::Labels do
describe 'GET /projects/:id/labels' do
let_it_be(:group) { create(:group) }
- let_it_be(:group_label) { create(:group_label, title: 'feature label', group: group) }
+ let_it_be(:group_label) { create(:group_label, title: valid_group_label_title_1, group: group) }
before do
project.update!(group: group)
@@ -219,7 +224,7 @@ RSpec.describe API::Labels do
'closed_issues_count' => 1,
'open_merge_requests_count' => 0,
'name' => label1.name,
- 'description' => 'the best label',
+ 'description' => label1.description,
'color' => a_string_matching(/^#\h{6}$/),
'text_color' => a_string_matching(/^#\h{6}$/),
'priority' => nil,
@@ -293,14 +298,14 @@ RSpec.describe API::Labels do
it 'returns created label when all params' do
post api("/projects/#{project.id}/labels", user),
params: {
- name: 'Foo',
+ name: valid_label_title_2,
color: '#FFAABB',
description: 'test',
priority: 2
}
expect(response).to have_gitlab_http_status(:created)
- expect(json_response['name']).to eq('Foo')
+ expect(json_response['name']).to eq(valid_label_title_2)
expect(json_response['color']).to eq('#FFAABB')
expect(json_response['description']).to eq('test')
expect(json_response['priority']).to eq(2)
@@ -309,12 +314,12 @@ RSpec.describe API::Labels do
it 'returns created label when only required params' do
post api("/projects/#{project.id}/labels", user),
params: {
- name: 'Foo & Bar',
+ name: valid_label_title_2,
color: '#FFAABB'
}
expect(response).to have_gitlab_http_status(:created)
- expect(json_response['name']).to eq('Foo & Bar')
+ expect(json_response['name']).to eq(valid_label_title_2)
expect(json_response['color']).to eq('#FFAABB')
expect(json_response['description']).to be_nil
expect(json_response['priority']).to be_nil
@@ -323,13 +328,13 @@ RSpec.describe API::Labels do
it 'creates a prioritized label' do
post api("/projects/#{project.id}/labels", user),
params: {
- name: 'Foo & Bar',
+ name: valid_label_title_2,
color: '#FFAABB',
priority: 3
}
expect(response).to have_gitlab_http_status(:created)
- expect(json_response['name']).to eq('Foo & Bar')
+ expect(json_response['name']).to eq(valid_label_title_2)
expect(json_response['color']).to eq('#FFAABB')
expect(json_response['description']).to be_nil
expect(json_response['priority']).to eq(3)
@@ -348,7 +353,7 @@ RSpec.describe API::Labels do
it 'returns 400 for invalid color' do
post api("/projects/#{project.id}/labels", user),
params: {
- name: 'Foo',
+ name: valid_label_title_2,
color: '#FFAA'
}
expect(response).to have_gitlab_http_status(:bad_request)
@@ -358,7 +363,7 @@ RSpec.describe API::Labels do
it 'returns 400 for too long color code' do
post api("/projects/#{project.id}/labels", user),
params: {
- name: 'Foo',
+ name: valid_label_title_2,
color: '#FFAAFFFF'
}
expect(response).to have_gitlab_http_status(:bad_request)
@@ -393,7 +398,7 @@ RSpec.describe API::Labels do
it 'returns 400 for invalid priority' do
post api("/projects/#{project.id}/labels", user),
params: {
- name: 'Foo',
+ name: valid_label_title_2,
color: '#FFAAFFFF',
priority: 'foo'
}
@@ -404,7 +409,7 @@ RSpec.describe API::Labels do
it 'returns 409 if label already exists in project' do
post api("/projects/#{project.id}/labels", user),
params: {
- name: 'label1',
+ name: valid_label_title_1,
color: '#FFAABB'
}
expect(response).to have_gitlab_http_status(:conflict)
@@ -414,7 +419,7 @@ RSpec.describe API::Labels do
describe 'DELETE /projects/:id/labels' do
it_behaves_like 'label delete API' do
- let(:spec_params) { { name: 'label1' } }
+ let(:spec_params) { { name: valid_label_title_1 } }
end
it_behaves_like 'label delete API' do
@@ -422,7 +427,7 @@ RSpec.describe API::Labels do
end
it 'returns 404 for non existing label' do
- delete api("/projects/#{project.id}/labels", user), params: { name: 'label2' }
+ delete api("/projects/#{project.id}/labels", user), params: { name: 'unknown' }
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 Label Not Found')
@@ -446,14 +451,14 @@ RSpec.describe API::Labels do
it_behaves_like '412 response' do
let(:request) { api("/projects/#{project.id}/labels", user) }
- let(:params) { { name: 'label1' } }
+ let(:params) { { name: valid_label_title_1 } }
end
end
describe 'PUT /projects/:id/labels' do
context 'when using name' do
it_behaves_like 'label update API' do
- let(:spec_params) { { name: 'label1' } }
+ let(:spec_params) { { name: valid_label_title_1 } }
let(:expected_response_label_id) { label1.id }
end
end
@@ -468,7 +473,7 @@ RSpec.describe API::Labels do
it 'returns 404 if label does not exist' do
put api("/projects/#{project.id}/labels", user),
params: {
- name: 'label2',
+ name: valid_label_title_2,
new_name: 'label3'
}
@@ -571,7 +576,7 @@ RSpec.describe API::Labels do
describe "POST /projects/:id/labels/:label_id/subscribe" do
context "when label_id is a label title" do
it "subscribes to the label" do
- post api("/projects/#{project.id}/labels/#{label1.title}/subscribe", user)
+ post api("/projects/#{project.id}/labels/#{valid_label_title_1_esc}/subscribe", user)
expect(response).to have_gitlab_http_status(:created)
expect(json_response["name"]).to eq(label1.title)
@@ -617,7 +622,7 @@ RSpec.describe API::Labels do
context "when label_id is a label title" do
it "unsubscribes from the label" do
- post api("/projects/#{project.id}/labels/#{label1.title}/unsubscribe", user)
+ post api("/projects/#{project.id}/labels/#{valid_label_title_1_esc}/unsubscribe", user)
expect(response).to have_gitlab_http_status(:created)
expect(json_response["name"]).to eq(label1.title)
diff --git a/spec/services/alert_management/process_prometheus_alert_service_spec.rb b/spec/services/alert_management/process_prometheus_alert_service_spec.rb
index 8f81c1967d5..fb1a23996e3 100644
--- a/spec/services/alert_management/process_prometheus_alert_service_spec.rb
+++ b/spec/services/alert_management/process_prometheus_alert_service_spec.rb
@@ -158,7 +158,7 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do
it 'writes a warning to the log' do
expect(Gitlab::AppLogger).to receive(:warn).with(
- message: 'Unable to create AlertManagement::Alert',
+ message: 'Unable to create AlertManagement::Alert from Prometheus',
project_id: project.id,
alert_errors: { hosts: ['hosts array is over 255 chars'] }
)
diff --git a/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb b/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
index 8df9b0c3e60..a3a616f0f64 100644
--- a/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
+++ b/spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
@@ -76,6 +76,31 @@ RSpec.describe Ci::CreatePipelineService, '#execute' do
}
end
end
+
+ context 'with resource group' do
+ let(:config) do
+ <<~YAML
+ instrumentation_test:
+ stage: test
+ resource_group: iOS
+ trigger:
+ include:
+ - local: path/to/child.yml
+ YAML
+ end
+
+ # TODO: This test will be properly implemented in the next MR
+ # for https://gitlab.com/gitlab-org/gitlab/-/issues/39057.
+ it 'creates bridge job but still resource group is no-op', :aggregate_failures do
+ pipeline = create_pipeline!
+
+ test = pipeline.statuses.find_by(name: 'instrumentation_test')
+
+ expect(pipeline).to be_persisted
+ expect(test).to be_a Ci::Bridge
+ expect(project.resource_groups.count).to eq(0)
+ end
+ end
end
describe 'child pipeline triggers' do
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index e1f1bdc41a1..2a9d73e75c0 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -952,9 +952,9 @@ RSpec.describe Ci::CreatePipelineService do
expect(result).to be_persisted
expect(deploy_job.resource_group.key).to eq(resource_group_key)
expect(project.resource_groups.count).to eq(1)
- expect(resource_group.builds.count).to eq(1)
+ expect(resource_group.processables.count).to eq(1)
expect(resource_group.resources.count).to eq(1)
- expect(resource_group.resources.first.build).to eq(nil)
+ expect(resource_group.resources.first.processable).to eq(nil)
end
context 'when resource group key includes predefined variables' do
diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb
index 85bcf4562b1..c2769d4fa88 100644
--- a/spec/services/merge_requests/push_options_handler_service_spec.rb
+++ b/spec/services/merge_requests/push_options_handler_service_spec.rb
@@ -21,6 +21,7 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} refs/heads/#{source_branch}" }
let(:default_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{project.default_branch}" }
+ let(:error_mr_required) { "A merge_request.create push option is required to create a merge request for branch #{source_branch}" }
shared_examples_for 'a service that can create a merge request' do
subject(:last_mr) { MergeRequest.last }
@@ -176,11 +177,9 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
it_behaves_like 'a service that does not create a merge request'
it 'adds an error to the service' do
- error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
-
service.execute
- expect(service.errors).to include(error)
+ expect(service.errors).to include(error_mr_required)
end
context 'when coupled with the `create` push option' do
@@ -197,11 +196,9 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
it_behaves_like 'a service that does not create a merge request'
it 'adds an error to the service' do
- error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
-
service.execute
- expect(service.errors).to include(error)
+ expect(service.errors).to include(error_mr_required)
end
context 'when coupled with the `create` push option' do
@@ -263,11 +260,9 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
it_behaves_like 'a service that does not create a merge request'
it 'adds an error to the service' do
- error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
-
service.execute
- expect(service.errors).to include(error)
+ expect(service.errors).to include(error_mr_required)
end
context 'when coupled with the `create` push option' do
@@ -308,11 +303,9 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
it_behaves_like 'a service that does not create a merge request'
it 'adds an error to the service' do
- error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
-
service.execute
- expect(service.errors).to include(error)
+ expect(service.errors).to include(error_mr_required)
end
context 'when coupled with the `create` push option' do
@@ -329,11 +322,9 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
it_behaves_like 'a service that does not create a merge request'
it 'adds an error to the service' do
- error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
-
service.execute
- expect(service.errors).to include(error)
+ expect(service.errors).to include(error_mr_required)
end
context 'when coupled with the `create` push option' do
@@ -374,11 +365,9 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
it_behaves_like 'a service that does not create a merge request'
it 'adds an error to the service' do
- error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
-
service.execute
- expect(service.errors).to include(error)
+ expect(service.errors).to include(error_mr_required)
end
context 'when coupled with the `create` push option' do
@@ -395,11 +384,9 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
it_behaves_like 'a service that does not create a merge request'
it 'adds an error to the service' do
- error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
-
service.execute
- expect(service.errors).to include(error)
+ expect(service.errors).to include(error_mr_required)
end
context 'when coupled with the `create` push option' do
@@ -440,11 +427,9 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
it_behaves_like 'a service that does not create a merge request'
it 'adds an error to the service' do
- error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
-
service.execute
- expect(service.errors).to include(error)
+ expect(service.errors).to include(error_mr_required)
end
context 'when coupled with the `create` push option' do
@@ -461,11 +446,9 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
it_behaves_like 'a service that does not create a merge request'
it 'adds an error to the service' do
- error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
-
service.execute
- expect(service.errors).to include(error)
+ expect(service.errors).to include(error_mr_required)
end
context 'when coupled with the `create` push option' do
@@ -506,11 +489,9 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
it_behaves_like 'a service that does not create a merge request'
it 'adds an error to the service' do
- error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
-
service.execute
- expect(service.errors).to include(error)
+ expect(service.errors).to include(error_mr_required)
end
context 'when coupled with the `create` push option' do
@@ -527,11 +508,9 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
it_behaves_like 'a service that does not create a merge request'
it 'adds an error to the service' do
- error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
-
service.execute
- expect(service.errors).to include(error)
+ expect(service.errors).to include(error_mr_required)
end
context 'when coupled with the `create` push option' do