Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b0d4724e47
commit
6c346448fd
|
@ -3,14 +3,13 @@
|
|||
# https://docs.gitlab.com/ee/install/requirements.html#supported-web-browsers
|
||||
# with the following reasoning:
|
||||
#
|
||||
# - We should support the latest ESR of Firefox: 78, because it used quite a lot.
|
||||
# - We use Edge/Chrome >= 84 because 83 had an annoying bug which would mean we
|
||||
# need to polyfill Array.reduce: https://bugs.chromium.org/p/chromium/issues/detail?id=1049982
|
||||
# - Safari 13.1 because it is the current minor version of the previous major version
|
||||
# - We should support the latest ESR of Firefox: 91, because it used quite a lot.
|
||||
# - We use Edge/Chrome >= 92 because they are about as old as the Firefox ESR
|
||||
# - Safari 14.1 because it is the current minor version of the previous major version
|
||||
#
|
||||
# See also this epic: https://gitlab.com/groups/gitlab-org/-/epics/3957
|
||||
#
|
||||
chrome >= 84
|
||||
edge >= 84
|
||||
firefox >= 78
|
||||
safari >= 13.1
|
||||
chrome >= 92
|
||||
edge >= 92
|
||||
firefox >= 91
|
||||
safari >= 14.1
|
|
@ -79,7 +79,7 @@
|
|||
policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
|
||||
|
||||
.assets-cache: &assets-cache
|
||||
key: "assets-debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}-node-${NODE_ENV}"
|
||||
key: "assets-debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}-node-${NODE_ENV}-v2"
|
||||
paths:
|
||||
- assets-hash.txt
|
||||
- public/assets/webpack/
|
||||
|
|
|
@ -296,7 +296,7 @@
|
|||
@include media-breakpoint-up(lg) {
|
||||
padding: 0;
|
||||
|
||||
form {
|
||||
.issuable-context-form {
|
||||
--initial-top: calc(#{$header-height} + #{$mr-tabs-height});
|
||||
--top: var(--initial-top);
|
||||
|
||||
|
|
|
@ -2056,7 +2056,6 @@ body.gl-dark {
|
|||
--nav-active-bg: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
.tab-width-8 {
|
||||
-moz-tab-size: 8;
|
||||
tab-size: 8;
|
||||
}
|
||||
.gl-sr-only {
|
||||
|
|
|
@ -1698,7 +1698,6 @@ svg.s16 {
|
|||
}
|
||||
|
||||
.tab-width-8 {
|
||||
-moz-tab-size: 8;
|
||||
tab-size: 8;
|
||||
}
|
||||
.gl-sr-only {
|
||||
|
|
|
@ -366,3 +366,7 @@ to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1709
|
|||
/* stylelint-disable property-no-vendor-prefix */
|
||||
-webkit-backdrop-filter: blur(2px); // still required by Safari
|
||||
}
|
||||
|
||||
.gl-flex-flow-row-wrap {
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
|
|||
|
||||
before_action :finder, only: [:edit, :update, :destroy]
|
||||
|
||||
feature_category :navigation
|
||||
feature_category :onboarding
|
||||
urgency :low
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
|
|
|
@ -17,6 +17,7 @@ module Ci
|
|||
search!
|
||||
filter_by_active!
|
||||
filter_by_status!
|
||||
filter_by_upgrade_status!
|
||||
filter_by_runner_type!
|
||||
filter_by_tag_list!
|
||||
sort!
|
||||
|
@ -67,6 +68,13 @@ module Ci
|
|||
filter_by!(:status_status, Ci::Runner::AVAILABLE_STATUSES)
|
||||
end
|
||||
|
||||
def filter_by_upgrade_status!
|
||||
return unless @params.key?(:upgrade_status)
|
||||
return unless Ci::RunnerVersion.statuses.key?(@params[:upgrade_status])
|
||||
|
||||
@runners = @runners.with_upgrade_status(@params[:upgrade_status])
|
||||
end
|
||||
|
||||
def filter_by_runner_type!
|
||||
filter_by!(:type_type, Ci::Runner::AVAILABLE_TYPES)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module WorkItems
|
||||
module Widgetable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def extract_widget_params(work_item_type, attributes)
|
||||
# Get the list of widgets for the work item's type to extract only the supported attributes
|
||||
widget_keys = work_item_type.widgets.map(&:api_symbol)
|
||||
widget_params = attributes.extract!(*widget_keys)
|
||||
|
||||
# Cannot use prepare to use `.to_h` on each input due to
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87472#note_945199865
|
||||
widget_params.transform_values { |values| values.to_h }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,6 +7,7 @@ module Mutations
|
|||
|
||||
include Mutations::SpamProtection
|
||||
include FindsProject
|
||||
include Mutations::WorkItems::Widgetable
|
||||
|
||||
description "Creates a work item. Available only when feature flag `work_items` is enabled."
|
||||
|
||||
|
@ -15,6 +16,9 @@ module Mutations
|
|||
argument :description, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: copy_field_description(Types::WorkItemType, :description)
|
||||
argument :hierarchy_widget, ::Types::WorkItems::Widgets::HierarchyCreateInputType,
|
||||
required: false,
|
||||
description: 'Input for hierarchy widget.'
|
||||
argument :project_path, GraphQL::Types::ID,
|
||||
required: true,
|
||||
description: 'Full path of the project the work item is associated with.'
|
||||
|
@ -36,10 +40,18 @@ module Mutations
|
|||
return { errors: ['`work_items` feature flag disabled for this project'] }
|
||||
end
|
||||
|
||||
params = global_id_compatibility_params(attributes).merge(author_id: current_user.id)
|
||||
|
||||
spam_params = ::Spam::SpamParams.new_from_request(request: context[:request])
|
||||
create_result = ::WorkItems::CreateService.new(project: project, current_user: current_user, params: params, spam_params: spam_params).execute
|
||||
params = global_id_compatibility_params(attributes).merge(author_id: current_user.id)
|
||||
type = ::WorkItems::Type.find(attributes[:work_item_type_id])
|
||||
widget_params = extract_widget_params(type, params)
|
||||
|
||||
create_result = ::WorkItems::CreateService.new(
|
||||
project: project,
|
||||
current_user: current_user,
|
||||
params: params,
|
||||
spam_params: spam_params,
|
||||
widget_params: widget_params
|
||||
).execute
|
||||
|
||||
check_spam_action_response!(create_result[:work_item]) if create_result[:work_item]
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ module Mutations
|
|||
|
||||
include Mutations::SpamProtection
|
||||
include Mutations::WorkItems::UpdateArguments
|
||||
include Mutations::WorkItems::Widgetable
|
||||
|
||||
authorize :update_work_item
|
||||
|
||||
|
@ -24,7 +25,7 @@ module Mutations
|
|||
end
|
||||
|
||||
spam_params = ::Spam::SpamParams.new_from_request(request: context[:request])
|
||||
widget_params = extract_widget_params(work_item, attributes)
|
||||
widget_params = extract_widget_params(work_item.work_item_type, attributes)
|
||||
|
||||
update_result = ::WorkItems::UpdateService.new(
|
||||
project: work_item.project,
|
||||
|
@ -47,16 +48,6 @@ module Mutations
|
|||
def find_object(id:)
|
||||
GitlabSchema.find_by_gid(id)
|
||||
end
|
||||
|
||||
def extract_widget_params(work_item, attributes)
|
||||
# Get the list of widgets for the work item's type to extract only the supported attributes
|
||||
widget_keys = work_item.work_item_type.widgets.map(&:api_symbol)
|
||||
widget_params = attributes.extract!(*widget_keys)
|
||||
|
||||
# Cannot use prepare to use `.to_h` on each input due to
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87472#note_945199865
|
||||
widget_params.transform_values { |values| values.to_h }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,6 +36,10 @@ module Resolvers
|
|||
required: false,
|
||||
description: 'Sort order of results.'
|
||||
|
||||
argument :upgrade_status, ::Types::Ci::RunnerUpgradeStatusTypeEnum,
|
||||
required: false,
|
||||
description: 'Filter by upgrade status.'
|
||||
|
||||
def resolve_with_lookahead(**args)
|
||||
apply_lookahead(
|
||||
::Ci::RunnersFinder
|
||||
|
@ -54,6 +58,7 @@ module Resolvers
|
|||
status_status: params[:status]&.to_s,
|
||||
type_type: params[:type],
|
||||
tag_name: params[:tag_list],
|
||||
upgrade_status: params[:upgrade_status],
|
||||
search: params[:search],
|
||||
sort: params[:sort]&.to_s,
|
||||
preload: {
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module WorkItems
|
||||
module Widgets
|
||||
class HierarchyCreateInputType < BaseInputObject
|
||||
graphql_name 'WorkItemWidgetHierarchyCreateInput'
|
||||
|
||||
argument :parent_id, ::Types::GlobalIDType[::WorkItem],
|
||||
required: false,
|
||||
description: 'Global ID of the parent work item.',
|
||||
prepare: ->(id, _) { id&.model_id }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -16,7 +16,7 @@ module Nav
|
|||
menu_sections.push(general_menu_section)
|
||||
|
||||
{
|
||||
title: _("Create new"),
|
||||
title: _("Create new..."),
|
||||
menu_sections: menu_sections.select { |x| x.fetch(:menu_items).any? }
|
||||
}
|
||||
end
|
||||
|
|
|
@ -78,6 +78,7 @@ module Ci
|
|||
has_many :groups, through: :runner_namespaces, disable_joins: true
|
||||
|
||||
has_one :last_build, -> { order('id DESC') }, class_name: 'Ci::Build'
|
||||
has_one :runner_version, primary_key: :version, foreign_key: :version, class_name: 'Ci::RunnerVersion'
|
||||
|
||||
before_save :ensure_token
|
||||
|
||||
|
@ -475,6 +476,10 @@ module Ci
|
|||
|
||||
private
|
||||
|
||||
scope :with_upgrade_status, ->(upgrade_status) do
|
||||
Ci::Runner.joins(:runner_version).where(runner_version: { status: upgrade_status })
|
||||
end
|
||||
|
||||
EXECUTOR_NAME_TO_TYPES = {
|
||||
'unknown' => :unknown,
|
||||
'custom' => :custom,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class WorkItem < Issue
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
self.table_name = 'issues'
|
||||
self.inheritance_column = :_type_disabled
|
||||
|
||||
|
@ -23,8 +25,10 @@ class WorkItem < Issue
|
|||
end
|
||||
|
||||
def widgets
|
||||
work_item_type.widgets.map do |widget_class|
|
||||
widget_class.new(self)
|
||||
strong_memoize(:widgets) do
|
||||
work_item_type.widgets.map do |widget_class|
|
||||
widget_class.new(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WorkItems
|
||||
module WidgetableService
|
||||
def execute_widgets(work_item:, callback:, widget_params: {})
|
||||
work_item.widgets.each do |widget|
|
||||
widget_service(widget).try(callback, params: widget_params[widget.class.api_symbol])
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
def widget_service(widget)
|
||||
@widget_services ||= {}
|
||||
return @widget_services[widget] if @widget_services.has_key?(widget)
|
||||
|
||||
@widget_services[widget] = widget_service_class(widget)&.new(widget: widget, current_user: current_user)
|
||||
end
|
||||
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||
|
||||
def widget_service_class(widget)
|
||||
"WorkItems::Widgets::#{widget.type.capitalize}Service::#{self.class.name.demodulize}".constantize
|
||||
rescue NameError
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
|
@ -231,7 +231,7 @@ class IssuableBaseService < ::BaseProjectService
|
|||
before_create(issuable)
|
||||
|
||||
issuable_saved = issuable.with_transaction_returning_status do
|
||||
issuable.save
|
||||
transaction_create(issuable)
|
||||
end
|
||||
|
||||
if issuable_saved
|
||||
|
@ -339,6 +339,10 @@ class IssuableBaseService < ::BaseProjectService
|
|||
issuable.save(touch: touch)
|
||||
end
|
||||
|
||||
def transaction_create(issuable)
|
||||
issuable.save
|
||||
end
|
||||
|
||||
def update_task(issuable)
|
||||
filter_params(issuable)
|
||||
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WorkItems
|
||||
class CreateService
|
||||
class CreateService < Issues::CreateService
|
||||
include ::Services::ReturnServiceResponses
|
||||
include WidgetableService
|
||||
|
||||
def initialize(project:, current_user: nil, params: {}, spam_params:)
|
||||
@create_service = ::Issues::CreateService.new(
|
||||
def initialize(project:, current_user: nil, params: {}, spam_params:, widget_params: {})
|
||||
super(
|
||||
project: project,
|
||||
current_user: current_user,
|
||||
params: params,
|
||||
spam_params: spam_params,
|
||||
build_service: ::WorkItems::BuildService.new(project: project, current_user: current_user, params: params)
|
||||
)
|
||||
@current_user = current_user
|
||||
@project = project
|
||||
@widget_params = widget_params
|
||||
end
|
||||
|
||||
def execute
|
||||
|
@ -21,13 +21,21 @@ module WorkItems
|
|||
return error(_('Operation not allowed'), :forbidden)
|
||||
end
|
||||
|
||||
work_item = @create_service.execute
|
||||
work_item = super
|
||||
|
||||
if work_item.valid?
|
||||
success(payload(work_item))
|
||||
else
|
||||
error(work_item.errors.full_messages, :unprocessable_entity, pass_back: payload(work_item))
|
||||
end
|
||||
rescue ::WorkItems::Widgets::BaseService::WidgetError => e
|
||||
error(e.message, :unprocessable_entity)
|
||||
end
|
||||
|
||||
def transaction_create(work_item)
|
||||
super
|
||||
|
||||
execute_widgets(work_item: work_item, callback: :after_create_in_transaction, widget_params: @widget_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
module WorkItems
|
||||
class UpdateService < ::Issues::UpdateService
|
||||
include WidgetableService
|
||||
|
||||
def initialize(project:, current_user: nil, params: {}, spam_params: nil, widget_params: {})
|
||||
params[:widget_params] = true if widget_params.present?
|
||||
|
||||
super(project: project, current_user: current_user, params: params, spam_params: nil)
|
||||
|
||||
@widget_params = widget_params
|
||||
@widget_services = {}
|
||||
end
|
||||
|
||||
def execute(work_item)
|
||||
|
@ -26,13 +27,13 @@ module WorkItems
|
|||
private
|
||||
|
||||
def update(work_item)
|
||||
execute_widgets(work_item: work_item, callback: :update)
|
||||
execute_widgets(work_item: work_item, callback: :update, widget_params: @widget_params)
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def transaction_update(work_item, opts = {})
|
||||
execute_widgets(work_item: work_item, callback: :before_update_in_transaction)
|
||||
execute_widgets(work_item: work_item, callback: :before_update_in_transaction, widget_params: @widget_params)
|
||||
|
||||
super
|
||||
end
|
||||
|
@ -43,22 +44,6 @@ module WorkItems
|
|||
GraphqlTriggers.issuable_title_updated(work_item) if work_item.previous_changes.key?(:title)
|
||||
end
|
||||
|
||||
def execute_widgets(work_item:, callback:)
|
||||
work_item.widgets.each do |widget|
|
||||
widget_service(widget).try(callback, params: @widget_params[widget.class.api_symbol])
|
||||
end
|
||||
end
|
||||
|
||||
def widget_service(widget)
|
||||
@widget_services[widget] ||= widget_service_class(widget)&.new(widget: widget, current_user: current_user)
|
||||
end
|
||||
|
||||
def widget_service_class(widget)
|
||||
"WorkItems::Widgets::#{widget.type.capitalize}Service::UpdateService".constantize
|
||||
rescue NameError
|
||||
nil
|
||||
end
|
||||
|
||||
def payload(work_item)
|
||||
{ work_item: work_item }
|
||||
end
|
||||
|
|
|
@ -6,6 +6,38 @@ module WorkItems
|
|||
class BaseService < WorkItems::Widgets::BaseService
|
||||
private
|
||||
|
||||
def handle_hierarchy_changes(params)
|
||||
return feature_flag_error unless feature_flag_enabled?
|
||||
return incompatible_args_error if incompatible_args?(params)
|
||||
|
||||
update_hierarchy(params)
|
||||
end
|
||||
|
||||
def update_hierarchy(params)
|
||||
parent_id = params.delete(:parent_id)
|
||||
children_ids = params.delete(:children_ids)
|
||||
|
||||
return update_work_item_parent(parent_id) if parent_id
|
||||
|
||||
update_work_item_children(children_ids) if children_ids
|
||||
end
|
||||
|
||||
def feature_flag_enabled?
|
||||
Feature.enabled?(:work_items_hierarchy, widget.work_item&.project)
|
||||
end
|
||||
|
||||
def incompatible_args?(params)
|
||||
params[:parent_id] && params[:children_ids]
|
||||
end
|
||||
|
||||
def feature_flag_error
|
||||
error(_('`work_items_hierarchy` feature flag disabled for this project'))
|
||||
end
|
||||
|
||||
def incompatible_args_error
|
||||
error(_('A Work Item can be a parent or a child, but not both.'))
|
||||
end
|
||||
|
||||
def update_work_item_parent(parent_id)
|
||||
begin
|
||||
parent = ::WorkItem.find(parent_id)
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WorkItems
|
||||
module Widgets
|
||||
module HierarchyService
|
||||
class CreateService < WorkItems::Widgets::HierarchyService::BaseService
|
||||
def after_create_in_transaction(params:)
|
||||
return unless params.present?
|
||||
|
||||
result = handle_hierarchy_changes(params)
|
||||
|
||||
raise WidgetError, result[:message] if result[:status] == :error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,40 +11,6 @@ module WorkItems
|
|||
|
||||
raise WidgetError, result[:message] if result[:status] == :error
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_hierarchy_changes(params)
|
||||
return feature_flag_error unless feature_flag_enabled?
|
||||
return incompatible_args_error if incompatible_args?(params)
|
||||
|
||||
update_hierarchy(params)
|
||||
end
|
||||
|
||||
def update_hierarchy(params)
|
||||
parent_id = params.delete(:parent_id)
|
||||
children_ids = params.delete(:children_ids)
|
||||
|
||||
return update_work_item_parent(parent_id) if parent_id
|
||||
|
||||
update_work_item_children(children_ids) if children_ids
|
||||
end
|
||||
|
||||
def feature_flag_enabled?
|
||||
Feature.enabled?(:work_items_hierarchy, widget.work_item&.project)
|
||||
end
|
||||
|
||||
def incompatible_args?(params)
|
||||
params[:parent_id] && params[:children_ids]
|
||||
end
|
||||
|
||||
def feature_flag_error
|
||||
error(_('`work_items_hierarchy` feature flag disabled for this project'))
|
||||
end
|
||||
|
||||
def incompatible_args_error
|
||||
error(_('A Work Item can be a parent or a child, but not both.'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
%tr
|
||||
%td.text-content
|
||||
%p
|
||||
Your request to join the
|
||||
|
||||
- if @source_hidden
|
||||
#{content_tag :span, 'Hidden', class: :highlight}
|
||||
- else
|
||||
#{link_to member_source.human_name, member_source.web_url, class: :highlight}
|
||||
|
||||
#{member_source.model_name.singular} has been #{content_tag :span, 'denied', class: :highlight}.
|
||||
- target_to_join = @source_hidden ? content_tag(:span, _('Hidden'), class: :highlight) : link_to(member_source.human_name, member_source.web_url, class: :highlight)
|
||||
- denied_tag = content_tag :span, _('denied'), class: :highlight
|
||||
= s_('Notify|Your request to join the %{target_to_join} %{target_type} has been %{denied_tag}.').html_safe % { target_to_join: target_to_join, target_type: member_source.model_name.singular, denied_tag: denied_tag }
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ table_name: broadcast_messages
|
|||
classes:
|
||||
- BroadcastMessage
|
||||
feature_categories:
|
||||
- navigation
|
||||
description: TODO
|
||||
- onboarding
|
||||
description: GitLab can display broadcast messages to users of a GitLab instance
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/f1ecf53c1e55fbbc66cb2d7d12fb411cbfc2ace8
|
||||
milestone: '6.3'
|
||||
|
|
|
@ -387,6 +387,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="queryrunnersstatus"></a>`status` | [`CiRunnerStatus`](#cirunnerstatus) | Filter runners by status. |
|
||||
| <a id="queryrunnerstaglist"></a>`tagList` | [`[String!]`](#string) | Filter by tags associated with the runner (comma-separated or array). |
|
||||
| <a id="queryrunnerstype"></a>`type` | [`CiRunnerType`](#cirunnertype) | Filter runners by type. |
|
||||
| <a id="queryrunnersupgradestatus"></a>`upgradeStatus` | [`CiRunnerUpgradeStatusType`](#cirunnerupgradestatustype) | Filter by upgrade status. |
|
||||
|
||||
### `Query.snippets`
|
||||
|
||||
|
@ -5546,6 +5547,7 @@ Input type: `WorkItemCreateInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationworkitemcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationworkitemcreatedescription"></a>`description` | [`String`](#string) | Description of the work item. |
|
||||
| <a id="mutationworkitemcreatehierarchywidget"></a>`hierarchyWidget` | [`WorkItemWidgetHierarchyCreateInput`](#workitemwidgethierarchycreateinput) | Input for hierarchy widget. |
|
||||
| <a id="mutationworkitemcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project the work item is associated with. |
|
||||
| <a id="mutationworkitemcreatetitle"></a>`title` | [`String!`](#string) | Title of the work item. |
|
||||
| <a id="mutationworkitemcreateworkitemtypeid"></a>`workItemTypeId` | [`WorkItemsTypeID!`](#workitemstypeid) | Global ID of a work item type. |
|
||||
|
@ -12397,6 +12399,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="grouprunnersstatus"></a>`status` | [`CiRunnerStatus`](#cirunnerstatus) | Filter runners by status. |
|
||||
| <a id="grouprunnerstaglist"></a>`tagList` | [`[String!]`](#string) | Filter by tags associated with the runner (comma-separated or array). |
|
||||
| <a id="grouprunnerstype"></a>`type` | [`CiRunnerType`](#cirunnertype) | Filter runners by type. |
|
||||
| <a id="grouprunnersupgradestatus"></a>`upgradeStatus` | [`CiRunnerUpgradeStatusType`](#cirunnerupgradestatustype) | Filter by upgrade status. |
|
||||
|
||||
##### `Group.scanExecutionPolicies`
|
||||
|
||||
|
@ -22110,6 +22113,14 @@ A time-frame defined as a closed inclusive range of two dates.
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="workitemwidgetdescriptioninputdescription"></a>`description` | [`String!`](#string) | Description of the work item. |
|
||||
|
||||
### `WorkItemWidgetHierarchyCreateInput`
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="workitemwidgethierarchycreateinputparentid"></a>`parentId` | [`WorkItemID`](#workitemid) | Global ID of the parent work item. |
|
||||
|
||||
### `WorkItemWidgetHierarchyUpdateInput`
|
||||
|
||||
#### Arguments
|
||||
|
|
|
@ -4,7 +4,7 @@ module API
|
|||
class BroadcastMessages < ::API::Base
|
||||
include PaginationParams
|
||||
|
||||
feature_category :navigation
|
||||
feature_category :onboarding
|
||||
urgency :low
|
||||
|
||||
resource :broadcast_messages do
|
||||
|
|
|
@ -6,42 +6,74 @@ module Gitlab
|
|||
include Singleton
|
||||
|
||||
RELEASES_VALIDITY_PERIOD = 1.day
|
||||
RELEASES_VALIDITY_AFTER_ERROR_PERIOD = 5.seconds
|
||||
|
||||
INITIAL_BACKOFF = 5.seconds
|
||||
MAX_BACKOFF = 1.hour
|
||||
BACKOFF_GROWTH_FACTOR = 2.0
|
||||
|
||||
def initialize
|
||||
reset!
|
||||
reset_backoff!
|
||||
end
|
||||
|
||||
def expired?
|
||||
backoff_active? || !Rails.cache.exist?(cache_key)
|
||||
end
|
||||
|
||||
# Returns a sorted list of the publicly available GitLab Runner releases
|
||||
#
|
||||
def releases
|
||||
return @releases unless Time.now.utc >= @expire_time
|
||||
return if backoff_active?
|
||||
|
||||
@releases = fetch_new_releases
|
||||
Rails.cache.fetch(
|
||||
cache_key,
|
||||
skip_nil: true,
|
||||
expires_in: RELEASES_VALIDITY_PERIOD,
|
||||
race_condition_ttl: 10.seconds
|
||||
) do
|
||||
response = Gitlab::HTTP.try_get(runner_releases_url)
|
||||
|
||||
unless response.success?
|
||||
@backoff_expire_time = next_backoff.from_now
|
||||
break nil
|
||||
end
|
||||
|
||||
reset_backoff!
|
||||
extract_releases(response)
|
||||
end
|
||||
end
|
||||
|
||||
def reset!
|
||||
@expire_time = Time.now.utc
|
||||
@releases = nil
|
||||
def reset_backoff!
|
||||
@backoff_expire_time = nil
|
||||
@backoff_count = 0
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_new_releases
|
||||
response = Gitlab::HTTP.try_get(::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url)
|
||||
def runner_releases_url
|
||||
@runner_releases_url ||= ::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url
|
||||
end
|
||||
|
||||
releases = response.success? ? extract_releases(response) : nil
|
||||
ensure
|
||||
@expire_time = (releases ? RELEASES_VALIDITY_PERIOD : next_backoff).from_now
|
||||
def cache_key
|
||||
runner_releases_url
|
||||
end
|
||||
|
||||
def backoff_active?
|
||||
return false unless @backoff_expire_time
|
||||
|
||||
Time.now.utc < @backoff_expire_time
|
||||
end
|
||||
|
||||
def extract_releases(response)
|
||||
response.parsed_response.map { |release| parse_runner_release(release) }.sort!
|
||||
return unless response.parsed_response.is_a?(Array)
|
||||
|
||||
releases = response.parsed_response
|
||||
.map { |release| parse_runner_release(release) }
|
||||
.select(&:valid?)
|
||||
.sort!
|
||||
|
||||
return if releases.empty? && response.parsed_response.present?
|
||||
|
||||
releases
|
||||
end
|
||||
|
||||
def parse_runner_release(release)
|
||||
|
|
|
@ -5,10 +5,11 @@ module Gitlab
|
|||
class IssueSerializer
|
||||
attr_reader :jira_issue, :project, :import_owner_id, :params, :formatter
|
||||
|
||||
def initialize(project, jira_issue, import_owner_id, params = {})
|
||||
def initialize(project, jira_issue, import_owner_id, work_item_type_id, params = {})
|
||||
@jira_issue = jira_issue
|
||||
@project = project
|
||||
@import_owner_id = import_owner_id
|
||||
@work_item_type_id = work_item_type_id
|
||||
@params = params
|
||||
@formatter = Gitlab::ImportFormatter.new
|
||||
end
|
||||
|
@ -24,7 +25,8 @@ module Gitlab
|
|||
created_at: jira_issue.created,
|
||||
author_id: reporter,
|
||||
assignee_ids: assignees,
|
||||
label_ids: label_ids
|
||||
label_ids: label_ids,
|
||||
work_item_type_id: @work_item_type_id
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ module Gitlab
|
|||
@start_at = Gitlab::JiraImport.get_issues_next_start_at(project.id)
|
||||
@imported_items_cache_key = JiraImport.already_imported_cache_key(:issues, project.id)
|
||||
@job_waiter = JobWaiter.new
|
||||
@issue_type_id = WorkItems::Type.default_issue_type.id
|
||||
end
|
||||
|
||||
def execute
|
||||
|
@ -58,8 +59,13 @@ module Gitlab
|
|||
next if already_imported?(jira_issue.id)
|
||||
|
||||
begin
|
||||
issue_attrs = IssueSerializer.new(project, jira_issue, running_import.user_id, { iid: next_iid }).execute
|
||||
|
||||
issue_attrs = IssueSerializer.new(
|
||||
project,
|
||||
jira_issue,
|
||||
running_import.user_id,
|
||||
@issue_type_id,
|
||||
{ iid: next_iid }
|
||||
).execute
|
||||
Gitlab::JiraImport::ImportIssueWorker.perform_async(project.id, jira_issue.id, issue_attrs, job_waiter.key)
|
||||
|
||||
job_waiter.jobs_remaining += 1
|
||||
|
|
|
@ -6261,6 +6261,12 @@ msgstr ""
|
|||
msgid "Billing|You can begin moving members in %{namespaceName} now. A member loses access to the group when you turn off %{strongStart}In a seat%{strongEnd}. If over 5 members have %{strongStart}In a seat%{strongEnd} enabled after June 22, 2022, we'll select the 5 members who maintain access. We'll first count members that have Owner and Maintainer roles, then the most recently active members until we reach 5 members. The remaining members will get a status of Over limit and lose access to the group."
|
||||
msgstr ""
|
||||
|
||||
msgid "Billing|Your free group is now limited to %{free_user_limit} members"
|
||||
msgstr ""
|
||||
|
||||
msgid "Billing|Your group recently changed to use the Free plan. Free groups are limited to %{free_user_limit} members and the remaining members will get a status of over-limit and lose access to the group. You can free up space for new members by removing those who no longer need access or toggling them to over-limit. To get an unlimited number of members, you can %{link_start}upgrade%{link_end} to a paid tier."
|
||||
msgstr ""
|
||||
|
||||
msgid "Bitbucket Server Import"
|
||||
msgstr ""
|
||||
|
||||
|
@ -10812,6 +10818,9 @@ msgstr ""
|
|||
msgid "Create new project"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create new..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Create one"
|
||||
msgstr ""
|
||||
|
||||
|
@ -26494,6 +26503,9 @@ msgstr ""
|
|||
msgid "Notify|You don't have access to the project."
|
||||
msgstr ""
|
||||
|
||||
msgid "Notify|Your request to join the %{target_to_join} %{target_type} has been %{denied_tag}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Notify|successfully completed %{jobs} in %{stages}."
|
||||
msgstr ""
|
||||
|
||||
|
@ -45554,6 +45566,9 @@ msgstr ""
|
|||
msgid "deleted"
|
||||
msgstr ""
|
||||
|
||||
msgid "denied"
|
||||
msgstr ""
|
||||
|
||||
msgid "deploy"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ RSpec.describe 'top nav responsive', :js do
|
|||
end
|
||||
|
||||
it 'has new dropdown', :aggregate_failures do
|
||||
click_button('Create new')
|
||||
click_button('Create new...')
|
||||
|
||||
expect(page).to have_link('New project', href: new_project_path)
|
||||
expect(page).to have_link('New group', href: new_group_path)
|
||||
|
|
|
@ -15,10 +15,10 @@ RSpec.describe 'top nav tooltips', :js do
|
|||
|
||||
page.find(btn).hover
|
||||
|
||||
expect(page).to have_content('Create new')
|
||||
expect(page).to have_content('Create new...')
|
||||
|
||||
page.find(btn).click
|
||||
|
||||
expect(page).not_to have_content('Create new')
|
||||
expect(page).not_to have_content('Create new...')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -49,6 +49,67 @@ RSpec.describe Ci::RunnersFinder do
|
|||
end
|
||||
end
|
||||
|
||||
context 'by upgrade status' do
|
||||
let(:upgrade_status) {}
|
||||
|
||||
let_it_be(:runner1) { create(:ci_runner, version: 'a') }
|
||||
let_it_be(:runner2) { create(:ci_runner, version: 'b') }
|
||||
let_it_be(:runner3) { create(:ci_runner, version: 'c') }
|
||||
let_it_be(:runner_version_recommended) do
|
||||
create(:ci_runner_version, version: 'a', status: :recommended)
|
||||
end
|
||||
|
||||
let_it_be(:runner_version_not_available) do
|
||||
create(:ci_runner_version, version: 'b', status: :not_available)
|
||||
end
|
||||
|
||||
let_it_be(:runner_version_available) do
|
||||
create(:ci_runner_version, version: 'c', status: :available)
|
||||
end
|
||||
|
||||
def execute
|
||||
described_class.new(current_user: admin, params: { upgrade_status: upgrade_status }).execute
|
||||
end
|
||||
|
||||
Ci::RunnerVersion.statuses.keys.map(&:to_sym).each do |status|
|
||||
context "set to :#{status}" do
|
||||
let(:upgrade_status) { status }
|
||||
|
||||
it "calls with_upgrade_status scope with corresponding :#{status} status" do
|
||||
if [:available, :not_available, :recommended].include?(status)
|
||||
expected_result = Ci::Runner.with_upgrade_status(status)
|
||||
end
|
||||
|
||||
expect(Ci::Runner).to receive(:with_upgrade_status).with(status).and_call_original
|
||||
|
||||
result = execute
|
||||
|
||||
expect(result).to match_array(expected_result) if expected_result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'set to an invalid value' do
|
||||
let(:upgrade_status) { :some_invalid_status }
|
||||
|
||||
it 'does not call with_upgrade_status' do
|
||||
expect(Ci::Runner).not_to receive(:with_upgrade_status)
|
||||
|
||||
expect(execute).to match_array(Ci::Runner.all)
|
||||
end
|
||||
end
|
||||
|
||||
context 'set to nil' do
|
||||
let(:upgrade_status) { nil }
|
||||
|
||||
it 'does not call with_upgrade_status' do
|
||||
expect(Ci::Runner).not_to receive(:with_upgrade_status)
|
||||
|
||||
expect(execute).to match_array(Ci::Runner.all)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'by status' do
|
||||
Ci::Runner::AVAILABLE_STATUSES.each do |status|
|
||||
it "calls the corresponding :#{status} scope on Ci::Runner" do
|
||||
|
|
|
@ -52,6 +52,7 @@ RSpec.describe Resolvers::Ci::RunnersResolver do
|
|||
{
|
||||
active: true,
|
||||
status: 'active',
|
||||
upgrade_status: 'recommended',
|
||||
type: :instance_type,
|
||||
tag_list: ['active_runner'],
|
||||
search: 'abc',
|
||||
|
@ -63,6 +64,7 @@ RSpec.describe Resolvers::Ci::RunnersResolver do
|
|||
{
|
||||
active: true,
|
||||
status_status: 'active',
|
||||
upgrade_status: 'recommended',
|
||||
type_type: :instance_type,
|
||||
tag_name: ['active_runner'],
|
||||
preload: { tag_name: nil },
|
||||
|
|
|
@ -55,7 +55,7 @@ RSpec.describe Nav::NewDropdownHelper do
|
|||
end
|
||||
|
||||
it 'has title' do
|
||||
expect(subject[:title]).to eq('Create new')
|
||||
expect(subject[:title]).to eq('Create new...')
|
||||
end
|
||||
|
||||
context 'when current_user is nil (anonymous)' do
|
||||
|
|
|
@ -5,12 +5,14 @@ require 'spec_helper'
|
|||
RSpec.describe Gitlab::Ci::RunnerReleases do
|
||||
subject { described_class.instance }
|
||||
|
||||
describe '#releases' do
|
||||
before do
|
||||
subject.reset!
|
||||
let(:runner_releases_url) { 'the release API URL' }
|
||||
|
||||
stub_application_setting(public_runner_releases_url: 'the release API URL')
|
||||
allow(Gitlab::HTTP).to receive(:try_get).with('the release API URL').once { mock_http_response(response) }
|
||||
describe '#releases', :use_clean_rails_memory_store_caching do
|
||||
before do
|
||||
subject.reset_backoff!
|
||||
|
||||
stub_application_setting(public_runner_releases_url: runner_releases_url)
|
||||
allow(Gitlab::HTTP).to receive(:try_get).with(runner_releases_url).once { mock_http_response(response) }
|
||||
end
|
||||
|
||||
def releases
|
||||
|
@ -40,7 +42,9 @@ RSpec.describe Gitlab::Ci::RunnerReleases do
|
|||
releases
|
||||
|
||||
travel followup_request_interval do
|
||||
expect(Gitlab::HTTP).to receive(:try_get).with('the release API URL').once { mock_http_response(followup_response) }
|
||||
expect(Gitlab::HTTP).to receive(:try_get)
|
||||
.with(runner_releases_url)
|
||||
.once { mock_http_response(followup_response) }
|
||||
|
||||
expect(releases).to eq((expected_result || []) + [Gitlab::VersionInfo.new(14, 9, 2)])
|
||||
end
|
||||
|
@ -62,14 +66,14 @@ RSpec.describe Gitlab::Ci::RunnerReleases do
|
|||
start_time = Time.now.utc.change(usec: 0)
|
||||
|
||||
http_call_timestamp_offsets = []
|
||||
allow(Gitlab::HTTP).to receive(:try_get).with('the release API URL') do
|
||||
allow(Gitlab::HTTP).to receive(:try_get).with(runner_releases_url) do
|
||||
http_call_timestamp_offsets << Time.now.utc - start_time
|
||||
mock_http_response(response)
|
||||
end
|
||||
|
||||
# An initial HTTP request fails
|
||||
travel_to(start_time)
|
||||
subject.reset!
|
||||
subject.reset_backoff!
|
||||
expect(releases).to be_nil
|
||||
|
||||
# Successive failed requests result in HTTP requests only after specific backoff periods
|
||||
|
@ -86,7 +90,7 @@ RSpec.describe Gitlab::Ci::RunnerReleases do
|
|||
|
||||
# Finally a successful HTTP request results in releases being returned
|
||||
allow(Gitlab::HTTP).to receive(:try_get)
|
||||
.with('the release API URL')
|
||||
.with(runner_releases_url)
|
||||
.once { mock_http_response([{ 'name' => 'v14.9.1-beta1-ee' }]) }
|
||||
travel 1.hour
|
||||
expect(releases).not_to be_nil
|
||||
|
@ -109,13 +113,58 @@ RSpec.describe Gitlab::Ci::RunnerReleases do
|
|||
it_behaves_like 'requests that follow cache status', 1.day
|
||||
end
|
||||
|
||||
def mock_http_response(response)
|
||||
http_response = instance_double(HTTParty::Response)
|
||||
context 'when response contains unexpected input type' do
|
||||
let(:response) { 'error' }
|
||||
|
||||
allow(http_response).to receive(:success?).and_return(response.present?)
|
||||
allow(http_response).to receive(:parsed_response).and_return(response)
|
||||
it { expect(releases).to be_nil }
|
||||
end
|
||||
|
||||
http_response
|
||||
context 'when response contains unexpected input array' do
|
||||
let(:response) { ['error'] }
|
||||
|
||||
it { expect(releases).to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#expired?', :use_clean_rails_memory_store_caching do
|
||||
def expired?
|
||||
described_class.instance.expired?
|
||||
end
|
||||
|
||||
before do
|
||||
stub_application_setting(public_runner_releases_url: runner_releases_url)
|
||||
|
||||
subject.send(:reset_backoff!)
|
||||
end
|
||||
|
||||
it { expect(expired?).to be_truthy }
|
||||
|
||||
it 'behaves appropriately in refetch' do
|
||||
allow(Gitlab::HTTP).to receive(:try_get).with(runner_releases_url).once { mock_http_response([]) }
|
||||
|
||||
subject.releases
|
||||
expect(expired?).to be_falsey
|
||||
|
||||
travel Gitlab::Ci::RunnerReleases::RELEASES_VALIDITY_PERIOD + 1.second do
|
||||
expect(expired?).to be_truthy
|
||||
|
||||
allow(Gitlab::HTTP).to receive(:try_get).with(runner_releases_url).once { mock_http_response(nil) }
|
||||
subject.releases
|
||||
expect(expired?).to be_truthy
|
||||
|
||||
allow(Gitlab::HTTP).to receive(:try_get).with(runner_releases_url).once { mock_http_response([]) }
|
||||
subject.releases
|
||||
expect(expired?).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def mock_http_response(response)
|
||||
http_response = instance_double(HTTParty::Response)
|
||||
|
||||
allow(http_response).to receive(:success?).and_return(!response.nil?)
|
||||
allow(http_response).to receive(:parsed_response).and_return(response)
|
||||
|
||||
http_response
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,6 +11,7 @@ RSpec.describe Gitlab::JiraImport::IssueSerializer do
|
|||
let_it_be(:group_label) { create(:group_label, group: group, title: 'dev') }
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:issue_type_id) { WorkItems::Type.default_issue_type.id }
|
||||
|
||||
let(:iid) { 5 }
|
||||
let(:key) { 'PROJECT-5' }
|
||||
|
@ -54,7 +55,7 @@ RSpec.describe Gitlab::JiraImport::IssueSerializer do
|
|||
|
||||
let(:params) { { iid: iid } }
|
||||
|
||||
subject { described_class.new(project, jira_issue, current_user.id, params).execute }
|
||||
subject { described_class.new(project, jira_issue, current_user.id, issue_type_id, params).execute }
|
||||
|
||||
let(:expected_description) do
|
||||
<<~MD
|
||||
|
@ -81,7 +82,8 @@ RSpec.describe Gitlab::JiraImport::IssueSerializer do
|
|||
created_at: created_at,
|
||||
author_id: current_user.id,
|
||||
assignee_ids: nil,
|
||||
label_ids: [project_label.id, group_label.id] + Label.reorder(id: :asc).last(2).pluck(:id)
|
||||
label_ids: [project_label.id, group_label.id] + Label.reorder(id: :asc).last(2).pluck(:id),
|
||||
work_item_type_id: issue_type_id
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ RSpec.describe Gitlab::JiraImport::IssuesImporter do
|
|||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:jira_import) { create(:jira_import_state, project: project, user: current_user) }
|
||||
let_it_be(:jira_integration) { create(:jira_integration, project: project) }
|
||||
let_it_be(:default_issue_type_id) { WorkItems::Type.default_issue_type.id }
|
||||
|
||||
subject { described_class.new(project) }
|
||||
|
||||
|
@ -47,12 +48,22 @@ RSpec.describe Gitlab::JiraImport::IssuesImporter do
|
|||
|
||||
count.times do |i|
|
||||
if raise_exception_on_even_mocks && i.even?
|
||||
expect(Gitlab::JiraImport::IssueSerializer).to receive(:new)
|
||||
.with(project, jira_issues[i], current_user.id, { iid: next_iid + 1 }).and_raise('Some error')
|
||||
expect(Gitlab::JiraImport::IssueSerializer).to receive(:new).with(
|
||||
project,
|
||||
jira_issues[i],
|
||||
current_user.id,
|
||||
default_issue_type_id,
|
||||
{ iid: next_iid + 1 }
|
||||
).and_raise('Some error')
|
||||
else
|
||||
next_iid += 1
|
||||
expect(Gitlab::JiraImport::IssueSerializer).to receive(:new)
|
||||
.with(project, jira_issues[i], current_user.id, { iid: next_iid }).and_return(serializer)
|
||||
expect(Gitlab::JiraImport::IssueSerializer).to receive(:new).with(
|
||||
project,
|
||||
jira_issues[i],
|
||||
current_user.id,
|
||||
default_issue_type_id,
|
||||
{ iid: next_iid }
|
||||
).and_return(serializer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1767,4 +1767,39 @@ RSpec.describe Ci::Runner do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#with_upgrade_status' do
|
||||
subject { described_class.with_upgrade_status(upgrade_status) }
|
||||
|
||||
let_it_be(:runner_14_0_0) { create(:ci_runner, version: '14.0.0') }
|
||||
let_it_be(:runner_14_1_0) { create(:ci_runner, version: '14.1.0') }
|
||||
let_it_be(:runner_14_1_1) { create(:ci_runner, version: '14.1.1') }
|
||||
let_it_be(:runner_version_14_0_0) { create(:ci_runner_version, version: '14.0.0', status: :available) }
|
||||
let_it_be(:runner_version_14_1_0) { create(:ci_runner_version, version: '14.1.0', status: :recommended) }
|
||||
let_it_be(:runner_version_14_1_1) { create(:ci_runner_version, version: '14.1.1', status: :not_available) }
|
||||
|
||||
context ':not_available' do
|
||||
let(:upgrade_status) { :not_available }
|
||||
|
||||
it 'returns runners whose version is assigned :not_available' do
|
||||
is_expected.to contain_exactly(runner_14_1_1)
|
||||
end
|
||||
end
|
||||
|
||||
context ':available' do
|
||||
let(:upgrade_status) { :available }
|
||||
|
||||
it 'returns runners whose version is assigned :available' do
|
||||
is_expected.to contain_exactly(runner_14_0_0)
|
||||
end
|
||||
end
|
||||
|
||||
context ':recommended' do
|
||||
let(:upgrade_status) { :recommended}
|
||||
|
||||
it 'returns runners whose version is assigned :recommended' do
|
||||
is_expected.to contain_exactly(runner_14_1_0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -133,7 +133,7 @@ RSpec.describe API::API do
|
|||
'meta.caller_id' => 'GET /api/:version/broadcast_messages',
|
||||
'meta.remote_ip' => an_instance_of(String),
|
||||
'meta.client_id' => a_string_matching(%r{\Aip/.+}),
|
||||
'meta.feature_category' => 'navigation',
|
||||
'meta.feature_category' => 'onboarding',
|
||||
'route' => '/api/:version/broadcast_messages')
|
||||
|
||||
expect(data.stringify_keys).not_to include('meta.project', 'meta.root_namespace', 'meta.user')
|
||||
|
@ -209,7 +209,7 @@ RSpec.describe API::API do
|
|||
'meta.caller_id' => 'GET /api/:version/broadcast_messages',
|
||||
'meta.remote_ip' => an_instance_of(String),
|
||||
'meta.client_id' => a_string_matching(%r{\Aip/.+}),
|
||||
'meta.feature_category' => 'navigation',
|
||||
'meta.feature_category' => 'onboarding',
|
||||
'route' => '/api/:version/broadcast_messages')
|
||||
|
||||
expect(data.stringify_keys).not_to include('meta.project', 'meta.root_namespace', 'meta.user')
|
||||
|
|
|
@ -63,6 +63,70 @@ RSpec.describe 'Create a work item' do
|
|||
let(:mutation_class) { ::Mutations::WorkItems::Create }
|
||||
end
|
||||
|
||||
context 'with hierarchy widget input' do
|
||||
let(:widgets_response) { mutation_response['workItem']['widgets'] }
|
||||
let(:fields) do
|
||||
<<~FIELDS
|
||||
workItem {
|
||||
widgets {
|
||||
type
|
||||
... on WorkItemWidgetHierarchy {
|
||||
parent {
|
||||
id
|
||||
}
|
||||
children {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
errors
|
||||
FIELDS
|
||||
end
|
||||
|
||||
let(:mutation) { graphql_mutation(:workItemCreate, input.merge('projectPath' => project.full_path), fields) }
|
||||
|
||||
context 'when setting parent' do
|
||||
let_it_be(:parent) { create(:work_item, project: project) }
|
||||
|
||||
let(:input) do
|
||||
{
|
||||
title: 'item1',
|
||||
workItemTypeId: WorkItems::Type.default_by_type(:task).to_global_id.to_s,
|
||||
hierarchyWidget: { 'parentId' => parent.to_global_id.to_s }
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates the work item parent' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(widgets_response).to include(
|
||||
{
|
||||
'children' => { 'edges' => [] },
|
||||
'parent' => { 'id' => parent.to_global_id.to_s },
|
||||
'type' => 'HIERARCHY'
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
context 'when parent work item type is invalid' do
|
||||
let_it_be(:parent) { create(:work_item, :task, project: project) }
|
||||
|
||||
it 'returns error' do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
|
||||
expect(mutation_response['errors']).to contain_exactly(/cannot be added: Only Issue can be parent of Task./)
|
||||
expect(mutation_response['workItem']).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the work_items feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(work_items: false)
|
||||
|
|
|
@ -9,6 +9,7 @@ RSpec.describe WorkItems::CreateService do
|
|||
let_it_be(:guest) { create(:user) }
|
||||
let_it_be(:user_with_no_access) { create(:user) }
|
||||
|
||||
let(:widget_params) { {} }
|
||||
let(:spam_params) { double }
|
||||
let(:current_user) { guest }
|
||||
let(:opts) do
|
||||
|
@ -23,7 +24,15 @@ RSpec.describe WorkItems::CreateService do
|
|||
end
|
||||
|
||||
describe '#execute' do
|
||||
subject(:service_result) { described_class.new(project: project, current_user: current_user, params: opts, spam_params: spam_params).execute }
|
||||
subject(:service_result) do
|
||||
described_class.new(
|
||||
project: project,
|
||||
current_user: current_user,
|
||||
params: opts,
|
||||
spam_params: spam_params,
|
||||
widget_params: widget_params
|
||||
).execute
|
||||
end
|
||||
|
||||
before do
|
||||
stub_spam_services
|
||||
|
@ -80,5 +89,79 @@ RSpec.describe WorkItems::CreateService do
|
|||
service_result
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'work item widgetable service' do
|
||||
let(:widget_params) do
|
||||
{
|
||||
hierarchy_widget: { parent_id: 1 }
|
||||
}
|
||||
end
|
||||
|
||||
let(:service) do
|
||||
described_class.new(
|
||||
project: project,
|
||||
current_user: current_user,
|
||||
params: opts,
|
||||
spam_params: spam_params,
|
||||
widget_params: widget_params
|
||||
)
|
||||
end
|
||||
|
||||
let(:service_execute) { service.execute }
|
||||
|
||||
let(:supported_widgets) do
|
||||
[
|
||||
{ klass: WorkItems::Widgets::HierarchyService::CreateService, callback: :after_create_in_transaction, params: { parent_id: 1 } }
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
describe 'hierarchy widget' do
|
||||
context 'when parent is valid work item' do
|
||||
let_it_be(:parent) { create(:work_item, project: project) }
|
||||
|
||||
let(:widget_params) { { hierarchy_widget: { parent_id: parent.id } } }
|
||||
|
||||
let(:opts) do
|
||||
{
|
||||
title: 'Awesome work_item',
|
||||
description: 'please fix',
|
||||
work_item_type: create(:work_item_type, :task)
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates new work item and sets parent reference' do
|
||||
expect { service_result }.to change(
|
||||
WorkItem, :count).by(1).and(change(
|
||||
WorkItems::ParentLink, :count).by(1))
|
||||
|
||||
expect(service_result[:status]).to be(:success)
|
||||
end
|
||||
|
||||
context 'when parent type is invalid' do
|
||||
let_it_be(:parent) { create(:work_item, :task, project: project) }
|
||||
|
||||
it 'does not create new work item if parent can not be set' do
|
||||
expect { service_result }.not_to change(WorkItem, :count)
|
||||
|
||||
expect(service_result[:status]).to be(:error)
|
||||
expect(service_result[:message]).to match(/Only Issue can be parent of Task./)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when hiearchy feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(work_items_hierarchy: false)
|
||||
end
|
||||
|
||||
it 'does not create new work item if parent can not be set' do
|
||||
expect { service_result }.not_to change(WorkItem, :count)
|
||||
|
||||
expect(service_result[:status]).to be(:error)
|
||||
expect(service_result[:message]).to eq('`work_items_hierarchy` feature flag disabled for this project')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -77,6 +77,36 @@ RSpec.describe WorkItems::UpdateService do
|
|||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'work item widgetable service' do
|
||||
let(:widget_params) do
|
||||
{
|
||||
hierarchy_widget: { parent_id: 1 },
|
||||
description_widget: { description: 'foo' },
|
||||
weight_widget: { weight: 1 }
|
||||
}
|
||||
end
|
||||
|
||||
let(:service) do
|
||||
described_class.new(
|
||||
project: project,
|
||||
current_user: current_user,
|
||||
params: opts,
|
||||
spam_params: spam_params,
|
||||
widget_params: widget_params
|
||||
)
|
||||
end
|
||||
|
||||
let(:service_execute) { service.execute(work_item) }
|
||||
|
||||
let(:supported_widgets) do
|
||||
[
|
||||
{ klass: WorkItems::Widgets::DescriptionService::UpdateService, callback: :update, params: { description: 'foo' } },
|
||||
{ klass: WorkItems::Widgets::WeightService::UpdateService, callback: :update, params: { weight: 1 } },
|
||||
{ klass: WorkItems::Widgets::HierarchyService::UpdateService, callback: :before_update_in_transaction, params: { parent_id: 1 } }
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
context 'when updating widgets' do
|
||||
let(:widget_service_class) { WorkItems::Widgets::DescriptionService::UpdateService }
|
||||
let(:widget_params) { { description_widget: { description: 'changed' } } }
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples_for 'work item widgetable service' do
|
||||
it 'executes callbacks for expected widgets' do
|
||||
supported_widgets.each do |widget|
|
||||
expect_next_instance_of(widget[:klass]) do |widget_instance|
|
||||
expect(widget_instance).to receive(widget[:callback]).with(params: widget[:params])
|
||||
end
|
||||
end
|
||||
|
||||
service_execute
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue