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/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'
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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!
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
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
|
@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])
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
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)
|
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
|
||||||
|
|
||||||
|
|
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
|
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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
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
|
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)
|
||||||
|
|
|
@ -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'],
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
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)
|
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) }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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_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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue