Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-04-07 09:09:06 +00:00
parent e28ed4a6b2
commit dc7cd84a3e
47 changed files with 917 additions and 196 deletions

View file

@ -2010,6 +2010,7 @@ Gitlab/NamespacedClass:
- 'app/finders/deployments_finder.rb' - 'app/finders/deployments_finder.rb'
- 'app/finders/environment_names_finder.rb' - 'app/finders/environment_names_finder.rb'
- 'app/finders/environments_finder.rb' - 'app/finders/environments_finder.rb'
- 'app/finders/environments_by_deployments_finder.rb'
- 'app/finders/events_finder.rb' - 'app/finders/events_finder.rb'
- 'app/finders/feature_flags_finder.rb' - 'app/finders/feature_flags_finder.rb'
- 'app/finders/feature_flags_user_lists_finder.rb' - 'app/finders/feature_flags_user_lists_finder.rb'

View file

@ -13,6 +13,7 @@ import {
GlFormRadioGroup, GlFormRadioGroup,
GlFormSelect, GlFormSelect,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { kebabCase } from 'lodash';
import { buildApiUrl } from '~/api/api_utils'; import { buildApiUrl } from '~/api/api_utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
@ -145,6 +146,10 @@ export default {
this.fork.visibility = visibility; this.fork.visibility = visibility;
} }
}, },
// eslint-disable-next-line func-names
'fork.name': function (newVal) {
this.fork.slug = kebabCase(newVal);
},
}, },
mounted() { mounted() {
this.fetchNamespaces(); this.fetchNamespaces();

View file

@ -60,6 +60,7 @@ export default class MergeRequestStore {
this.rebaseInProgress = data.rebase_in_progress; this.rebaseInProgress = data.rebase_in_progress;
this.mergeRequestDiffsPath = data.diffs_path; this.mergeRequestDiffsPath = data.diffs_path;
this.approvalsWidgetType = data.approvals_widget_type; this.approvalsWidgetType = data.approvals_widget_type;
this.mergeRequestWidgetPath = data.merge_request_widget_path;
if (data.issues_links) { if (data.issues_links) {
const links = data.issues_links; const links = data.issues_links;

View file

@ -20,7 +20,7 @@ class Projects::BlameController < Projects::ApplicationController
environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit } environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
environment_params[:find_latest] = true environment_params[:find_latest] = true
@environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last @environment = EnvironmentsByDeploymentsFinder.new(@project, current_user, environment_params).execute.last
@blame = Gitlab::Blame.new(@blob, @commit) @blame = Gitlab::Blame.new(@blob, @commit)
@blame = Gitlab::View::Presenter::Factory.new(@blame, project: @project, path: @path).fabricate! @blame = Gitlab::View::Presenter::Factory.new(@blame, project: @project, path: @path).fabricate!

View file

@ -214,7 +214,7 @@ class Projects::BlobController < Projects::ApplicationController
def show_html def show_html
environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit } environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
environment_params[:find_latest] = true environment_params[:find_latest] = true
@environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last @environment = EnvironmentsByDeploymentsFinder.new(@project, current_user, environment_params).execute.last
@last_commit = @repository.last_commit_for_path(@commit.id, @blob.path, literal_pathspec: true) @last_commit = @repository.last_commit_for_path(@commit.id, @blob.path, literal_pathspec: true)
@code_navigation_path = Gitlab::CodeNavigationPath.new(@project, @blob.commit_id).full_json_path_for(@blob.path) @code_navigation_path = Gitlab::CodeNavigationPath.new(@project, @blob.commit_id).full_json_path_for(@blob.path)

View file

@ -168,7 +168,7 @@ class Projects::CommitController < Projects::ApplicationController
@diffs = commit.diffs(opts) @diffs = commit.diffs(opts)
@notes_count = commit.notes.count @notes_count = commit.notes.count
@environment = EnvironmentsFinder.new(@project, current_user, commit: @commit, find_latest: true).execute.last @environment = EnvironmentsByDeploymentsFinder.new(@project, current_user, commit: @commit, find_latest: true).execute.last
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord

View file

@ -132,7 +132,7 @@ class Projects::CompareController < Projects::ApplicationController
if compare if compare
environment_params = source_project.repository.branch_exists?(head_ref) ? { ref: head_ref } : { commit: compare.commit } environment_params = source_project.repository.branch_exists?(head_ref) ? { ref: head_ref } : { commit: compare.commit }
environment_params[:find_latest] = true environment_params[:find_latest] = true
@environment = EnvironmentsFinder.new(source_project, current_user, environment_params).execute.last @environment = EnvironmentsByDeploymentsFinder.new(source_project, current_user, environment_params).execute.last
end end
end end

View file

@ -14,6 +14,8 @@ class Projects::MergeRequests::ContentController < Projects::MergeRequests::Appl
SLOW_POLLING_INTERVAL = 5.minutes.in_milliseconds SLOW_POLLING_INTERVAL = 5.minutes.in_milliseconds
def widget def widget
check_mergeability_async!
respond_to do |format| respond_to do |format|
format.json do format.json do
render json: serializer(MergeRequestPollWidgetEntity) render json: serializer(MergeRequestPollWidgetEntity)
@ -38,6 +40,13 @@ class Projects::MergeRequests::ContentController < Projects::MergeRequests::Appl
def serializer(entity) def serializer(entity)
serializer = MergeRequestSerializer.new(current_user: current_user, project: merge_request.project) serializer = MergeRequestSerializer.new(current_user: current_user, project: merge_request.project)
serializer.represent(merge_request, {}, entity) serializer.represent(merge_request, { async_mergeability_check: params[:async_mergeability_check] }, entity)
end
def check_mergeability_async!
return unless Feature.enabled?(:check_mergeability_async_in_widget, merge_request.project, default_enabled: :yaml)
return if params[:async_mergeability_check].blank?
merge_request.check_mergeability(async: true)
end end
end end

View file

@ -92,7 +92,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def show def show
close_merge_request_if_no_source_project close_merge_request_if_no_source_project
@merge_request.check_mergeability(async: true)
if Feature.disabled?(:check_mergeability_async_in_widget, @project, default_enabled: :yaml)
@merge_request.check_mergeability(async: true)
end
respond_to do |format| respond_to do |format|
format.html do format.html do

View file

@ -0,0 +1,65 @@
# frozen_string_literal: true
class EnvironmentsByDeploymentsFinder
attr_reader :project, :current_user, :params
def initialize(project, current_user, params = {})
@project, @current_user, @params = project, current_user, params
end
# rubocop: disable CodeReuse/ActiveRecord
def execute
deployments = project.deployments
deployments =
if ref
deployments_query = params[:with_tags] ? 'ref = :ref OR tag IS TRUE' : 'ref = :ref'
deployments.where(deployments_query, ref: ref.to_s)
elsif commit
deployments.where(sha: commit.sha)
else
deployments.none
end
environment_ids = deployments
.group(:environment_id)
.select(:environment_id)
environments = project.environments.available
.where(id: environment_ids)
if params[:find_latest]
find_one(environments.order_by_last_deployed_at_desc)
else
find_all(environments.order_by_last_deployed_at.to_a)
end
end
# rubocop: enable CodeReuse/ActiveRecord
private
def find_one(environments)
[environments.find { |environment| valid_environment?(environment) }].compact
end
def find_all(environments)
environments.select { |environment| valid_environment?(environment) }
end
def valid_environment?(environment)
# Go in order of cost: SQL calls are cheaper than Gitaly calls
return false unless Ability.allowed?(current_user, :read_environment, environment)
return false if ref && params[:recently_updated] && !environment.recently_updated_on_branch?(ref)
return false if ref && commit && !environment.includes_commit?(commit)
true
end
def ref
params[:ref].try(:to_s)
end
def commit
params[:commit]
end
end

View file

@ -9,34 +9,6 @@ class EnvironmentsFinder
@project, @current_user, @params = project, current_user, params @project, @current_user, @params = project, current_user, params
end end
# rubocop: disable CodeReuse/ActiveRecord
def execute
deployments = project.deployments
deployments =
if ref
deployments_query = params[:with_tags] ? 'ref = :ref OR tag IS TRUE' : 'ref = :ref'
deployments.where(deployments_query, ref: ref.to_s)
elsif commit
deployments.where(sha: commit.sha)
else
deployments.none
end
environment_ids = deployments
.group(:environment_id)
.select(:environment_id)
environments = project.environments.available
.where(id: environment_ids)
if params[:find_latest]
find_one(environments.order_by_last_deployed_at_desc)
else
find_all(environments.order_by_last_deployed_at.to_a)
end
end
# rubocop: enable CodeReuse/ActiveRecord
# This method will eventually take the place of `#execute` as an # This method will eventually take the place of `#execute` as an
# efficient way to get relevant environment entries. # efficient way to get relevant environment entries.
# Currently, `#execute` method has a serious technical debt and # Currently, `#execute` method has a serious technical debt and
@ -55,32 +27,6 @@ class EnvironmentsFinder
private private
def find_one(environments)
[environments.find { |environment| valid_environment?(environment) }].compact
end
def find_all(environments)
environments.select { |environment| valid_environment?(environment) }
end
def valid_environment?(environment)
# Go in order of cost: SQL calls are cheaper than Gitaly calls
return false unless Ability.allowed?(current_user, :read_environment, environment)
return false if ref && params[:recently_updated] && !environment.recently_updated_on_branch?(ref)
return false if ref && commit && !environment.includes_commit?(commit)
true
end
def ref
params[:ref].try(:to_s)
end
def commit
params[:commit]
end
def by_name(environments) def by_name(environments)
if params[:name].present? if params[:name].present?
environments.for_name(params[:name]) environments.for_name(params[:name])

View file

@ -22,8 +22,7 @@ module Types
description(enum_mod.description) if use_description description(enum_mod.description) if use_description
enum_mod.definition.each do |key, content| enum_mod.definition.each do |key, content|
desc = content.delete(:description) value(key.to_s.upcase, **content)
value(key.to_s.upcase, description: desc, **content)
end end
end end

View file

@ -0,0 +1,35 @@
# frozen_string_literal: true
module Sidebars
module ContainerWithHtmlOptions
# The attributes returned from this method
# will be applied to helper methods like
# `link_to` or the div containing the container.
def container_html_options
{
title: title
}.merge(extra_container_html_options)
end
# Classes will override mostly this method
# and not `container_html_options`.
def extra_container_html_options
{}
end
def title
raise NotImplementedError
end
# The attributes returned from this method
# will be applied right next to the title,
# for example in the span that renders the title.
def title_html_options
{}
end
def link
raise NotImplementedError
end
end
end

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
module Sidebars
module HasActiveRoutes
# This method will indicate for which paths or
# controllers, the menu or menu item should
# be set as active.
#
# The returned values are passed to the `nav_link` helper method,
# so the params can be either `path`, `page`, `controller`.
# Param 'action' is not supported.
def active_routes
{}
end
end
end

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
# This module has the necessary methods to store
# hints for menus. Hints are elements displayed
# when the user hover the menu item.
module Sidebars
module HasHint
def show_hint?
false
end
def hint_html_options
{}
end
end
end

View file

@ -0,0 +1,27 @@
# frozen_string_literal: true
# This module has the necessary methods to show
# sprites or images next to the menu item.
module Sidebars
module HasIcon
def sprite_icon
nil
end
def sprite_icon_html_options
{}
end
def image_path
nil
end
def image_html_options
{}
end
def icon_or_image?
sprite_icon || image_path
end
end
end

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
# This module introduces the logic to show the "pill" element
# next to the menu item, indicating the a count.
module Sidebars
module HasPill
def has_pill?
false
end
# In this method we will need to provide the query
# to retrieve the elements count
def pill_count
raise NotImplementedError
end
def pill_html_options
{}
end
end
end

View file

@ -0,0 +1,37 @@
# frozen_string_literal: true
# This module handles elements in a list. All elements
# must have a different class
module Sidebars
module PositionableList
def add_element(list, element)
list << element
end
def insert_element_before(list, before_element, new_element)
index = index_of(list, before_element)
if index
list.insert(index, new_element)
else
list.unshift(new_element)
end
end
def insert_element_after(list, after_element, new_element)
index = index_of(list, after_element)
if index
list.insert(index + 1, new_element)
else
add_element(list, new_element)
end
end
private
def index_of(list, element)
list.index { |e| e.is_a?(element) }
end
end
end

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
module Sidebars
module Renderable
# This method will control whether the menu or menu_item
# should be rendered. It will be overriden by specific
# classes.
def render?
true
end
end
end

View file

@ -1367,11 +1367,11 @@ class MergeRequest < ApplicationRecord
def environments_for(current_user, latest: false) def environments_for(current_user, latest: false)
return [] unless diff_head_commit return [] unless diff_head_commit
envs = EnvironmentsFinder.new(target_project, current_user, envs = EnvironmentsByDeploymentsFinder.new(target_project, current_user,
ref: target_branch, commit: diff_head_commit, with_tags: true, find_latest: latest).execute ref: target_branch, commit: diff_head_commit, with_tags: true, find_latest: latest).execute
if source_project if source_project
envs.concat EnvironmentsFinder.new(source_project, current_user, envs.concat EnvironmentsByDeploymentsFinder.new(source_project, current_user,
ref: source_branch, commit: diff_head_commit, find_latest: latest).execute ref: source_branch, commit: diff_head_commit, find_latest: latest).execute
end end

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
# This class stores all the information needed to display and
# render the sidebar and menus.
# It usually stores information regarding the context and calculated
# values where the logic is in helpers.
module Sidebars
class Context
attr_reader :current_user, :container
def initialize(current_user:, container:, **args)
@current_user = current_user
@container = container
args.each do |key, value|
singleton_class.public_send(:attr_reader, key) # rubocop:disable GitlabSecurity/PublicSend
instance_variable_set("@#{key}", value)
end
end
end
end

View file

@ -0,0 +1,82 @@
# frozen_string_literal: true
module Sidebars
class Menu
extend ::Gitlab::Utils::Override
include ::Gitlab::Routing
include GitlabRoutingHelper
include Gitlab::Allowable
include ::Sidebars::HasPill
include ::Sidebars::HasIcon
include ::Sidebars::PositionableList
include ::Sidebars::Renderable
include ::Sidebars::ContainerWithHtmlOptions
include ::Sidebars::HasActiveRoutes
attr_reader :context
delegate :current_user, :container, to: :@context
def initialize(context)
@context = context
@items = []
configure_menu_items
end
def configure_menu_items
# No-op
end
override :render?
def render?
@items.empty? || renderable_items.any?
end
# Menus might have or not a link
override :link
def link
nil
end
# This method normalizes the information retrieved from the submenus and this menu
# Value from menus is something like: [{ path: 'foo', path: 'bar', controller: :foo }]
# This method filters the information and returns: { path: ['foo', 'bar'], controller: :foo }
def all_active_routes
@all_active_routes ||= begin
([active_routes] + renderable_items.map(&:active_routes)).flatten.each_with_object({}) do |pairs, hash|
pairs.each do |k, v|
hash[k] ||= []
hash[k] += Array(v)
hash[k].uniq!
end
hash
end
end
end
def has_items?
@items.any?
end
def add_item(item)
add_element(@items, item)
end
def insert_item_before(before_item, new_item)
insert_element_before(@items, before_item, new_item)
end
def insert_item_after(after_item, new_item)
insert_element_after(@items, after_item, new_item)
end
def has_renderable_items?
renderable_items.any?
end
def renderable_items
@renderable_items ||= @items.select(&:render?)
end
end
end

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
module Sidebars
class MenuItem
extend ::Gitlab::Utils::Override
include ::Gitlab::Routing
include GitlabRoutingHelper
include Gitlab::Allowable
include ::Sidebars::HasIcon
include ::Sidebars::HasHint
include ::Sidebars::Renderable
include ::Sidebars::ContainerWithHtmlOptions
include ::Sidebars::HasActiveRoutes
attr_reader :context
def initialize(context)
@context = context
end
end
end

View file

@ -0,0 +1,55 @@
# frozen_string_literal: true
module Sidebars
class Panel
extend ::Gitlab::Utils::Override
include ::Sidebars::PositionableList
attr_reader :context, :scope_menu, :hidden_menu
def initialize(context)
@context = context
@scope_menu = nil
@hidden_menu = nil
@menus = []
configure_menus
end
def configure_menus
# No-op
end
def add_menu(menu)
add_element(@menus, menu)
end
def insert_menu_before(before_menu, new_menu)
insert_element_before(@menus, before_menu, new_menu)
end
def insert_menu_after(after_menu, new_menu)
insert_element_after(@menus, after_menu, new_menu)
end
def set_scope_menu(scope_menu)
@scope_menu = scope_menu
end
def set_hidden_menu(hidden_menu)
@hidden_menu = hidden_menu
end
def aria_label
raise NotImplementedError
end
def has_renderable_menus?
renderable_menus.any?
end
def renderable_menus
@renderable_menus ||= @menus.select(&:render?)
end
end
end

View file

@ -64,6 +64,10 @@ class MergeRequestPollCachedWidgetEntity < IssuableEntity
presenter(merge_request).target_branch_commits_path presenter(merge_request).target_branch_commits_path
end end
expose :merge_request_widget_path do |merge_request|
widget_project_json_merge_request_path(merge_request.target_project, merge_request, format: :json)
end
expose :target_branch_tree_path do |merge_request| expose :target_branch_tree_path do |merge_request|
presenter(merge_request).target_branch_tree_path presenter(merge_request).target_branch_tree_path
end end

View file

@ -29,7 +29,12 @@ class MergeRequestPollWidgetEntity < Grape::Entity
expose :default_merge_commit_message expose :default_merge_commit_message
expose :mergeable?, as: :mergeable expose :mergeable do |merge_request, options|
next merge_request.mergeable? if Feature.disabled?(:check_mergeability_async_in_widget, merge_request.project, default_enabled: :yaml)
next false if options[:async_mergeability_check].present? && merge_request.checking?
merge_request.mergeable?
end
expose :default_merge_commit_message_with_description do |merge_request| expose :default_merge_commit_message_with_description do |merge_request|
merge_request.default_merge_commit_message(include_description: true) merge_request.default_merge_commit_message(include_description: true)

View file

@ -36,7 +36,7 @@ class MergeRequestWidgetEntity < Grape::Entity
end end
expose :merge_request_widget_path do |merge_request| expose :merge_request_widget_path do |merge_request|
widget_project_json_merge_request_path(merge_request.target_project, merge_request, format: :json) widget_project_json_merge_request_path(merge_request.target_project, merge_request, async_mergeability_check: true, format: :json)
end end
expose :merge_request_cached_widget_path do |merge_request| expose :merge_request_cached_widget_path do |merge_request|

View file

@ -35,7 +35,7 @@ module Ci
private private
def environments def environments
@environments ||= EnvironmentsFinder @environments ||= EnvironmentsByDeploymentsFinder
.new(project, current_user, ref: @ref, recently_updated: true) .new(project, current_user, ref: @ref, recently_updated: true)
.execute .execute
end end

View file

@ -62,7 +62,7 @@
- add_page_startup_api_call notes_url - add_page_startup_api_call notes_url
- else - else
- add_page_startup_api_call discussions_path(@merge_request) - add_page_startup_api_call discussions_path(@merge_request)
- add_page_startup_api_call widget_project_json_merge_request_path(@project, @merge_request, format: :json) - add_page_startup_api_call widget_project_json_merge_request_path(@project, @merge_request, async_mergeability_check: true, format: :json)
- add_page_startup_api_call cached_widget_project_json_merge_request_path(@project, @merge_request, format: :json) - add_page_startup_api_call cached_widget_project_json_merge_request_path(@project, @merge_request, format: :json)
#js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request, Feature.enabled?(:paginated_notes, @project)).to_json, #js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request, Feature.enabled?(:paginated_notes, @project)).to_json,
endpoint_metadata: @endpoint_metadata_url, endpoint_metadata: @endpoint_metadata_url,

View file

@ -10,6 +10,7 @@ module Database
LEASE_TIMEOUT_MULTIPLIER = 3 LEASE_TIMEOUT_MULTIPLIER = 3
MINIMUM_LEASE_TIMEOUT = 10.minutes.freeze MINIMUM_LEASE_TIMEOUT = 10.minutes.freeze
INTERVAL_VARIANCE = 5.seconds.freeze
def perform def perform
return unless Feature.enabled?(:execute_batched_migrations_on_schedule, type: :ops) && active_migration return unless Feature.enabled?(:execute_batched_migrations_on_schedule, type: :ops) && active_migration
@ -22,7 +23,7 @@ module Database
# models don't inherit from ApplicationRecord # models don't inherit from ApplicationRecord
active_migration.reload # rubocop:disable Cop/ActiveRecordAssociationReload active_migration.reload # rubocop:disable Cop/ActiveRecordAssociationReload
run_active_migration if active_migration.active? && active_migration.interval_elapsed? run_active_migration if active_migration.active? && active_migration.interval_elapsed?(variance: INTERVAL_VARIANCE)
end end
end end

View file

@ -0,0 +1,5 @@
---
title: Fix HAML in _promote_issue_weights.html.haml
merge_request: 58546
author: Yogi (@yo)
type: changed

View file

@ -0,0 +1,5 @@
---
title: Let users create groups and projects at signup and onboard them through issues on gitlab.com
merge_request: 58301
author:
type: added

View file

@ -0,0 +1,8 @@
---
name: check_mergeability_async_in_widget
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58178
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326567
milestone: '13.11'
type: development
group: group::source code
default_enabled: false

View file

@ -27,8 +27,11 @@ module Gitlab
active.queue_order.first active.queue_order.first
end end
def interval_elapsed? def interval_elapsed?(variance: 0)
last_job.nil? || last_job.created_at <= Time.current - interval return true unless last_job
interval_with_variance = interval - variance
last_job.created_at <= Time.current - interval_with_variance
end end
def create_batched_job!(min, max) def create_batched_job!(min, max)

View file

@ -258,6 +258,11 @@ module Trigger
ENV['DOCS_BRANCH'] || 'master' ENV['DOCS_BRANCH'] || 'master'
end end
# `gitlab-org/gitlab-docs` pipeline trigger "Triggered from gitlab-org/gitlab 'review-docs-deploy' job"
def trigger_token
ENV['DOCS_TRIGGER_TOKEN']
end
def extra_variables def extra_variables
{ {
"BRANCH_#{project_slug.upcase}" => ENV['CI_COMMIT_REF_NAME'], "BRANCH_#{project_slug.upcase}" => ENV['CI_COMMIT_REF_NAME'],

View file

@ -11,13 +11,13 @@ RSpec.describe Projects::MergeRequests::ContentController do
sign_in(user) sign_in(user)
end end
def do_request(action = :cached_widget) def do_request(action = :cached_widget, params = {})
get action, params: { get action, params: {
namespace_id: project.namespace.to_param, namespace_id: project.namespace.to_param,
project_id: project, project_id: project,
id: merge_request.iid, id: merge_request.iid,
format: :json format: :json
} }.merge(params)
end end
context 'user has access to the project' do context 'user has access to the project' do
@ -42,6 +42,10 @@ RSpec.describe Projects::MergeRequests::ContentController do
end end
describe 'GET widget' do describe 'GET widget' do
before do
merge_request.mark_as_unchecked!
end
it 'checks whether the MR can be merged' do it 'checks whether the MR can be merged' do
controller.instance_variable_set(:@merge_request, merge_request) controller.instance_variable_set(:@merge_request, merge_request)
@ -53,6 +57,17 @@ RSpec.describe Projects::MergeRequests::ContentController do
expect(response.headers['Poll-Interval']).to eq('10000') expect(response.headers['Poll-Interval']).to eq('10000')
end end
context 'when async_mergeability_check param is passed' do
it 'checks mergeability asynchronously' do
expect_next_instance_of(MergeRequests::MergeabilityCheckService) do |service|
expect(service).not_to receive(:execute)
expect(service).to receive(:async_execute).and_call_original
end
do_request(:widget, { async_mergeability_check: true })
end
end
context 'merged merge request' do context 'merged merge request' do
let(:merge_request) do let(:merge_request) do
create(:merged_merge_request, :with_test_reports, target_project: project, source_project: project) create(:merged_merge_request, :with_test_reports, target_project: project, source_project: project)

View file

@ -82,13 +82,19 @@ RSpec.describe Projects::MergeRequestsController do
merge_request.mark_as_unchecked! merge_request.mark_as_unchecked!
end end
it 'checks mergeability asynchronously' do context 'check_mergeability_async_in_widget feature flag is disabled' do
expect_next_instance_of(MergeRequests::MergeabilityCheckService) do |service| before do
expect(service).not_to receive(:execute) stub_feature_flags(check_mergeability_async_in_widget: false)
expect(service).to receive(:async_execute)
end end
go it 'checks mergeability asynchronously' do
expect_next_instance_of(MergeRequests::MergeabilityCheckService) do |service|
expect(service).not_to receive(:execute)
expect(service).to receive(:async_execute)
end
go
end
end end
end end

View file

@ -0,0 +1,127 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe EnvironmentsByDeploymentsFinder do
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
let(:environment) { create(:environment, :available, project: project) }
before do
project.add_maintainer(user)
end
describe '#execute' do
context 'tagged deployment' do
let(:environment_two) { create(:environment, project: project) }
# Environments need to include commits, so rewind two commits to fit
let(:commit) { project.commit('HEAD~2') }
before do
create(:deployment, :success, environment: environment, ref: 'v1.0.0', tag: true, sha: project.commit.id)
create(:deployment, :success, environment: environment_two, ref: 'v1.1.0', tag: true, sha: project.commit('HEAD~1').id)
end
it 'returns environment when with_tags is set' do
expect(described_class.new(project, user, ref: 'master', commit: commit, with_tags: true).execute)
.to contain_exactly(environment, environment_two)
end
it 'does not return environment when no with_tags is set' do
expect(described_class.new(project, user, ref: 'master', commit: commit).execute)
.to be_empty
end
it 'does not return environment when commit is not part of deployment' do
expect(described_class.new(project, user, ref: 'master', commit: project.commit('feature')).execute)
.to be_empty
end
# We expect two Gitaly calls: FindCommit, CommitIsAncestor
# This tests to ensure we don't call one CommitIsAncestor per environment
it 'only calls Gitaly twice when multiple environments are present', :request_store do
expect do
result = described_class.new(project, user, ref: 'master', commit: commit, with_tags: true, find_latest: true).execute
expect(result).to contain_exactly(environment_two)
end.to change { Gitlab::GitalyClient.get_request_count }.by(2)
end
end
context 'branch deployment' do
before do
create(:deployment, :success, environment: environment, ref: 'master', sha: project.commit.id)
end
it 'returns environment when ref is set' do
expect(described_class.new(project, user, ref: 'master', commit: project.commit).execute)
.to contain_exactly(environment)
end
it 'does not environment when ref is different' do
expect(described_class.new(project, user, ref: 'feature', commit: project.commit).execute)
.to be_empty
end
it 'does not return environment when commit is not part of deployment' do
expect(described_class.new(project, user, ref: 'master', commit: project.commit('feature')).execute)
.to be_empty
end
it 'returns environment when commit constraint is not set' do
expect(described_class.new(project, user, ref: 'master').execute)
.to contain_exactly(environment)
end
end
context 'commit deployment' do
before do
create(:deployment, :success, environment: environment, ref: 'master', sha: project.commit.id)
end
it 'returns environment' do
expect(described_class.new(project, user, commit: project.commit).execute)
.to contain_exactly(environment)
end
end
context 'recently updated' do
context 'when last deployment to environment is the most recent one' do
before do
create(:deployment, :success, environment: environment, ref: 'feature')
end
it 'finds recently updated environment' do
expect(described_class.new(project, user, ref: 'feature', recently_updated: true).execute)
.to contain_exactly(environment)
end
end
context 'when last deployment to environment is not the most recent' do
before do
create(:deployment, :success, environment: environment, ref: 'feature')
create(:deployment, :success, environment: environment, ref: 'master')
end
it 'does not find environment' do
expect(described_class.new(project, user, ref: 'feature', recently_updated: true).execute)
.to be_empty
end
end
context 'when there are two environments that deploy to the same branch' do
let(:second_environment) { create(:environment, project: project) }
before do
create(:deployment, :success, environment: environment, ref: 'feature')
create(:deployment, :success, environment: second_environment, ref: 'feature')
end
it 'finds both environments' do
expect(described_class.new(project, user, ref: 'feature', recently_updated: true).execute)
.to contain_exactly(environment, second_environment)
end
end
end
end
end

View file

@ -11,120 +11,6 @@ RSpec.describe EnvironmentsFinder do
project.add_maintainer(user) project.add_maintainer(user)
end end
describe '#execute' do
context 'tagged deployment' do
let(:environment_two) { create(:environment, project: project) }
# Environments need to include commits, so rewind two commits to fit
let(:commit) { project.commit('HEAD~2') }
before do
create(:deployment, :success, environment: environment, ref: 'v1.0.0', tag: true, sha: project.commit.id)
create(:deployment, :success, environment: environment_two, ref: 'v1.1.0', tag: true, sha: project.commit('HEAD~1').id)
end
it 'returns environment when with_tags is set' do
expect(described_class.new(project, user, ref: 'master', commit: commit, with_tags: true).execute)
.to contain_exactly(environment, environment_two)
end
it 'does not return environment when no with_tags is set' do
expect(described_class.new(project, user, ref: 'master', commit: commit).execute)
.to be_empty
end
it 'does not return environment when commit is not part of deployment' do
expect(described_class.new(project, user, ref: 'master', commit: project.commit('feature')).execute)
.to be_empty
end
# We expect two Gitaly calls: FindCommit, CommitIsAncestor
# This tests to ensure we don't call one CommitIsAncestor per environment
it 'only calls Gitaly twice when multiple environments are present', :request_store do
expect do
result = described_class.new(project, user, ref: 'master', commit: commit, with_tags: true, find_latest: true).execute
expect(result).to contain_exactly(environment_two)
end.to change { Gitlab::GitalyClient.get_request_count }.by(2)
end
end
context 'branch deployment' do
before do
create(:deployment, :success, environment: environment, ref: 'master', sha: project.commit.id)
end
it 'returns environment when ref is set' do
expect(described_class.new(project, user, ref: 'master', commit: project.commit).execute)
.to contain_exactly(environment)
end
it 'does not environment when ref is different' do
expect(described_class.new(project, user, ref: 'feature', commit: project.commit).execute)
.to be_empty
end
it 'does not return environment when commit is not part of deployment' do
expect(described_class.new(project, user, ref: 'master', commit: project.commit('feature')).execute)
.to be_empty
end
it 'returns environment when commit constraint is not set' do
expect(described_class.new(project, user, ref: 'master').execute)
.to contain_exactly(environment)
end
end
context 'commit deployment' do
before do
create(:deployment, :success, environment: environment, ref: 'master', sha: project.commit.id)
end
it 'returns environment' do
expect(described_class.new(project, user, commit: project.commit).execute)
.to contain_exactly(environment)
end
end
context 'recently updated' do
context 'when last deployment to environment is the most recent one' do
before do
create(:deployment, :success, environment: environment, ref: 'feature')
end
it 'finds recently updated environment' do
expect(described_class.new(project, user, ref: 'feature', recently_updated: true).execute)
.to contain_exactly(environment)
end
end
context 'when last deployment to environment is not the most recent' do
before do
create(:deployment, :success, environment: environment, ref: 'feature')
create(:deployment, :success, environment: environment, ref: 'master')
end
it 'does not find environment' do
expect(described_class.new(project, user, ref: 'feature', recently_updated: true).execute)
.to be_empty
end
end
context 'when there are two environments that deploy to the same branch' do
let(:second_environment) { create(:environment, project: project) }
before do
create(:deployment, :success, environment: environment, ref: 'feature')
create(:deployment, :success, environment: second_environment, ref: 'feature')
end
it 'finds both environments' do
expect(described_class.new(project, user, ref: 'feature', recently_updated: true).execute)
.to contain_exactly(environment, second_environment)
end
end
end
end
describe '#find' do describe '#find' do
context 'with states parameter' do context 'with states parameter' do
let(:stopped_environment) { create(:environment, :stopped, project: project) } let(:stopped_environment) { create(:environment, :stopped, project: project) }

View file

@ -1,7 +1,8 @@
import { GlForm, GlFormInputGroup } from '@gitlab/ui'; import { GlForm, GlFormInputGroup, GlFormInput } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import axios from 'axios'; import axios from 'axios';
import AxiosMockAdapter from 'axios-mock-adapter'; import AxiosMockAdapter from 'axios-mock-adapter';
import { kebabCase } from 'lodash';
import createFlash from '~/flash'; import createFlash from '~/flash';
import httpStatus from '~/lib/utils/http_status'; import httpStatus from '~/lib/utils/http_status';
import * as urlUtility from '~/lib/utils/url_utility'; import * as urlUtility from '~/lib/utils/url_utility';
@ -59,6 +60,7 @@ describe('ForkForm component', () => {
}, },
stubs: { stubs: {
GlFormInputGroup, GlFormInputGroup,
GlFormInput,
}, },
}); });
}; };
@ -204,6 +206,37 @@ describe('ForkForm component', () => {
}); });
}); });
describe('project slug', () => {
const projectPath = 'some other project slug';
beforeEach(() => {
mockGetRequest();
createComponent({
projectPath,
});
});
it('initially loads slug without kebab-case transformation', () => {
expect(findForkSlugInput().attributes('value')).toBe(projectPath);
});
it('changes to kebab case when project name changes', async () => {
const newInput = `${projectPath}1`;
findForkNameInput().vm.$emit('input', newInput);
await wrapper.vm.$nextTick();
expect(findForkSlugInput().attributes('value')).toBe(kebabCase(newInput));
});
it('does not change to kebab case when project slug is changed manually', async () => {
const newInput = `${projectPath}1`;
findForkSlugInput().vm.$emit('input', newInput);
await wrapper.vm.$nextTick();
expect(findForkSlugInput().attributes('value')).toBe(newInput);
});
});
describe('visibility level', () => { describe('visibility level', () => {
it.each` it.each`
project | namespace | privateIsDisabled | internalIsDisabled | publicIsDisabled project | namespace | privateIsDisabled | internalIsDisabled | publicIsDisabled

View file

@ -87,6 +87,34 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
end end
end end
end end
context 'when an interval variance is given' do
let(:variance) { 2.seconds }
context 'when the last job is less than an interval with variance old' do
it 'returns false' do
freeze_time do
create(:batched_background_migration_job,
batched_migration: batched_migration,
created_at: Time.current - 1.minute - 57.seconds)
expect(batched_migration.interval_elapsed?(variance: variance)).to eq(false)
end
end
end
context 'when the last job is more than an interval with variance old' do
it 'returns true' do
freeze_time do
create(:batched_background_migration_job,
batched_migration: batched_migration,
created_at: Time.current - 1.minute - 58.seconds)
expect(batched_migration.interval_elapsed?(variance: variance)).to eq(true)
end
end
end
end
end end
end end

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sidebars::ContainerWithHtmlOptions do
subject do
Class.new do
include Sidebars::ContainerWithHtmlOptions
def title
'Foo'
end
end.new
end
describe '#container_html_options' do
it 'includes title attribute' do
expect(subject.container_html_options).to eq(title: 'Foo')
end
end
end

View file

@ -0,0 +1,59 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sidebars::PositionableList do
subject do
Class.new do
include Sidebars::PositionableList
end.new
end
describe '#add_element' do
it 'adds the element to the last position of the list' do
list = [1, 2]
subject.add_element(list, 3)
expect(list).to eq([1, 2, 3])
end
end
describe '#insert_element_before' do
let(:user) { build(:user) }
let(:list) { [1, user] }
it 'adds element before the specific element class' do
subject.insert_element_before(list, User, 2)
expect(list).to eq [1, 2, user]
end
context 'when reference element does not exist' do
it 'adds the element to the top of the list' do
subject.insert_element_before(list, Project, 2)
expect(list).to eq [2, 1, user]
end
end
end
describe '#insert_element_after' do
let(:user) { build(:user) }
let(:list) { [1, user] }
it 'adds element after the specific element class' do
subject.insert_element_after(list, Integer, 2)
expect(list).to eq [1, 2, user]
end
context 'when reference element does not exist' do
it 'adds the element to the end of the list' do
subject.insert_element_after(list, Project, 2)
expect(list).to eq [1, user, 2]
end
end
end
end

View file

@ -0,0 +1,67 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sidebars::Menu do
let(:menu) { described_class.new(context) }
let(:context) { Sidebars::Context.new(current_user: nil, container: nil) }
describe '#all_active_routes' do
it 'gathers all active routes of items and the current menu' do
menu_item1 = Sidebars::MenuItem.new(context)
menu_item2 = Sidebars::MenuItem.new(context)
menu_item3 = Sidebars::MenuItem.new(context)
menu.add_item(menu_item1)
menu.add_item(menu_item2)
menu.add_item(menu_item3)
allow(menu).to receive(:active_routes).and_return({ path: 'foo' })
allow(menu_item1).to receive(:active_routes).and_return({ path: %w(bar test) })
allow(menu_item2).to receive(:active_routes).and_return({ controller: 'fooc' })
allow(menu_item3).to receive(:active_routes).and_return({ controller: 'barc' })
expect(menu.all_active_routes).to eq({ path: %w(foo bar test), controller: %w(fooc barc) })
end
it 'does not include routes for non renderable items' do
menu_item = Sidebars::MenuItem.new(context)
menu.add_item(menu_item)
allow(menu).to receive(:active_routes).and_return({ path: 'foo' })
allow(menu_item).to receive(:render?).and_return(false)
allow(menu_item).to receive(:active_routes).and_return({ controller: 'bar' })
expect(menu.all_active_routes).to eq({ path: ['foo'] })
end
end
describe '#render?' do
context 'when the menus has no items' do
it 'returns true' do
expect(menu.render?).to be true
end
end
context 'when the menu has items' do
let(:menu_item) { Sidebars::MenuItem.new(context) }
before do
menu.add_item(menu_item)
end
context 'when items are not renderable' do
it 'returns false' do
allow(menu_item).to receive(:render?).and_return(false)
expect(menu.render?).to be false
end
end
context 'when there are renderable items' do
it 'returns true' do
expect(menu.render?).to be true
end
end
end
end
end

View file

@ -0,0 +1,34 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sidebars::Panel do
let(:context) { Sidebars::Context.new(current_user: nil, container: nil) }
let(:panel) { Sidebars::Panel.new(context) }
let(:menu1) { Sidebars::Menu.new(context) }
let(:menu2) { Sidebars::Menu.new(context) }
describe '#renderable_menus' do
it 'returns only renderable menus' do
panel.add_menu(menu1)
panel.add_menu(menu2)
allow(menu1).to receive(:render?).and_return(true)
allow(menu2).to receive(:render?).and_return(false)
expect(panel.renderable_menus).to eq([menu1])
end
end
describe '#has_renderable_menus?' do
it 'returns false when no renderable menus' do
expect(panel.has_renderable_menus?).to be false
end
it 'returns true when no renderable menus' do
panel.add_menu(menu1)
expect(panel.has_renderable_menus?).to be true
end
end
end

View file

@ -11,9 +11,10 @@ RSpec.describe MergeRequestPollWidgetEntity do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:request) { double('request', current_user: user, project: project) } let(:request) { double('request', current_user: user, project: project) }
let(:options) { {} }
subject do subject do
described_class.new(resource, request: request).as_json described_class.new(resource, { request: request }.merge(options)).as_json
end end
it 'has default_merge_commit_message_with_description' do it 'has default_merge_commit_message_with_description' do
@ -278,4 +279,39 @@ RSpec.describe MergeRequestPollWidgetEntity do
]) ])
end end
end end
describe '#mergeable' do
it 'shows whether a merge request is mergeable' do
expect(subject[:mergeable]).to eq(true)
end
context 'when merge request is in checking state' do
before do
resource.mark_as_unchecked!
resource.mark_as_checking!
end
it 'calculates mergeability and returns true' do
expect(subject[:mergeable]).to eq(true)
end
context 'when async_mergeability_check is passed' do
let(:options) { { async_mergeability_check: true } }
it 'returns false' do
expect(subject[:mergeable]).to eq(false)
end
context 'when check_mergeability_async_in_widget is disabled' do
before do
stub_feature_flags(check_mergeability_async_in_widget: false)
end
it 'calculates mergeability and returns true' do
expect(subject[:mergeable]).to eq(true)
end
end
end
end
end
end end

View file

@ -40,12 +40,13 @@ RSpec.describe Database::BatchedBackgroundMigrationWorker, '#perform', :clean_gi
let(:lease_timeout) { 15.minutes } let(:lease_timeout) { 15.minutes }
let(:lease_key) { 'batched_background_migration_worker' } let(:lease_key) { 'batched_background_migration_worker' }
let(:migration) { build(:batched_background_migration, :active, interval: job_interval) } let(:migration) { build(:batched_background_migration, :active, interval: job_interval) }
let(:interval_variance) { described_class::INTERVAL_VARIANCE }
before do before do
allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_migration) allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_migration)
.and_return(migration) .and_return(migration)
allow(migration).to receive(:interval_elapsed?).and_return(true) allow(migration).to receive(:interval_elapsed?).with(variance: interval_variance).and_return(true)
allow(migration).to receive(:reload) allow(migration).to receive(:reload)
end end
@ -66,7 +67,7 @@ RSpec.describe Database::BatchedBackgroundMigrationWorker, '#perform', :clean_gi
it 'does not run the migration' do it 'does not run the migration' do
expect_to_obtain_exclusive_lease(lease_key, timeout: lease_timeout) expect_to_obtain_exclusive_lease(lease_key, timeout: lease_timeout)
expect(migration).to receive(:interval_elapsed?).and_return(false) expect(migration).to receive(:interval_elapsed?).with(variance: interval_variance).and_return(false)
expect(worker).not_to receive(:run_active_migration) expect(worker).not_to receive(:run_active_migration)