Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
95feeb4ee4
commit
6e495b4e91
|
|
@ -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',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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.'));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export const PROJECT_MEMBER_BASE_PROPERTY_NAME = 'project_member';
|
||||
|
|
@ -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,
|
||||
);
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,24 +25,21 @@ 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
|
||||
override :process_new_alert
|
||||
def process_new_alert
|
||||
return if resolving_alert?
|
||||
|
||||
super
|
||||
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
|
||||
override :process_firing_alert
|
||||
def process_firing_alert
|
||||
super
|
||||
|
||||
reset_alert_status
|
||||
end
|
||||
|
||||
def reset_alert_management_alert_status
|
||||
def reset_alert_status
|
||||
return if alert.trigger
|
||||
|
||||
logger.warn(
|
||||
|
|
@ -54,83 +49,7 @@ module AlertManagement
|
|||
)
|
||||
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
|
||||
|
||||
logger.warn(
|
||||
message: 'Unable to create AlertManagement::Alert',
|
||||
project_id: project.id,
|
||||
alert_errors: alert.errors.messages
|
||||
)
|
||||
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
|
||||
|
||||
logger.warn(
|
||||
message: 'Unable to update AlertManagement::Alert status to resolved',
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
end
|
||||
|
||||
def notification_source
|
||||
alert.monitoring_tool || integration&.name || 'Generic Alert Endpoint'
|
||||
end
|
||||
|
||||
def valid_payload_size?
|
||||
Gitlab::Utils::DeepSize.new(payload).valid?
|
||||
end
|
||||
|
||||
override :alert_source
|
||||
def alert_source
|
||||
alert.monitoring_tool || integration&.name || 'Generic Alert Endpoint'
|
||||
end
|
||||
|
||||
def active_integration?
|
||||
integration&.active?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allow dots in label names through REST API
|
||||
merge_request: 52591
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Improve search filter by taking space in file path into account
|
||||
merge_request: 52392
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Code extraction - refactoring of MR services classes
|
||||
merge_request: 49827
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Migrate toggle button in subscription to GitLab UI component
|
||||
merge_request: 52717
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Introduce a rendering limit for commit titles
|
||||
merge_request: 52904
|
||||
author:
|
||||
type: performance
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
@ -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 -->
|
||||
|
|
@ -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 -->
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
redirect_to: 'https://docs.gitlab.com'
|
||||
---
|
||||
|
||||
Visit our [documentation page](https://docs.gitlab.com) for information about GitLab.
|
||||
|
|
@ -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 -->
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
redirect_to: 'https://docs.gitlab.com'
|
||||
---
|
||||
|
||||
Visit our [documentation page](https://docs.gitlab.com) for information about GitLab.
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
redirect_to: 'https://docs.gitlab.com'
|
||||
---
|
||||
|
||||
Visit our [documentation page](https://docs.gitlab.com) for information about GitLab.
|
||||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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' } });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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'] }
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue