Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-07-08 15:08:58 +00:00
parent b0d4724e47
commit 6c346448fd
45 changed files with 655 additions and 145 deletions

View File

@ -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

View File

@ -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/

View File

@ -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);

View File

@ -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 {

View File

@ -1698,7 +1698,6 @@ svg.s16 {
}
.tab-width-8 {
-moz-tab-size: 8;
tab-size: 8;
}
.gl-sr-only {

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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: {

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -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'

View File

@ -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

View File

@ -4,7 +4,7 @@ module API
class BroadcastMessages < ::API::Base
include PaginationParams
feature_category :navigation
feature_category :onboarding
urgency :low
resource :broadcast_messages do

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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 ""

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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 },

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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)

View File

@ -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

View File

@ -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' } } }

View File

@ -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