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