Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-02-01 09:09:28 +00:00
parent 95feeb4ee4
commit 6e495b4e91
85 changed files with 796 additions and 647 deletions

View File

@ -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',
};

View File

@ -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.'));
});
}

View File

@ -0,0 +1 @@
export const PROJECT_MEMBER_BASE_PROPERTY_NAME = 'project_member';

View File

@ -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,
);

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 }

View File

@ -0,0 +1,5 @@
---
title: Allow dots in label names through REST API
merge_request: 52591
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Improve search filter by taking space in file path into account
merge_request: 52392
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Code extraction - refactoring of MR services classes
merge_request: 49827
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Migrate toggle button in subscription to GitLab UI component
merge_request: 52717
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Introduce a rendering limit for commit titles
merge_request: 52904
author:
type: performance

View File

@ -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

View File

@ -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.

View File

@ -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 -->

View File

@ -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"
}
]
```

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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 -->

View File

@ -1,5 +0,0 @@
---
redirect_to: 'https://docs.gitlab.com'
---
Visit our [documentation page](https://docs.gitlab.com) for information about GitLab.

View File

@ -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 -->

View File

@ -1,5 +0,0 @@
---
redirect_to: 'https://docs.gitlab.com'
---
Visit our [documentation page](https://docs.gitlab.com) for information about GitLab.

View File

@ -1,5 +0,0 @@
---
redirect_to: 'https://docs.gitlab.com'
---
Visit our [documentation page](https://docs.gitlab.com) for information about GitLab.

View File

@ -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`.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)'

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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' } });
});
});
});

View File

@ -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);
});
});
});

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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])

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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'] }
)

View File

@ -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

View File

@ -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

View File

@ -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