Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e28ed4a6b2
commit
dc7cd84a3e
47 changed files with 917 additions and 196 deletions
|
@ -2010,6 +2010,7 @@ Gitlab/NamespacedClass:
|
|||
- 'app/finders/deployments_finder.rb'
|
||||
- 'app/finders/environment_names_finder.rb'
|
||||
- 'app/finders/environments_finder.rb'
|
||||
- 'app/finders/environments_by_deployments_finder.rb'
|
||||
- 'app/finders/events_finder.rb'
|
||||
- 'app/finders/feature_flags_finder.rb'
|
||||
- 'app/finders/feature_flags_user_lists_finder.rb'
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
GlFormRadioGroup,
|
||||
GlFormSelect,
|
||||
} from '@gitlab/ui';
|
||||
import { kebabCase } from 'lodash';
|
||||
import { buildApiUrl } from '~/api/api_utils';
|
||||
import createFlash from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
@ -145,6 +146,10 @@ export default {
|
|||
this.fork.visibility = visibility;
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line func-names
|
||||
'fork.name': function (newVal) {
|
||||
this.fork.slug = kebabCase(newVal);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchNamespaces();
|
||||
|
|
|
@ -60,6 +60,7 @@ export default class MergeRequestStore {
|
|||
this.rebaseInProgress = data.rebase_in_progress;
|
||||
this.mergeRequestDiffsPath = data.diffs_path;
|
||||
this.approvalsWidgetType = data.approvals_widget_type;
|
||||
this.mergeRequestWidgetPath = data.merge_request_widget_path;
|
||||
|
||||
if (data.issues_links) {
|
||||
const links = data.issues_links;
|
||||
|
|
|
@ -20,7 +20,7 @@ class Projects::BlameController < Projects::ApplicationController
|
|||
|
||||
environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
|
||||
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::View::Presenter::Factory.new(@blame, project: @project, path: @path).fabricate!
|
||||
|
|
|
@ -214,7 +214,7 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
def show_html
|
||||
environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit }
|
||||
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)
|
||||
@code_navigation_path = Gitlab::CodeNavigationPath.new(@project, @blob.commit_id).full_json_path_for(@blob.path)
|
||||
|
||||
|
|
|
@ -168,7 +168,7 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
@diffs = commit.diffs(opts)
|
||||
@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
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
|
|
|
@ -132,7 +132,7 @@ class Projects::CompareController < Projects::ApplicationController
|
|||
if compare
|
||||
environment_params = source_project.repository.branch_exists?(head_ref) ? { ref: head_ref } : { commit: compare.commit }
|
||||
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
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ class Projects::MergeRequests::ContentController < Projects::MergeRequests::Appl
|
|||
SLOW_POLLING_INTERVAL = 5.minutes.in_milliseconds
|
||||
|
||||
def widget
|
||||
check_mergeability_async!
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: serializer(MergeRequestPollWidgetEntity)
|
||||
|
@ -38,6 +40,13 @@ class Projects::MergeRequests::ContentController < Projects::MergeRequests::Appl
|
|||
|
||||
def serializer(entity)
|
||||
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
|
||||
|
|
|
@ -92,7 +92,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
|
||||
def show
|
||||
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|
|
||||
format.html do
|
||||
|
|
65
app/finders/environments_by_deployments_finder.rb
Normal file
65
app/finders/environments_by_deployments_finder.rb
Normal 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
|
|
@ -9,34 +9,6 @@ class EnvironmentsFinder
|
|||
@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
|
||||
|
||||
# This method will eventually take the place of `#execute` as an
|
||||
# efficient way to get relevant environment entries.
|
||||
# Currently, `#execute` method has a serious technical debt and
|
||||
|
@ -55,32 +27,6 @@ class EnvironmentsFinder
|
|||
|
||||
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)
|
||||
if params[:name].present?
|
||||
environments.for_name(params[:name])
|
||||
|
|
|
@ -22,8 +22,7 @@ module Types
|
|||
description(enum_mod.description) if use_description
|
||||
|
||||
enum_mod.definition.each do |key, content|
|
||||
desc = content.delete(:description)
|
||||
value(key.to_s.upcase, description: desc, **content)
|
||||
value(key.to_s.upcase, **content)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
35
app/models/concerns/sidebars/container_with_html_options.rb
Normal file
35
app/models/concerns/sidebars/container_with_html_options.rb
Normal 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
|
16
app/models/concerns/sidebars/has_active_routes.rb
Normal file
16
app/models/concerns/sidebars/has_active_routes.rb
Normal 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
|
16
app/models/concerns/sidebars/has_hint.rb
Normal file
16
app/models/concerns/sidebars/has_hint.rb
Normal 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
|
27
app/models/concerns/sidebars/has_icon.rb
Normal file
27
app/models/concerns/sidebars/has_icon.rb
Normal 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
|
21
app/models/concerns/sidebars/has_pill.rb
Normal file
21
app/models/concerns/sidebars/has_pill.rb
Normal 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
|
37
app/models/concerns/sidebars/positionable_list.rb
Normal file
37
app/models/concerns/sidebars/positionable_list.rb
Normal 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
|
12
app/models/concerns/sidebars/renderable.rb
Normal file
12
app/models/concerns/sidebars/renderable.rb
Normal 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
|
|
@ -1367,11 +1367,11 @@ class MergeRequest < ApplicationRecord
|
|||
def environments_for(current_user, latest: false)
|
||||
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
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
|
|
21
app/models/sidebars/context.rb
Normal file
21
app/models/sidebars/context.rb
Normal 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
|
82
app/models/sidebars/menu.rb
Normal file
82
app/models/sidebars/menu.rb
Normal 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
|
21
app/models/sidebars/menu_item.rb
Normal file
21
app/models/sidebars/menu_item.rb
Normal 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
|
55
app/models/sidebars/panel.rb
Normal file
55
app/models/sidebars/panel.rb
Normal 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
|
|
@ -64,6 +64,10 @@ class MergeRequestPollCachedWidgetEntity < IssuableEntity
|
|||
presenter(merge_request).target_branch_commits_path
|
||||
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|
|
||||
presenter(merge_request).target_branch_tree_path
|
||||
end
|
||||
|
|
|
@ -29,7 +29,12 @@ class MergeRequestPollWidgetEntity < Grape::Entity
|
|||
|
||||
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|
|
||||
merge_request.default_merge_commit_message(include_description: true)
|
||||
|
|
|
@ -36,7 +36,7 @@ class MergeRequestWidgetEntity < Grape::Entity
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
expose :merge_request_cached_widget_path do |merge_request|
|
||||
|
|
|
@ -35,7 +35,7 @@ module Ci
|
|||
private
|
||||
|
||||
def environments
|
||||
@environments ||= EnvironmentsFinder
|
||||
@environments ||= EnvironmentsByDeploymentsFinder
|
||||
.new(project, current_user, ref: @ref, recently_updated: true)
|
||||
.execute
|
||||
end
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
- add_page_startup_api_call notes_url
|
||||
- else
|
||||
- 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)
|
||||
#js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request, Feature.enabled?(:paginated_notes, @project)).to_json,
|
||||
endpoint_metadata: @endpoint_metadata_url,
|
||||
|
|
|
@ -10,6 +10,7 @@ module Database
|
|||
|
||||
LEASE_TIMEOUT_MULTIPLIER = 3
|
||||
MINIMUM_LEASE_TIMEOUT = 10.minutes.freeze
|
||||
INTERVAL_VARIANCE = 5.seconds.freeze
|
||||
|
||||
def perform
|
||||
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
|
||||
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
|
||||
|
||||
|
|
5
changelogs/unreleased/fix-haml-promote-issue-weights.yml
Normal file
5
changelogs/unreleased/fix-haml-promote-issue-weights.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix HAML in _promote_issue_weights.html.haml
|
||||
merge_request: 58546
|
||||
author: Yogi (@yo)
|
||||
type: changed
|
|
@ -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
|
|
@ -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
|
|
@ -27,8 +27,11 @@ module Gitlab
|
|||
active.queue_order.first
|
||||
end
|
||||
|
||||
def interval_elapsed?
|
||||
last_job.nil? || last_job.created_at <= Time.current - interval
|
||||
def interval_elapsed?(variance: 0)
|
||||
return true unless last_job
|
||||
|
||||
interval_with_variance = interval - variance
|
||||
last_job.created_at <= Time.current - interval_with_variance
|
||||
end
|
||||
|
||||
def create_batched_job!(min, max)
|
||||
|
|
|
@ -258,6 +258,11 @@ module Trigger
|
|||
ENV['DOCS_BRANCH'] || 'master'
|
||||
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
|
||||
{
|
||||
"BRANCH_#{project_slug.upcase}" => ENV['CI_COMMIT_REF_NAME'],
|
||||
|
|
|
@ -11,13 +11,13 @@ RSpec.describe Projects::MergeRequests::ContentController do
|
|||
sign_in(user)
|
||||
end
|
||||
|
||||
def do_request(action = :cached_widget)
|
||||
def do_request(action = :cached_widget, params = {})
|
||||
get action, params: {
|
||||
namespace_id: project.namespace.to_param,
|
||||
project_id: project,
|
||||
id: merge_request.iid,
|
||||
format: :json
|
||||
}
|
||||
}.merge(params)
|
||||
end
|
||||
|
||||
context 'user has access to the project' do
|
||||
|
@ -42,6 +42,10 @@ RSpec.describe Projects::MergeRequests::ContentController do
|
|||
end
|
||||
|
||||
describe 'GET widget' do
|
||||
before do
|
||||
merge_request.mark_as_unchecked!
|
||||
end
|
||||
|
||||
it 'checks whether the MR can be merged' do
|
||||
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')
|
||||
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
|
||||
let(:merge_request) do
|
||||
create(:merged_merge_request, :with_test_reports, target_project: project, source_project: project)
|
||||
|
|
|
@ -82,13 +82,19 @@ RSpec.describe Projects::MergeRequestsController do
|
|||
merge_request.mark_as_unchecked!
|
||||
end
|
||||
|
||||
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)
|
||||
context 'check_mergeability_async_in_widget feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(check_mergeability_async_in_widget: false)
|
||||
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
|
||||
|
||||
|
|
127
spec/finders/environments_by_deployments_finder_spec.rb
Normal file
127
spec/finders/environments_by_deployments_finder_spec.rb
Normal 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
|
|
@ -11,120 +11,6 @@ RSpec.describe EnvironmentsFinder 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
|
||||
|
||||
describe '#find' do
|
||||
context 'with states parameter' do
|
||||
let(:stopped_environment) { create(:environment, :stopped, project: project) }
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { GlForm, GlFormInputGroup } from '@gitlab/ui';
|
||||
import { GlForm, GlFormInputGroup, GlFormInput } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import axios from 'axios';
|
||||
import AxiosMockAdapter from 'axios-mock-adapter';
|
||||
import { kebabCase } from 'lodash';
|
||||
import createFlash from '~/flash';
|
||||
import httpStatus from '~/lib/utils/http_status';
|
||||
import * as urlUtility from '~/lib/utils/url_utility';
|
||||
|
@ -59,6 +60,7 @@ describe('ForkForm component', () => {
|
|||
},
|
||||
stubs: {
|
||||
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', () => {
|
||||
it.each`
|
||||
project | namespace | privateIsDisabled | internalIsDisabled | publicIsDisabled
|
||||
|
|
|
@ -87,6 +87,34 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
|
|||
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
|
||||
|
||||
|
|
|
@ -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
|
59
spec/models/concerns/sidebars/positionable_list_spec.rb
Normal file
59
spec/models/concerns/sidebars/positionable_list_spec.rb
Normal 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
|
67
spec/models/sidebars/menu_spec.rb
Normal file
67
spec/models/sidebars/menu_spec.rb
Normal 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
|
34
spec/models/sidebars/panel_spec.rb
Normal file
34
spec/models/sidebars/panel_spec.rb
Normal 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
|
|
@ -11,9 +11,10 @@ RSpec.describe MergeRequestPollWidgetEntity do
|
|||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:request) { double('request', current_user: user, project: project) }
|
||||
let(:options) { {} }
|
||||
|
||||
subject do
|
||||
described_class.new(resource, request: request).as_json
|
||||
described_class.new(resource, { request: request }.merge(options)).as_json
|
||||
end
|
||||
|
||||
it 'has default_merge_commit_message_with_description' do
|
||||
|
@ -278,4 +279,39 @@ RSpec.describe MergeRequestPollWidgetEntity do
|
|||
])
|
||||
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
|
||||
|
|
|
@ -40,12 +40,13 @@ RSpec.describe Database::BatchedBackgroundMigrationWorker, '#perform', :clean_gi
|
|||
let(:lease_timeout) { 15.minutes }
|
||||
let(:lease_key) { 'batched_background_migration_worker' }
|
||||
let(:migration) { build(:batched_background_migration, :active, interval: job_interval) }
|
||||
let(:interval_variance) { described_class::INTERVAL_VARIANCE }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Database::BackgroundMigration::BatchedMigration).to receive(:active_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)
|
||||
end
|
||||
|
||||
|
@ -66,7 +67,7 @@ RSpec.describe Database::BatchedBackgroundMigrationWorker, '#perform', :clean_gi
|
|||
it 'does not run the migration' do
|
||||
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)
|
||||
|
||||
|
|
Loading…
Reference in a new issue