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
|
# https://docs.gitlab.com/ee/install/requirements.html#supported-web-browsers
|
||||||
# with the following reasoning:
|
# with the following reasoning:
|
||||||
#
|
#
|
||||||
# - We should support the latest ESR of Firefox: 78, because it used quite a lot.
|
# - We should support the latest ESR of Firefox: 91, because it used quite a lot.
|
||||||
# - We use Edge/Chrome >= 84 because 83 had an annoying bug which would mean we
|
# - We use Edge/Chrome >= 92 because they are about as old as the Firefox ESR
|
||||||
# need to polyfill Array.reduce: https://bugs.chromium.org/p/chromium/issues/detail?id=1049982
|
# - Safari 14.1 because it is the current minor version of the previous major version
|
||||||
# - Safari 13.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
|
# See also this epic: https://gitlab.com/groups/gitlab-org/-/epics/3957
|
||||||
#
|
#
|
||||||
chrome >= 84
|
chrome >= 92
|
||||||
edge >= 84
|
edge >= 92
|
||||||
firefox >= 78
|
firefox >= 91
|
||||||
safari >= 13.1
|
safari >= 14.1
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
|
policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
|
||||||
|
|
||||||
.assets-cache: &assets-cache
|
.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:
|
paths:
|
||||||
- assets-hash.txt
|
- assets-hash.txt
|
||||||
- public/assets/webpack/
|
- public/assets/webpack/
|
||||||
|
|
|
||||||
|
|
@ -296,7 +296,7 @@
|
||||||
@include media-breakpoint-up(lg) {
|
@include media-breakpoint-up(lg) {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
form {
|
.issuable-context-form {
|
||||||
--initial-top: calc(#{$header-height} + #{$mr-tabs-height});
|
--initial-top: calc(#{$header-height} + #{$mr-tabs-height});
|
||||||
--top: var(--initial-top);
|
--top: var(--initial-top);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2056,7 +2056,6 @@ body.gl-dark {
|
||||||
--nav-active-bg: rgba(255, 255, 255, 0.08);
|
--nav-active-bg: rgba(255, 255, 255, 0.08);
|
||||||
}
|
}
|
||||||
.tab-width-8 {
|
.tab-width-8 {
|
||||||
-moz-tab-size: 8;
|
|
||||||
tab-size: 8;
|
tab-size: 8;
|
||||||
}
|
}
|
||||||
.gl-sr-only {
|
.gl-sr-only {
|
||||||
|
|
|
||||||
|
|
@ -1698,7 +1698,6 @@ svg.s16 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-width-8 {
|
.tab-width-8 {
|
||||||
-moz-tab-size: 8;
|
|
||||||
tab-size: 8;
|
tab-size: 8;
|
||||||
}
|
}
|
||||||
.gl-sr-only {
|
.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 */
|
/* stylelint-disable property-no-vendor-prefix */
|
||||||
-webkit-backdrop-filter: blur(2px); // still required by Safari
|
-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]
|
before_action :finder, only: [:edit, :update, :destroy]
|
||||||
|
|
||||||
feature_category :navigation
|
feature_category :onboarding
|
||||||
urgency :low
|
urgency :low
|
||||||
|
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
# rubocop: disable CodeReuse/ActiveRecord
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ module Ci
|
||||||
search!
|
search!
|
||||||
filter_by_active!
|
filter_by_active!
|
||||||
filter_by_status!
|
filter_by_status!
|
||||||
|
filter_by_upgrade_status!
|
||||||
filter_by_runner_type!
|
filter_by_runner_type!
|
||||||
filter_by_tag_list!
|
filter_by_tag_list!
|
||||||
sort!
|
sort!
|
||||||
|
|
@ -67,6 +68,13 @@ module Ci
|
||||||
filter_by!(:status_status, Ci::Runner::AVAILABLE_STATUSES)
|
filter_by!(:status_status, Ci::Runner::AVAILABLE_STATUSES)
|
||||||
end
|
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!
|
def filter_by_runner_type!
|
||||||
filter_by!(:type_type, Ci::Runner::AVAILABLE_TYPES)
|
filter_by!(:type_type, Ci::Runner::AVAILABLE_TYPES)
|
||||||
end
|
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 Mutations::SpamProtection
|
||||||
include FindsProject
|
include FindsProject
|
||||||
|
include Mutations::WorkItems::Widgetable
|
||||||
|
|
||||||
description "Creates a work item. Available only when feature flag `work_items` is enabled."
|
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,
|
argument :description, GraphQL::Types::String,
|
||||||
required: false,
|
required: false,
|
||||||
description: copy_field_description(Types::WorkItemType, :description)
|
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,
|
argument :project_path, GraphQL::Types::ID,
|
||||||
required: true,
|
required: true,
|
||||||
description: 'Full path of the project the work item is associated with.'
|
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'] }
|
return { errors: ['`work_items` feature flag disabled for this project'] }
|
||||||
end
|
end
|
||||||
|
|
||||||
params = global_id_compatibility_params(attributes).merge(author_id: current_user.id)
|
|
||||||
|
|
||||||
spam_params = ::Spam::SpamParams.new_from_request(request: context[:request])
|
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]
|
check_spam_action_response!(create_result[:work_item]) if create_result[:work_item]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ module Mutations
|
||||||
|
|
||||||
include Mutations::SpamProtection
|
include Mutations::SpamProtection
|
||||||
include Mutations::WorkItems::UpdateArguments
|
include Mutations::WorkItems::UpdateArguments
|
||||||
|
include Mutations::WorkItems::Widgetable
|
||||||
|
|
||||||
authorize :update_work_item
|
authorize :update_work_item
|
||||||
|
|
||||||
|
|
@ -24,7 +25,7 @@ module Mutations
|
||||||
end
|
end
|
||||||
|
|
||||||
spam_params = ::Spam::SpamParams.new_from_request(request: context[:request])
|
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(
|
update_result = ::WorkItems::UpdateService.new(
|
||||||
project: work_item.project,
|
project: work_item.project,
|
||||||
|
|
@ -47,16 +48,6 @@ module Mutations
|
||||||
def find_object(id:)
|
def find_object(id:)
|
||||||
GitlabSchema.find_by_gid(id)
|
GitlabSchema.find_by_gid(id)
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,10 @@ module Resolvers
|
||||||
required: false,
|
required: false,
|
||||||
description: 'Sort order of results.'
|
description: 'Sort order of results.'
|
||||||
|
|
||||||
|
argument :upgrade_status, ::Types::Ci::RunnerUpgradeStatusTypeEnum,
|
||||||
|
required: false,
|
||||||
|
description: 'Filter by upgrade status.'
|
||||||
|
|
||||||
def resolve_with_lookahead(**args)
|
def resolve_with_lookahead(**args)
|
||||||
apply_lookahead(
|
apply_lookahead(
|
||||||
::Ci::RunnersFinder
|
::Ci::RunnersFinder
|
||||||
|
|
@ -54,6 +58,7 @@ module Resolvers
|
||||||
status_status: params[:status]&.to_s,
|
status_status: params[:status]&.to_s,
|
||||||
type_type: params[:type],
|
type_type: params[:type],
|
||||||
tag_name: params[:tag_list],
|
tag_name: params[:tag_list],
|
||||||
|
upgrade_status: params[:upgrade_status],
|
||||||
search: params[:search],
|
search: params[:search],
|
||||||
sort: params[:sort]&.to_s,
|
sort: params[:sort]&.to_s,
|
||||||
preload: {
|
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)
|
menu_sections.push(general_menu_section)
|
||||||
|
|
||||||
{
|
{
|
||||||
title: _("Create new"),
|
title: _("Create new..."),
|
||||||
menu_sections: menu_sections.select { |x| x.fetch(:menu_items).any? }
|
menu_sections: menu_sections.select { |x| x.fetch(:menu_items).any? }
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ module Ci
|
||||||
has_many :groups, through: :runner_namespaces, disable_joins: true
|
has_many :groups, through: :runner_namespaces, disable_joins: true
|
||||||
|
|
||||||
has_one :last_build, -> { order('id DESC') }, class_name: 'Ci::Build'
|
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
|
before_save :ensure_token
|
||||||
|
|
||||||
|
|
@ -475,6 +476,10 @@ module Ci
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
scope :with_upgrade_status, ->(upgrade_status) do
|
||||||
|
Ci::Runner.joins(:runner_version).where(runner_version: { status: upgrade_status })
|
||||||
|
end
|
||||||
|
|
||||||
EXECUTOR_NAME_TO_TYPES = {
|
EXECUTOR_NAME_TO_TYPES = {
|
||||||
'unknown' => :unknown,
|
'unknown' => :unknown,
|
||||||
'custom' => :custom,
|
'custom' => :custom,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class WorkItem < Issue
|
class WorkItem < Issue
|
||||||
|
include Gitlab::Utils::StrongMemoize
|
||||||
|
|
||||||
self.table_name = 'issues'
|
self.table_name = 'issues'
|
||||||
self.inheritance_column = :_type_disabled
|
self.inheritance_column = :_type_disabled
|
||||||
|
|
||||||
|
|
@ -23,8 +25,10 @@ class WorkItem < Issue
|
||||||
end
|
end
|
||||||
|
|
||||||
def widgets
|
def widgets
|
||||||
work_item_type.widgets.map do |widget_class|
|
strong_memoize(:widgets) do
|
||||||
widget_class.new(self)
|
work_item_type.widgets.map do |widget_class|
|
||||||
|
widget_class.new(self)
|
||||||
|
end
|
||||||
end
|
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)
|
before_create(issuable)
|
||||||
|
|
||||||
issuable_saved = issuable.with_transaction_returning_status do
|
issuable_saved = issuable.with_transaction_returning_status do
|
||||||
issuable.save
|
transaction_create(issuable)
|
||||||
end
|
end
|
||||||
|
|
||||||
if issuable_saved
|
if issuable_saved
|
||||||
|
|
@ -339,6 +339,10 @@ class IssuableBaseService < ::BaseProjectService
|
||||||
issuable.save(touch: touch)
|
issuable.save(touch: touch)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def transaction_create(issuable)
|
||||||
|
issuable.save
|
||||||
|
end
|
||||||
|
|
||||||
def update_task(issuable)
|
def update_task(issuable)
|
||||||
filter_params(issuable)
|
filter_params(issuable)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WorkItems
|
module WorkItems
|
||||||
class CreateService
|
class CreateService < Issues::CreateService
|
||||||
include ::Services::ReturnServiceResponses
|
include ::Services::ReturnServiceResponses
|
||||||
|
include WidgetableService
|
||||||
|
|
||||||
def initialize(project:, current_user: nil, params: {}, spam_params:)
|
def initialize(project:, current_user: nil, params: {}, spam_params:, widget_params: {})
|
||||||
@create_service = ::Issues::CreateService.new(
|
super(
|
||||||
project: project,
|
project: project,
|
||||||
current_user: current_user,
|
current_user: current_user,
|
||||||
params: params,
|
params: params,
|
||||||
spam_params: spam_params,
|
spam_params: spam_params,
|
||||||
build_service: ::WorkItems::BuildService.new(project: project, current_user: current_user, params: params)
|
build_service: ::WorkItems::BuildService.new(project: project, current_user: current_user, params: params)
|
||||||
)
|
)
|
||||||
@current_user = current_user
|
@widget_params = widget_params
|
||||||
@project = project
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute
|
def execute
|
||||||
|
|
@ -21,13 +21,21 @@ module WorkItems
|
||||||
return error(_('Operation not allowed'), :forbidden)
|
return error(_('Operation not allowed'), :forbidden)
|
||||||
end
|
end
|
||||||
|
|
||||||
work_item = @create_service.execute
|
work_item = super
|
||||||
|
|
||||||
if work_item.valid?
|
if work_item.valid?
|
||||||
success(payload(work_item))
|
success(payload(work_item))
|
||||||
else
|
else
|
||||||
error(work_item.errors.full_messages, :unprocessable_entity, pass_back: payload(work_item))
|
error(work_item.errors.full_messages, :unprocessable_entity, pass_back: payload(work_item))
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@
|
||||||
|
|
||||||
module WorkItems
|
module WorkItems
|
||||||
class UpdateService < ::Issues::UpdateService
|
class UpdateService < ::Issues::UpdateService
|
||||||
|
include WidgetableService
|
||||||
|
|
||||||
def initialize(project:, current_user: nil, params: {}, spam_params: nil, widget_params: {})
|
def initialize(project:, current_user: nil, params: {}, spam_params: nil, widget_params: {})
|
||||||
params[:widget_params] = true if widget_params.present?
|
params[:widget_params] = true if widget_params.present?
|
||||||
|
|
||||||
super(project: project, current_user: current_user, params: params, spam_params: nil)
|
super(project: project, current_user: current_user, params: params, spam_params: nil)
|
||||||
|
|
||||||
@widget_params = widget_params
|
@widget_params = widget_params
|
||||||
@widget_services = {}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute(work_item)
|
def execute(work_item)
|
||||||
|
|
@ -26,13 +27,13 @@ module WorkItems
|
||||||
private
|
private
|
||||||
|
|
||||||
def update(work_item)
|
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
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def transaction_update(work_item, opts = {})
|
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
|
super
|
||||||
end
|
end
|
||||||
|
|
@ -43,22 +44,6 @@ module WorkItems
|
||||||
GraphqlTriggers.issuable_title_updated(work_item) if work_item.previous_changes.key?(:title)
|
GraphqlTriggers.issuable_title_updated(work_item) if work_item.previous_changes.key?(:title)
|
||||||
end
|
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)
|
def payload(work_item)
|
||||||
{ work_item: work_item }
|
{ work_item: work_item }
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,38 @@ module WorkItems
|
||||||
class BaseService < WorkItems::Widgets::BaseService
|
class BaseService < WorkItems::Widgets::BaseService
|
||||||
private
|
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)
|
def update_work_item_parent(parent_id)
|
||||||
begin
|
begin
|
||||||
parent = ::WorkItem.find(parent_id)
|
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
|
raise WidgetError, result[:message] if result[:status] == :error
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,7 @@
|
||||||
%tr
|
%tr
|
||||||
%td.text-content
|
%td.text-content
|
||||||
%p
|
%p
|
||||||
Your request to join the
|
- 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
|
||||||
- if @source_hidden
|
= 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 }
|
||||||
#{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}.
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ table_name: broadcast_messages
|
||||||
classes:
|
classes:
|
||||||
- BroadcastMessage
|
- BroadcastMessage
|
||||||
feature_categories:
|
feature_categories:
|
||||||
- navigation
|
- onboarding
|
||||||
description: TODO
|
description: GitLab can display broadcast messages to users of a GitLab instance
|
||||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/f1ecf53c1e55fbbc66cb2d7d12fb411cbfc2ace8
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/f1ecf53c1e55fbbc66cb2d7d12fb411cbfc2ace8
|
||||||
milestone: '6.3'
|
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="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="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="queryrunnerstype"></a>`type` | [`CiRunnerType`](#cirunnertype) | Filter runners by type. |
|
||||||
|
| <a id="queryrunnersupgradestatus"></a>`upgradeStatus` | [`CiRunnerUpgradeStatusType`](#cirunnerupgradestatustype) | Filter by upgrade status. |
|
||||||
|
|
||||||
### `Query.snippets`
|
### `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="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="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="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="mutationworkitemcreatetitle"></a>`title` | [`String!`](#string) | Title of the work item. |
|
||||||
| <a id="mutationworkitemcreateworkitemtypeid"></a>`workItemTypeId` | [`WorkItemsTypeID!`](#workitemstypeid) | Global ID of a work item type. |
|
| <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="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="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="grouprunnerstype"></a>`type` | [`CiRunnerType`](#cirunnertype) | Filter runners by type. |
|
||||||
|
| <a id="grouprunnersupgradestatus"></a>`upgradeStatus` | [`CiRunnerUpgradeStatusType`](#cirunnerupgradestatustype) | Filter by upgrade status. |
|
||||||
|
|
||||||
##### `Group.scanExecutionPolicies`
|
##### `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. |
|
| <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`
|
### `WorkItemWidgetHierarchyUpdateInput`
|
||||||
|
|
||||||
#### Arguments
|
#### Arguments
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ module API
|
||||||
class BroadcastMessages < ::API::Base
|
class BroadcastMessages < ::API::Base
|
||||||
include PaginationParams
|
include PaginationParams
|
||||||
|
|
||||||
feature_category :navigation
|
feature_category :onboarding
|
||||||
urgency :low
|
urgency :low
|
||||||
|
|
||||||
resource :broadcast_messages do
|
resource :broadcast_messages do
|
||||||
|
|
|
||||||
|
|
@ -6,42 +6,74 @@ module Gitlab
|
||||||
include Singleton
|
include Singleton
|
||||||
|
|
||||||
RELEASES_VALIDITY_PERIOD = 1.day
|
RELEASES_VALIDITY_PERIOD = 1.day
|
||||||
RELEASES_VALIDITY_AFTER_ERROR_PERIOD = 5.seconds
|
|
||||||
|
|
||||||
INITIAL_BACKOFF = 5.seconds
|
INITIAL_BACKOFF = 5.seconds
|
||||||
MAX_BACKOFF = 1.hour
|
MAX_BACKOFF = 1.hour
|
||||||
BACKOFF_GROWTH_FACTOR = 2.0
|
BACKOFF_GROWTH_FACTOR = 2.0
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
reset!
|
reset_backoff!
|
||||||
|
end
|
||||||
|
|
||||||
|
def expired?
|
||||||
|
backoff_active? || !Rails.cache.exist?(cache_key)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a sorted list of the publicly available GitLab Runner releases
|
# Returns a sorted list of the publicly available GitLab Runner releases
|
||||||
#
|
#
|
||||||
def 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
|
end
|
||||||
|
|
||||||
def reset!
|
def reset_backoff!
|
||||||
@expire_time = Time.now.utc
|
@backoff_expire_time = nil
|
||||||
@releases = nil
|
|
||||||
@backoff_count = 0
|
@backoff_count = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def fetch_new_releases
|
def runner_releases_url
|
||||||
response = Gitlab::HTTP.try_get(::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url)
|
@runner_releases_url ||= ::Gitlab::CurrentSettings.current_application_settings.public_runner_releases_url
|
||||||
|
end
|
||||||
|
|
||||||
releases = response.success? ? extract_releases(response) : nil
|
def cache_key
|
||||||
ensure
|
runner_releases_url
|
||||||
@expire_time = (releases ? RELEASES_VALIDITY_PERIOD : next_backoff).from_now
|
end
|
||||||
|
|
||||||
|
def backoff_active?
|
||||||
|
return false unless @backoff_expire_time
|
||||||
|
|
||||||
|
Time.now.utc < @backoff_expire_time
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract_releases(response)
|
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
|
end
|
||||||
|
|
||||||
def parse_runner_release(release)
|
def parse_runner_release(release)
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,11 @@ module Gitlab
|
||||||
class IssueSerializer
|
class IssueSerializer
|
||||||
attr_reader :jira_issue, :project, :import_owner_id, :params, :formatter
|
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
|
@jira_issue = jira_issue
|
||||||
@project = project
|
@project = project
|
||||||
@import_owner_id = import_owner_id
|
@import_owner_id = import_owner_id
|
||||||
|
@work_item_type_id = work_item_type_id
|
||||||
@params = params
|
@params = params
|
||||||
@formatter = Gitlab::ImportFormatter.new
|
@formatter = Gitlab::ImportFormatter.new
|
||||||
end
|
end
|
||||||
|
|
@ -24,7 +25,8 @@ module Gitlab
|
||||||
created_at: jira_issue.created,
|
created_at: jira_issue.created,
|
||||||
author_id: reporter,
|
author_id: reporter,
|
||||||
assignee_ids: assignees,
|
assignee_ids: assignees,
|
||||||
label_ids: label_ids
|
label_ids: label_ids,
|
||||||
|
work_item_type_id: @work_item_type_id
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ module Gitlab
|
||||||
@start_at = Gitlab::JiraImport.get_issues_next_start_at(project.id)
|
@start_at = Gitlab::JiraImport.get_issues_next_start_at(project.id)
|
||||||
@imported_items_cache_key = JiraImport.already_imported_cache_key(:issues, project.id)
|
@imported_items_cache_key = JiraImport.already_imported_cache_key(:issues, project.id)
|
||||||
@job_waiter = JobWaiter.new
|
@job_waiter = JobWaiter.new
|
||||||
|
@issue_type_id = WorkItems::Type.default_issue_type.id
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute
|
def execute
|
||||||
|
|
@ -58,8 +59,13 @@ module Gitlab
|
||||||
next if already_imported?(jira_issue.id)
|
next if already_imported?(jira_issue.id)
|
||||||
|
|
||||||
begin
|
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)
|
Gitlab::JiraImport::ImportIssueWorker.perform_async(project.id, jira_issue.id, issue_attrs, job_waiter.key)
|
||||||
|
|
||||||
job_waiter.jobs_remaining += 1
|
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."
|
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 ""
|
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"
|
msgid "Bitbucket Server Import"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -10812,6 +10818,9 @@ msgstr ""
|
||||||
msgid "Create new project"
|
msgid "Create new project"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Create new..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Create one"
|
msgid "Create one"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -26494,6 +26503,9 @@ msgstr ""
|
||||||
msgid "Notify|You don't have access to the project."
|
msgid "Notify|You don't have access to the project."
|
||||||
msgstr ""
|
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}."
|
msgid "Notify|successfully completed %{jobs} in %{stages}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -45554,6 +45566,9 @@ msgstr ""
|
||||||
msgid "deleted"
|
msgid "deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "denied"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "deploy"
|
msgid "deploy"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ RSpec.describe 'top nav responsive', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has new dropdown', :aggregate_failures do
|
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 project', href: new_project_path)
|
||||||
expect(page).to have_link('New group', href: new_group_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
|
page.find(btn).hover
|
||||||
|
|
||||||
expect(page).to have_content('Create new')
|
expect(page).to have_content('Create new...')
|
||||||
|
|
||||||
page.find(btn).click
|
page.find(btn).click
|
||||||
|
|
||||||
expect(page).not_to have_content('Create new')
|
expect(page).not_to have_content('Create new...')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,67 @@ RSpec.describe Ci::RunnersFinder do
|
||||||
end
|
end
|
||||||
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
|
context 'by status' do
|
||||||
Ci::Runner::AVAILABLE_STATUSES.each do |status|
|
Ci::Runner::AVAILABLE_STATUSES.each do |status|
|
||||||
it "calls the corresponding :#{status} scope on Ci::Runner" do
|
it "calls the corresponding :#{status} scope on Ci::Runner" do
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ RSpec.describe Resolvers::Ci::RunnersResolver do
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
status: 'active',
|
status: 'active',
|
||||||
|
upgrade_status: 'recommended',
|
||||||
type: :instance_type,
|
type: :instance_type,
|
||||||
tag_list: ['active_runner'],
|
tag_list: ['active_runner'],
|
||||||
search: 'abc',
|
search: 'abc',
|
||||||
|
|
@ -63,6 +64,7 @@ RSpec.describe Resolvers::Ci::RunnersResolver do
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
status_status: 'active',
|
status_status: 'active',
|
||||||
|
upgrade_status: 'recommended',
|
||||||
type_type: :instance_type,
|
type_type: :instance_type,
|
||||||
tag_name: ['active_runner'],
|
tag_name: ['active_runner'],
|
||||||
preload: { tag_name: nil },
|
preload: { tag_name: nil },
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ RSpec.describe Nav::NewDropdownHelper do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has title' do
|
it 'has title' do
|
||||||
expect(subject[:title]).to eq('Create new')
|
expect(subject[:title]).to eq('Create new...')
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when current_user is nil (anonymous)' do
|
context 'when current_user is nil (anonymous)' do
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,14 @@ require 'spec_helper'
|
||||||
RSpec.describe Gitlab::Ci::RunnerReleases do
|
RSpec.describe Gitlab::Ci::RunnerReleases do
|
||||||
subject { described_class.instance }
|
subject { described_class.instance }
|
||||||
|
|
||||||
describe '#releases' do
|
let(:runner_releases_url) { 'the release API URL' }
|
||||||
before do
|
|
||||||
subject.reset!
|
|
||||||
|
|
||||||
stub_application_setting(public_runner_releases_url: 'the release API URL')
|
describe '#releases', :use_clean_rails_memory_store_caching do
|
||||||
allow(Gitlab::HTTP).to receive(:try_get).with('the release API URL').once { mock_http_response(response) }
|
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
|
end
|
||||||
|
|
||||||
def releases
|
def releases
|
||||||
|
|
@ -40,7 +42,9 @@ RSpec.describe Gitlab::Ci::RunnerReleases do
|
||||||
releases
|
releases
|
||||||
|
|
||||||
travel followup_request_interval do
|
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)])
|
expect(releases).to eq((expected_result || []) + [Gitlab::VersionInfo.new(14, 9, 2)])
|
||||||
end
|
end
|
||||||
|
|
@ -62,14 +66,14 @@ RSpec.describe Gitlab::Ci::RunnerReleases do
|
||||||
start_time = Time.now.utc.change(usec: 0)
|
start_time = Time.now.utc.change(usec: 0)
|
||||||
|
|
||||||
http_call_timestamp_offsets = []
|
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
|
http_call_timestamp_offsets << Time.now.utc - start_time
|
||||||
mock_http_response(response)
|
mock_http_response(response)
|
||||||
end
|
end
|
||||||
|
|
||||||
# An initial HTTP request fails
|
# An initial HTTP request fails
|
||||||
travel_to(start_time)
|
travel_to(start_time)
|
||||||
subject.reset!
|
subject.reset_backoff!
|
||||||
expect(releases).to be_nil
|
expect(releases).to be_nil
|
||||||
|
|
||||||
# Successive failed requests result in HTTP requests only after specific backoff periods
|
# 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
|
# Finally a successful HTTP request results in releases being returned
|
||||||
allow(Gitlab::HTTP).to receive(:try_get)
|
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' }]) }
|
.once { mock_http_response([{ 'name' => 'v14.9.1-beta1-ee' }]) }
|
||||||
travel 1.hour
|
travel 1.hour
|
||||||
expect(releases).not_to be_nil
|
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
|
it_behaves_like 'requests that follow cache status', 1.day
|
||||||
end
|
end
|
||||||
|
|
||||||
def mock_http_response(response)
|
context 'when response contains unexpected input type' do
|
||||||
http_response = instance_double(HTTParty::Response)
|
let(:response) { 'error' }
|
||||||
|
|
||||||
allow(http_response).to receive(:success?).and_return(response.present?)
|
it { expect(releases).to be_nil }
|
||||||
allow(http_response).to receive(:parsed_response).and_return(response)
|
end
|
||||||
|
|
||||||
http_response
|
context 'when response contains unexpected input array' do
|
||||||
|
let(:response) { ['error'] }
|
||||||
|
|
||||||
|
it { expect(releases).to be_nil }
|
||||||
end
|
end
|
||||||
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
|
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(:group_label) { create(:group_label, group: group, title: 'dev') }
|
||||||
let_it_be(:current_user) { create(:user) }
|
let_it_be(:current_user) { create(:user) }
|
||||||
let_it_be(: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(:iid) { 5 }
|
||||||
let(:key) { 'PROJECT-5' }
|
let(:key) { 'PROJECT-5' }
|
||||||
|
|
@ -54,7 +55,7 @@ RSpec.describe Gitlab::JiraImport::IssueSerializer do
|
||||||
|
|
||||||
let(:params) { { iid: iid } }
|
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
|
let(:expected_description) do
|
||||||
<<~MD
|
<<~MD
|
||||||
|
|
@ -81,7 +82,8 @@ RSpec.describe Gitlab::JiraImport::IssueSerializer do
|
||||||
created_at: created_at,
|
created_at: created_at,
|
||||||
author_id: current_user.id,
|
author_id: current_user.id,
|
||||||
assignee_ids: nil,
|
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
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ RSpec.describe Gitlab::JiraImport::IssuesImporter do
|
||||||
let_it_be(:project) { create(:project) }
|
let_it_be(:project) { create(:project) }
|
||||||
let_it_be(:jira_import) { create(:jira_import_state, project: project, user: current_user) }
|
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(: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) }
|
subject { described_class.new(project) }
|
||||||
|
|
||||||
|
|
@ -47,12 +48,22 @@ RSpec.describe Gitlab::JiraImport::IssuesImporter do
|
||||||
|
|
||||||
count.times do |i|
|
count.times do |i|
|
||||||
if raise_exception_on_even_mocks && i.even?
|
if raise_exception_on_even_mocks && i.even?
|
||||||
expect(Gitlab::JiraImport::IssueSerializer).to receive(:new)
|
expect(Gitlab::JiraImport::IssueSerializer).to receive(:new).with(
|
||||||
.with(project, jira_issues[i], current_user.id, { iid: next_iid + 1 }).and_raise('Some error')
|
project,
|
||||||
|
jira_issues[i],
|
||||||
|
current_user.id,
|
||||||
|
default_issue_type_id,
|
||||||
|
{ iid: next_iid + 1 }
|
||||||
|
).and_raise('Some error')
|
||||||
else
|
else
|
||||||
next_iid += 1
|
next_iid += 1
|
||||||
expect(Gitlab::JiraImport::IssueSerializer).to receive(:new)
|
expect(Gitlab::JiraImport::IssueSerializer).to receive(:new).with(
|
||||||
.with(project, jira_issues[i], current_user.id, { iid: next_iid }).and_return(serializer)
|
project,
|
||||||
|
jira_issues[i],
|
||||||
|
current_user.id,
|
||||||
|
default_issue_type_id,
|
||||||
|
{ iid: next_iid }
|
||||||
|
).and_return(serializer)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1767,4 +1767,39 @@ RSpec.describe Ci::Runner do
|
||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ RSpec.describe API::API do
|
||||||
'meta.caller_id' => 'GET /api/:version/broadcast_messages',
|
'meta.caller_id' => 'GET /api/:version/broadcast_messages',
|
||||||
'meta.remote_ip' => an_instance_of(String),
|
'meta.remote_ip' => an_instance_of(String),
|
||||||
'meta.client_id' => a_string_matching(%r{\Aip/.+}),
|
'meta.client_id' => a_string_matching(%r{\Aip/.+}),
|
||||||
'meta.feature_category' => 'navigation',
|
'meta.feature_category' => 'onboarding',
|
||||||
'route' => '/api/:version/broadcast_messages')
|
'route' => '/api/:version/broadcast_messages')
|
||||||
|
|
||||||
expect(data.stringify_keys).not_to include('meta.project', 'meta.root_namespace', 'meta.user')
|
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.caller_id' => 'GET /api/:version/broadcast_messages',
|
||||||
'meta.remote_ip' => an_instance_of(String),
|
'meta.remote_ip' => an_instance_of(String),
|
||||||
'meta.client_id' => a_string_matching(%r{\Aip/.+}),
|
'meta.client_id' => a_string_matching(%r{\Aip/.+}),
|
||||||
'meta.feature_category' => 'navigation',
|
'meta.feature_category' => 'onboarding',
|
||||||
'route' => '/api/:version/broadcast_messages')
|
'route' => '/api/:version/broadcast_messages')
|
||||||
|
|
||||||
expect(data.stringify_keys).not_to include('meta.project', 'meta.root_namespace', 'meta.user')
|
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 }
|
let(:mutation_class) { ::Mutations::WorkItems::Create }
|
||||||
end
|
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
|
context 'when the work_items feature flag is disabled' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(work_items: false)
|
stub_feature_flags(work_items: false)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ RSpec.describe WorkItems::CreateService do
|
||||||
let_it_be(:guest) { create(:user) }
|
let_it_be(:guest) { create(:user) }
|
||||||
let_it_be(:user_with_no_access) { create(:user) }
|
let_it_be(:user_with_no_access) { create(:user) }
|
||||||
|
|
||||||
|
let(:widget_params) { {} }
|
||||||
let(:spam_params) { double }
|
let(:spam_params) { double }
|
||||||
let(:current_user) { guest }
|
let(:current_user) { guest }
|
||||||
let(:opts) do
|
let(:opts) do
|
||||||
|
|
@ -23,7 +24,15 @@ RSpec.describe WorkItems::CreateService do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#execute' do
|
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
|
before do
|
||||||
stub_spam_services
|
stub_spam_services
|
||||||
|
|
@ -80,5 +89,79 @@ RSpec.describe WorkItems::CreateService do
|
||||||
service_result
|
service_result
|
||||||
end
|
end
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,36 @@ RSpec.describe WorkItems::UpdateService do
|
||||||
end
|
end
|
||||||
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
|
context 'when updating widgets' do
|
||||||
let(:widget_service_class) { WorkItems::Widgets::DescriptionService::UpdateService }
|
let(:widget_service_class) { WorkItems::Widgets::DescriptionService::UpdateService }
|
||||||
let(:widget_params) { { description_widget: { description: 'changed' } } }
|
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