From dc7cd84a3edeab2f799e3a99edf33bf2a381eee7 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 7 Apr 2021 09:09:06 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .rubocop_manual_todo.yml | 1 + .../forks/new/components/fork_form.vue | 5 + .../stores/mr_widget_store.js | 1 + app/controllers/projects/blame_controller.rb | 2 +- app/controllers/projects/blob_controller.rb | 2 +- app/controllers/projects/commit_controller.rb | 2 +- .../projects/compare_controller.rb | 2 +- .../merge_requests/content_controller.rb | 11 +- .../projects/merge_requests_controller.rb | 5 +- .../environments_by_deployments_finder.rb | 65 +++++++++ app/finders/environments_finder.rb | 54 -------- app/graphql/types/base_enum.rb | 3 +- .../sidebars/container_with_html_options.rb | 35 +++++ .../concerns/sidebars/has_active_routes.rb | 16 +++ app/models/concerns/sidebars/has_hint.rb | 16 +++ app/models/concerns/sidebars/has_icon.rb | 27 ++++ app/models/concerns/sidebars/has_pill.rb | 21 +++ .../concerns/sidebars/positionable_list.rb | 37 +++++ app/models/concerns/sidebars/renderable.rb | 12 ++ app/models/merge_request.rb | 4 +- app/models/sidebars/context.rb | 21 +++ app/models/sidebars/menu.rb | 82 +++++++++++ app/models/sidebars/menu_item.rb | 21 +++ app/models/sidebars/panel.rb | 55 ++++++++ ...merge_request_poll_cached_widget_entity.rb | 4 + .../merge_request_poll_widget_entity.rb | 7 +- .../merge_request_widget_entity.rb | 2 +- app/services/ci/stop_environments_service.rb | 2 +- .../projects/merge_requests/show.html.haml | 2 +- .../batched_background_migration_worker.rb | 3 +- .../fix-haml-promote-issue-weights.yml | 5 + ...olasdular-cleanup-signup-onboarding-ff.yml | 5 + .../check_mergeability_async_in_widget.yml | 8 ++ .../background_migration/batched_migration.rb | 7 +- scripts/trigger-build | 5 + .../merge_requests/content_controller_spec.rb | 19 ++- .../merge_requests_controller_spec.rb | 16 ++- ...environments_by_deployments_finder_spec.rb | 127 ++++++++++++++++++ spec/finders/environments_finder_spec.rb | 114 ---------------- .../forks/new/components/fork_form_spec.js | 35 ++++- .../batched_migration_spec.rb | 28 ++++ .../container_with_html_options_spec.rb | 21 +++ .../sidebars/positionable_list_spec.rb | 59 ++++++++ spec/models/sidebars/menu_spec.rb | 67 +++++++++ spec/models/sidebars/panel_spec.rb | 34 +++++ .../merge_request_poll_widget_entity_spec.rb | 38 +++++- ...atched_background_migration_worker_spec.rb | 5 +- 47 files changed, 917 insertions(+), 196 deletions(-) create mode 100644 app/finders/environments_by_deployments_finder.rb create mode 100644 app/models/concerns/sidebars/container_with_html_options.rb create mode 100644 app/models/concerns/sidebars/has_active_routes.rb create mode 100644 app/models/concerns/sidebars/has_hint.rb create mode 100644 app/models/concerns/sidebars/has_icon.rb create mode 100644 app/models/concerns/sidebars/has_pill.rb create mode 100644 app/models/concerns/sidebars/positionable_list.rb create mode 100644 app/models/concerns/sidebars/renderable.rb create mode 100644 app/models/sidebars/context.rb create mode 100644 app/models/sidebars/menu.rb create mode 100644 app/models/sidebars/menu_item.rb create mode 100644 app/models/sidebars/panel.rb create mode 100644 changelogs/unreleased/fix-haml-promote-issue-weights.yml create mode 100644 changelogs/unreleased/nicolasdular-cleanup-signup-onboarding-ff.yml create mode 100644 config/feature_flags/development/check_mergeability_async_in_widget.yml create mode 100644 spec/finders/environments_by_deployments_finder_spec.rb create mode 100644 spec/models/concerns/sidebars/container_with_html_options_spec.rb create mode 100644 spec/models/concerns/sidebars/positionable_list_spec.rb create mode 100644 spec/models/sidebars/menu_spec.rb create mode 100644 spec/models/sidebars/panel_spec.rb diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index 30d5454ee58..96b2a6a1abc 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -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' diff --git a/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue b/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue index 7112b23775d..e20cf299c2a 100644 --- a/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue +++ b/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue @@ -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(); diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index bdecc66855b..f57b638dd81 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -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; diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb index d5de0d38152..2c7c49b4250 100644 --- a/app/controllers/projects/blame_controller.rb +++ b/app/controllers/projects/blame_controller.rb @@ -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! diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 0080ae1a5be..a398fc56a35 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -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) diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 0c78f239523..fdf66340cbb 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -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 diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 81f80d37662..221bc16e256 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -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 diff --git a/app/controllers/projects/merge_requests/content_controller.rb b/app/controllers/projects/merge_requests/content_controller.rb index 399745151b1..dfc060c9204 100644 --- a/app/controllers/projects/merge_requests/content_controller.rb +++ b/app/controllers/projects/merge_requests/content_controller.rb @@ -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 diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index eb639cca127..bc1b364b73b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -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 diff --git a/app/finders/environments_by_deployments_finder.rb b/app/finders/environments_by_deployments_finder.rb new file mode 100644 index 00000000000..7c0fb4c5424 --- /dev/null +++ b/app/finders/environments_by_deployments_finder.rb @@ -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 diff --git a/app/finders/environments_finder.rb b/app/finders/environments_finder.rb index 32ca1a42db7..62de2c6ecc8 100644 --- a/app/finders/environments_finder.rb +++ b/app/finders/environments_finder.rb @@ -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]) diff --git a/app/graphql/types/base_enum.rb b/app/graphql/types/base_enum.rb index 24a0ae2969f..518a902a5d7 100644 --- a/app/graphql/types/base_enum.rb +++ b/app/graphql/types/base_enum.rb @@ -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 diff --git a/app/models/concerns/sidebars/container_with_html_options.rb b/app/models/concerns/sidebars/container_with_html_options.rb new file mode 100644 index 00000000000..911540f25f8 --- /dev/null +++ b/app/models/concerns/sidebars/container_with_html_options.rb @@ -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 diff --git a/app/models/concerns/sidebars/has_active_routes.rb b/app/models/concerns/sidebars/has_active_routes.rb new file mode 100644 index 00000000000..e7a153f067a --- /dev/null +++ b/app/models/concerns/sidebars/has_active_routes.rb @@ -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 diff --git a/app/models/concerns/sidebars/has_hint.rb b/app/models/concerns/sidebars/has_hint.rb new file mode 100644 index 00000000000..21dca39dca0 --- /dev/null +++ b/app/models/concerns/sidebars/has_hint.rb @@ -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 diff --git a/app/models/concerns/sidebars/has_icon.rb b/app/models/concerns/sidebars/has_icon.rb new file mode 100644 index 00000000000..d1a87918285 --- /dev/null +++ b/app/models/concerns/sidebars/has_icon.rb @@ -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 diff --git a/app/models/concerns/sidebars/has_pill.rb b/app/models/concerns/sidebars/has_pill.rb new file mode 100644 index 00000000000..ad7064fe63d --- /dev/null +++ b/app/models/concerns/sidebars/has_pill.rb @@ -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 diff --git a/app/models/concerns/sidebars/positionable_list.rb b/app/models/concerns/sidebars/positionable_list.rb new file mode 100644 index 00000000000..30830d547f3 --- /dev/null +++ b/app/models/concerns/sidebars/positionable_list.rb @@ -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 diff --git a/app/models/concerns/sidebars/renderable.rb b/app/models/concerns/sidebars/renderable.rb new file mode 100644 index 00000000000..a3976af8515 --- /dev/null +++ b/app/models/concerns/sidebars/renderable.rb @@ -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 diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 2344d93e8e7..8f2ec2d6b88 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -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 diff --git a/app/models/sidebars/context.rb b/app/models/sidebars/context.rb new file mode 100644 index 00000000000..d9ac2705aaf --- /dev/null +++ b/app/models/sidebars/context.rb @@ -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 diff --git a/app/models/sidebars/menu.rb b/app/models/sidebars/menu.rb new file mode 100644 index 00000000000..a5c8be2bb31 --- /dev/null +++ b/app/models/sidebars/menu.rb @@ -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 diff --git a/app/models/sidebars/menu_item.rb b/app/models/sidebars/menu_item.rb new file mode 100644 index 00000000000..7466b31898e --- /dev/null +++ b/app/models/sidebars/menu_item.rb @@ -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 diff --git a/app/models/sidebars/panel.rb b/app/models/sidebars/panel.rb new file mode 100644 index 00000000000..8fa421defc6 --- /dev/null +++ b/app/models/sidebars/panel.rb @@ -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 diff --git a/app/serializers/merge_request_poll_cached_widget_entity.rb b/app/serializers/merge_request_poll_cached_widget_entity.rb index 845d7a0c52a..c47bcaf00e0 100644 --- a/app/serializers/merge_request_poll_cached_widget_entity.rb +++ b/app/serializers/merge_request_poll_cached_widget_entity.rb @@ -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 diff --git a/app/serializers/merge_request_poll_widget_entity.rb b/app/serializers/merge_request_poll_widget_entity.rb index fc064a3a3b1..8f1a058af86 100644 --- a/app/serializers/merge_request_poll_widget_entity.rb +++ b/app/serializers/merge_request_poll_widget_entity.rb @@ -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) diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb index 3ed7d9d8914..a168c7a8490 100644 --- a/app/serializers/merge_request_widget_entity.rb +++ b/app/serializers/merge_request_widget_entity.rb @@ -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| diff --git a/app/services/ci/stop_environments_service.rb b/app/services/ci/stop_environments_service.rb index b6c5b398cb1..81457130fa0 100644 --- a/app/services/ci/stop_environments_service.rb +++ b/app/services/ci/stop_environments_service.rb @@ -35,7 +35,7 @@ module Ci private def environments - @environments ||= EnvironmentsFinder + @environments ||= EnvironmentsByDeploymentsFinder .new(project, current_user, ref: @ref, recently_updated: true) .execute end diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index 14a67b721f5..92bc1874c97 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -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, diff --git a/app/workers/database/batched_background_migration_worker.rb b/app/workers/database/batched_background_migration_worker.rb index 5af2f713787..de274d58ad7 100644 --- a/app/workers/database/batched_background_migration_worker.rb +++ b/app/workers/database/batched_background_migration_worker.rb @@ -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 diff --git a/changelogs/unreleased/fix-haml-promote-issue-weights.yml b/changelogs/unreleased/fix-haml-promote-issue-weights.yml new file mode 100644 index 00000000000..1dd6cc02657 --- /dev/null +++ b/changelogs/unreleased/fix-haml-promote-issue-weights.yml @@ -0,0 +1,5 @@ +--- +title: Fix HAML in _promote_issue_weights.html.haml +merge_request: 58546 +author: Yogi (@yo) +type: changed diff --git a/changelogs/unreleased/nicolasdular-cleanup-signup-onboarding-ff.yml b/changelogs/unreleased/nicolasdular-cleanup-signup-onboarding-ff.yml new file mode 100644 index 00000000000..71a65258ff5 --- /dev/null +++ b/changelogs/unreleased/nicolasdular-cleanup-signup-onboarding-ff.yml @@ -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 diff --git a/config/feature_flags/development/check_mergeability_async_in_widget.yml b/config/feature_flags/development/check_mergeability_async_in_widget.yml new file mode 100644 index 00000000000..ff8116c3a65 --- /dev/null +++ b/config/feature_flags/development/check_mergeability_async_in_widget.yml @@ -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 diff --git a/lib/gitlab/database/background_migration/batched_migration.rb b/lib/gitlab/database/background_migration/batched_migration.rb index fe5d20ed41a..324695adb1f 100644 --- a/lib/gitlab/database/background_migration/batched_migration.rb +++ b/lib/gitlab/database/background_migration/batched_migration.rb @@ -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) diff --git a/scripts/trigger-build b/scripts/trigger-build index c81af332b0e..3a9301bda3b 100755 --- a/scripts/trigger-build +++ b/scripts/trigger-build @@ -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'], diff --git a/spec/controllers/projects/merge_requests/content_controller_spec.rb b/spec/controllers/projects/merge_requests/content_controller_spec.rb index 67d3ef6f4f0..0eaa528a330 100644 --- a/spec/controllers/projects/merge_requests/content_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/content_controller_spec.rb @@ -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) diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 6644373f758..82b969cad40 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -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 diff --git a/spec/finders/environments_by_deployments_finder_spec.rb b/spec/finders/environments_by_deployments_finder_spec.rb new file mode 100644 index 00000000000..f5fcc4ef72a --- /dev/null +++ b/spec/finders/environments_by_deployments_finder_spec.rb @@ -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 diff --git a/spec/finders/environments_finder_spec.rb b/spec/finders/environments_finder_spec.rb index fd714ab9a8f..888a1ef9567 100644 --- a/spec/finders/environments_finder_spec.rb +++ b/spec/finders/environments_finder_spec.rb @@ -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) } diff --git a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js index 694a0c2b9c1..2992c7f0624 100644 --- a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js +++ b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js @@ -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 diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb index bb1d45eb6c2..a7f29da7962 100644 --- a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb @@ -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 diff --git a/spec/models/concerns/sidebars/container_with_html_options_spec.rb b/spec/models/concerns/sidebars/container_with_html_options_spec.rb new file mode 100644 index 00000000000..c555945e121 --- /dev/null +++ b/spec/models/concerns/sidebars/container_with_html_options_spec.rb @@ -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 diff --git a/spec/models/concerns/sidebars/positionable_list_spec.rb b/spec/models/concerns/sidebars/positionable_list_spec.rb new file mode 100644 index 00000000000..231aa5295dd --- /dev/null +++ b/spec/models/concerns/sidebars/positionable_list_spec.rb @@ -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 diff --git a/spec/models/sidebars/menu_spec.rb b/spec/models/sidebars/menu_spec.rb new file mode 100644 index 00000000000..320f5f1ad1e --- /dev/null +++ b/spec/models/sidebars/menu_spec.rb @@ -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 diff --git a/spec/models/sidebars/panel_spec.rb b/spec/models/sidebars/panel_spec.rb new file mode 100644 index 00000000000..0e539460810 --- /dev/null +++ b/spec/models/sidebars/panel_spec.rb @@ -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 diff --git a/spec/serializers/merge_request_poll_widget_entity_spec.rb b/spec/serializers/merge_request_poll_widget_entity_spec.rb index 9618624f226..366211731cc 100644 --- a/spec/serializers/merge_request_poll_widget_entity_spec.rb +++ b/spec/serializers/merge_request_poll_widget_entity_spec.rb @@ -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 diff --git a/spec/workers/database/batched_background_migration_worker_spec.rb b/spec/workers/database/batched_background_migration_worker_spec.rb index e71debadcb8..b13d1f5c7aa 100644 --- a/spec/workers/database/batched_background_migration_worker_spec.rb +++ b/spec/workers/database/batched_background_migration_worker_spec.rb @@ -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)