Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8dae4070d2
commit
cdd5eba514
2
Gemfile
2
Gemfile
|
@ -485,7 +485,7 @@ gem 'ssh_data', '~> 1.2'
|
|||
gem 'spamcheck', '~> 0.1.0'
|
||||
|
||||
# Gitaly GRPC protocol definitions
|
||||
gem 'gitaly', '~> 15.0.0-rc3'
|
||||
gem 'gitaly', '~> 15.1.0-rc1'
|
||||
|
||||
# KAS GRPC protocol definitions
|
||||
gem 'kas-grpc', '~> 0.0.2'
|
||||
|
|
|
@ -466,7 +466,7 @@ GEM
|
|||
rails (>= 3.2.0)
|
||||
git (1.7.0)
|
||||
rchardet (~> 1.8)
|
||||
gitaly (15.0.0.pre.rc3)
|
||||
gitaly (15.1.0.pre.rc1)
|
||||
grpc (~> 1.0)
|
||||
github-markup (1.7.0)
|
||||
gitlab (4.16.1)
|
||||
|
@ -1529,7 +1529,7 @@ DEPENDENCIES
|
|||
gettext (~> 3.3)
|
||||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.3)
|
||||
gitaly (~> 15.0.0.pre.rc3)
|
||||
gitaly (~> 15.1.0.pre.rc1)
|
||||
github-markup (~> 1.7.0)
|
||||
gitlab-chronic (~> 0.10.5)
|
||||
gitlab-dangerfiles (~> 3.1.0)
|
||||
|
|
|
@ -60,7 +60,7 @@ export default {
|
|||
},
|
||||
modalCloseButton: {
|
||||
text: __('Close'),
|
||||
attributes: [{ variant: 'info' }],
|
||||
attributes: [{ variant: 'confirm' }],
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -130,11 +130,7 @@ export default {
|
|||
<slot name="extra-description"></slot>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<gl-breadcrumb v-if="breadcrumbs" :items="breadcrumbs">
|
||||
<template #separator>
|
||||
<gl-icon name="chevron-right" :size="8" />
|
||||
</template>
|
||||
</gl-breadcrumb>
|
||||
<gl-breadcrumb v-if="breadcrumbs" :items="breadcrumbs" />
|
||||
<legacy-container :key="activePanel.name" :selector="activePanel.selector" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,12 +2,6 @@
|
|||
|
||||
module Diffs
|
||||
class OverflowWarningComponent < BaseComponent
|
||||
# Skipping coverage because of https://gitlab.com/gitlab-org/gitlab/-/issues/357381
|
||||
#
|
||||
# This is fully tested by the output in the view part of this component,
|
||||
# but undercoverage doesn't understand the relationship between the two parts.
|
||||
#
|
||||
# :nocov:
|
||||
def initialize(diffs:, diff_files:, project:, commit: nil, merge_request: nil)
|
||||
@diffs = diffs
|
||||
@diff_files = diff_files
|
||||
|
@ -68,6 +62,5 @@ module Diffs
|
|||
def button_classes
|
||||
"btn gl-alert-action btn-default gl-button btn-default-secondary"
|
||||
end
|
||||
# :nocov:
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,13 +28,6 @@ module Diffs
|
|||
Gitlab::Json.dump(diffs_map)
|
||||
end
|
||||
|
||||
# Disabled undercoverage reports for this method
|
||||
# as it returns a false positive on the last line,
|
||||
# which is covered in the tests
|
||||
#
|
||||
# Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/357381
|
||||
#
|
||||
# :nocov:
|
||||
def diff_file_path_text(diff_file, max: 60)
|
||||
path = diff_file.new_path
|
||||
|
||||
|
@ -42,7 +35,6 @@ module Diffs
|
|||
|
||||
"...#{path[-(max - 3)..]}"
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -4,8 +4,6 @@ module Pajamas
|
|||
class Component < ViewComponent::Base
|
||||
private
|
||||
|
||||
# :nocov:
|
||||
|
||||
# Filter a given a value against a list of allowed values
|
||||
# If no value is given or value is not allowed return default one
|
||||
#
|
||||
|
@ -18,6 +16,5 @@ module Pajamas
|
|||
|
||||
default
|
||||
end
|
||||
# :nocov:
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
class WorkItemsResolver < BaseResolver
|
||||
include SearchArguments
|
||||
include LooksAhead
|
||||
|
||||
type Types::WorkItemType.connection_type, null: true
|
||||
|
||||
argument :iid, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: 'IID of the issue. For example, "1".'
|
||||
argument :iids, [GraphQL::Types::String],
|
||||
required: false,
|
||||
description: 'List of IIDs of work items. For example, `["1", "2"]`.'
|
||||
argument :sort, Types::WorkItemSortEnum,
|
||||
description: 'Sort work items by this criteria.',
|
||||
required: false,
|
||||
default_value: :created_desc
|
||||
argument :state, Types::IssuableStateEnum,
|
||||
required: false,
|
||||
description: 'Current state of this work item.'
|
||||
argument :types, [Types::IssueTypeEnum],
|
||||
as: :issue_types,
|
||||
description: 'Filter work items by the given work item types.',
|
||||
required: false
|
||||
|
||||
def resolve_with_lookahead(**args)
|
||||
# The project could have been loaded in batch by `BatchLoader`.
|
||||
# At this point we need the `id` of the project to query for issues, so
|
||||
# make sure it's loaded and not `nil` before continuing.
|
||||
parent = object.respond_to?(:sync) ? object.sync : object
|
||||
return WorkItem.none if parent.nil? || !parent.work_items_feature_flag_enabled?
|
||||
|
||||
args[:iids] ||= [args.delete(:iid)].compact if args[:iid]
|
||||
args[:attempt_project_search_optimizations] = true if args[:search].present?
|
||||
|
||||
finder = ::WorkItems::WorkItemsFinder.new(current_user, args)
|
||||
|
||||
Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all { |q| apply_lookahead(q) }
|
||||
end
|
||||
|
||||
def ready?(**args)
|
||||
validate_anonymous_search_access! if args[:search].present?
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def unconditional_includes
|
||||
[
|
||||
{
|
||||
project: [:project_feature, :group]
|
||||
},
|
||||
:author
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -144,6 +144,14 @@ module Types
|
|||
extras: [:lookahead],
|
||||
resolver: Resolvers::IssuesResolver
|
||||
|
||||
field :work_items,
|
||||
Types::WorkItemType.connection_type,
|
||||
null: true,
|
||||
deprecated: { milestone: '15.1', reason: :alpha },
|
||||
description: 'Work items of the project.',
|
||||
extras: [:lookahead],
|
||||
resolver: Resolvers::WorkItemsResolver
|
||||
|
||||
field :issue_status_counts,
|
||||
Types::IssueStatusCountsType,
|
||||
null: true,
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
class WorkItemSortEnum < SortEnum
|
||||
graphql_name 'WorkItemSort'
|
||||
description 'Values for sorting work items'
|
||||
|
||||
value 'TITLE_ASC', 'Title by ascending order.', value: :title_asc
|
||||
value 'TITLE_DESC', 'Title by descending order.', value: :title_desc
|
||||
end
|
||||
end
|
|
@ -12,7 +12,8 @@ module Integrations
|
|||
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||
unless method_defined?(arg)
|
||||
def #{arg}
|
||||
data_fields.send('#{arg}') || (properties && properties['#{arg}'])
|
||||
value = data_fields.send('#{arg}')
|
||||
value.nil? ? properties&.dig('#{arg}') : value
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ module Integrations
|
|||
[
|
||||
{ key: 'HARBOR_URL', value: url },
|
||||
{ key: 'HARBOR_PROJECT', value: project_name },
|
||||
{ key: 'HARBOR_USERNAME', value: username },
|
||||
{ key: 'HARBOR_USERNAME', value: username.gsub(/^robot\$/, 'robot$$') },
|
||||
{ key: 'HARBOR_PASSWORD', value: password, public: false, masked: true }
|
||||
]
|
||||
end
|
||||
|
|
|
@ -36,7 +36,7 @@ module Packages
|
|||
sha256: file_sha256
|
||||
)
|
||||
|
||||
append_metadata_file(content: file_md5, file_name: MD5_FILE_NAME)
|
||||
append_metadata_file(content: file_md5, file_name: MD5_FILE_NAME) unless Gitlab::FIPS.enabled?
|
||||
append_metadata_file(content: file_sha1, file_name: SHA1_FILE_NAME)
|
||||
append_metadata_file(content: file_sha256, file_name: SHA256_FILE_NAME)
|
||||
append_metadata_file(content: file_sha512, file_name: SHA512_FILE_NAME)
|
||||
|
@ -70,6 +70,8 @@ module Packages
|
|||
end
|
||||
|
||||
def digest_from(content, type)
|
||||
return if type == :md5 && Gitlab::FIPS.enabled?
|
||||
|
||||
digest_class = case type
|
||||
when :md5
|
||||
Digest::MD5
|
||||
|
|
|
@ -31,6 +31,14 @@ class GitlabUploader < CarrierWave::Uploader::Base
|
|||
def absolute_path(upload_record)
|
||||
File.join(root, upload_record.path)
|
||||
end
|
||||
|
||||
def version(*args, **kwargs, &block)
|
||||
# CarrierWave uploaded file "versions" are not tracked in the uploads
|
||||
# table. Because of this they won't get replicated to Geo secondaries.
|
||||
# If we ever want to use versions, we need to fix that first. Also see
|
||||
# https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1757.
|
||||
raise "using CarrierWave alternate file version is not supported"
|
||||
end
|
||||
end
|
||||
|
||||
storage_options Gitlab.config.uploads
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
= _("A member of the abuse team will review your report as soon as possible.")
|
||||
%hr
|
||||
= form_for @abuse_report, html: { class: 'js-quick-submit js-requires-input'} do |f|
|
||||
= form_errors(@abuse_report)
|
||||
= form_errors(@abuse_report, pajamas_alert: true)
|
||||
|
||||
= f.hidden_field :user_id
|
||||
.form-group.row
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
= gitlab_ui_form_for @topic, url: url, html: { multipart: true, class: 'js-project-topic-form gl-show-field-errors common-note-form js-quick-submit js-requires-input' }, authenticity_token: true do |f|
|
||||
= form_errors(@topic)
|
||||
= form_errors(@topic, pajamas_alert: true)
|
||||
|
||||
.form-group
|
||||
= f.label :name do
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
= form_errors(@group)
|
||||
= form_errors(@group, pajamas_alert: true)
|
||||
= render 'shared/group_form', f: f, autofocus: true
|
||||
|
||||
.row
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
%section.settings.no-animate#branch-rules{ class: ('expanded' if expanded) }
|
||||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Branch rules')
|
||||
%button.btn.gl-button.btn-default.js-settings-toggle
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
%p
|
||||
= _('Define rules for who can push, merge, and the required approvals for each branch.')
|
||||
|
|
|
@ -106,8 +106,7 @@
|
|||
.sidebar-collapsed-icon{ data: { toggle: 'tooltip', placement: 'left', container: 'body', boundary: 'viewport' }, title: _('Move issue') }
|
||||
= sprite_icon('long-arrow')
|
||||
.dropdown.sidebar-move-issue-dropdown.hide-collapsed
|
||||
%button.gl-button.btn.btn-default.btn-block.js-sidebar-dropdown-toggle.js-move-issue{ type: 'button',
|
||||
data: { toggle: 'dropdown', display: 'static', track_label: "right_sidebar", track_property: "move_issue", track_action: "click_button", track_value: "" } }
|
||||
= render Pajamas::ButtonComponent.new(block: true, button_options: { class: 'js-sidebar-dropdown-toggle js-move-issue', data: { toggle: 'dropdown', display: 'static', track_label: "right_sidebar", track_property: "move_issue", track_action: "click_button", track_value: "" } } ) do
|
||||
= _('Move issue')
|
||||
.dropdown-menu.dropdown-menu-selectable.dropdown-extended-height
|
||||
= dropdown_title(_('Move issue'))
|
||||
|
|
|
@ -4,7 +4,7 @@ group: Memory
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Change the expiration interval for application cache **(FREE SELF)**
|
||||
# Application cache interval **(FREE SELF)**
|
||||
|
||||
By default, GitLab caches application settings for 60 seconds. Occasionally,
|
||||
you may need to increase that interval to have more delay between application
|
||||
|
@ -14,6 +14,8 @@ We recommend you set this value to greater than `0` seconds. Setting it to `0`
|
|||
causes the `application_settings` table to load for every request. This causes
|
||||
extra load for Redis and PostgreSQL.
|
||||
|
||||
## Change the expiration interval for application cache
|
||||
|
||||
To change the expiry value:
|
||||
|
||||
**For Omnibus installations**
|
||||
|
@ -32,8 +34,6 @@ To change the expiry value:
|
|||
gitlab-ctl restart
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**For installations from source**
|
||||
|
||||
1. Edit `config/gitlab.yml`:
|
||||
|
|
|
@ -15806,6 +15806,31 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="projectworkitemtypestaskable"></a>`taskable` | [`Boolean`](#boolean) | If `true`, only taskable work item types will be returned. Argument is experimental and can be removed in the future without notice. |
|
||||
|
||||
##### `Project.workItems`
|
||||
|
||||
Work items of the project.
|
||||
|
||||
WARNING:
|
||||
**Deprecated** in 15.1.
|
||||
This feature is in Alpha, and can be removed or changed at any point.
|
||||
|
||||
Returns [`WorkItemConnection`](#workitemconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#connection-pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, `last: Int`.
|
||||
|
||||
###### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="projectworkitemsiid"></a>`iid` | [`String`](#string) | IID of the issue. For example, "1". |
|
||||
| <a id="projectworkitemsiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of work items. For example, `["1", "2"]`. |
|
||||
| <a id="projectworkitemssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
|
||||
| <a id="projectworkitemssort"></a>`sort` | [`WorkItemSort`](#workitemsort) | Sort work items by this criteria. |
|
||||
| <a id="projectworkitemsstate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of this work item. |
|
||||
| <a id="projectworkitemstypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter work items by the given work item types. |
|
||||
|
||||
### `ProjectCiCdSetting`
|
||||
|
||||
#### Fields
|
||||
|
@ -19887,6 +19912,23 @@ Weight ID wildcard values.
|
|||
| <a id="weightwildcardidany"></a>`ANY` | Weight is assigned. |
|
||||
| <a id="weightwildcardidnone"></a>`NONE` | No weight is assigned. |
|
||||
|
||||
### `WorkItemSort`
|
||||
|
||||
Values for sorting work items.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="workitemsortcreated_asc"></a>`CREATED_ASC` | Created at ascending order. |
|
||||
| <a id="workitemsortcreated_desc"></a>`CREATED_DESC` | Created at descending order. |
|
||||
| <a id="workitemsorttitle_asc"></a>`TITLE_ASC` | Title by ascending order. |
|
||||
| <a id="workitemsorttitle_desc"></a>`TITLE_DESC` | Title by descending order. |
|
||||
| <a id="workitemsortupdated_asc"></a>`UPDATED_ASC` | Updated at ascending order. |
|
||||
| <a id="workitemsortupdated_desc"></a>`UPDATED_DESC` | Updated at descending order. |
|
||||
| <a id="workitemsortcreated_asc"></a>`created_asc` **{warning-solid}** | **Deprecated** in 13.5. This was renamed. Use: `CREATED_ASC`. |
|
||||
| <a id="workitemsortcreated_desc"></a>`created_desc` **{warning-solid}** | **Deprecated** in 13.5. This was renamed. Use: `CREATED_DESC`. |
|
||||
| <a id="workitemsortupdated_asc"></a>`updated_asc` **{warning-solid}** | **Deprecated** in 13.5. This was renamed. Use: `UPDATED_ASC`. |
|
||||
| <a id="workitemsortupdated_desc"></a>`updated_desc` **{warning-solid}** | **Deprecated** in 13.5. This was renamed. Use: `UPDATED_DESC`. |
|
||||
|
||||
### `WorkItemState`
|
||||
|
||||
State of a GitLab work item.
|
||||
|
|
|
@ -113,8 +113,8 @@ the following table) as these were used for development and testing:
|
|||
|----------------|----------------------------|
|
||||
| 13.0 | 11 |
|
||||
| 14.0 | 12.10 |
|
||||
| 15.0 | 12.0 |
|
||||
| 16.0 (planned) | 13.0 |
|
||||
| 15.0 | 12.10 |
|
||||
| 16.0 (planned) | 13.6 |
|
||||
|
||||
You must also ensure the following extensions are loaded into every
|
||||
GitLab database. [Read more about this requirement, and troubleshooting](postgresql_extensions.md).
|
||||
|
|
|
@ -18,7 +18,7 @@ this feature, ask an administrator to [enable the feature flag](../../../adminis
|
|||
`bulk_import_projects`. On GitLab.com, migrating group resources is available but migrating project resources is not
|
||||
available.
|
||||
|
||||
You can migrate your existing top-level groups to any of the following:
|
||||
Users with the Owner role on a top-level group can migrate it to:
|
||||
|
||||
- Another top-level group.
|
||||
- The subgroup of any existing top-level group.
|
||||
|
|
|
@ -431,7 +431,7 @@ The following table lists group permissions available for each role:
|
|||
| View [Billing](../subscriptions/gitlab_com/index.md#view-your-gitlab-saas-subscription) | | | | | ✓ (4) |
|
||||
| View group [Usage Quotas](usage_quotas.md) page | | | | | ✓ (4) |
|
||||
| Manage group runners | | | | | ✓ |
|
||||
| GitLab migration | | | | | ✓ |
|
||||
| [Migrate groups](group/import/index.md) | | | | | ✓ |
|
||||
|
||||
<!-- markdownlint-disable MD029 -->
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ GitLab supports integrating Harbor projects at the group or project level. Compl
|
|||
|
||||
After the Harbor integration is activated:
|
||||
|
||||
- The global variables `$HARBOR_USER`, `$HARBOR_PASSWORD`, `$HARBOR_URL`, and `$HARBOR_PROJECT` are created for CI/CD use.
|
||||
- The global variables `$HARBOR_USERNAME`, `$HARBOR_PASSWORD`, `$HARBOR_URL`, and `$HARBOR_PROJECT` are created for CI/CD use.
|
||||
- The project-level integration settings override the group-level integration settings.
|
||||
|
||||
## Secure your requests to the Harbor APIs
|
||||
|
|
|
@ -101,6 +101,16 @@ module Gitlab
|
|||
if pre_receive_error = response.pre_receive_error.presence
|
||||
raise Gitlab::Git::PreReceiveError, pre_receive_error
|
||||
end
|
||||
rescue GRPC::BadStatus => e
|
||||
detailed_error = decode_detailed_error(e)
|
||||
|
||||
case detailed_error&.error
|
||||
when :custom_hook
|
||||
raise Gitlab::Git::PreReceiveError.new(custom_hook_error_message(detailed_error.custom_hook),
|
||||
fallback_message: e.details)
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def user_merge_to_ref(user, source_sha:, branch:, target_ref:, message:, first_parent_ref:, allow_conflicts: false)
|
||||
|
@ -164,14 +174,8 @@ module Gitlab
|
|||
# These messages were returned from internal/allowed API calls
|
||||
raise Gitlab::Git::PreReceiveError.new(fallback_message: access_check_error.error_message)
|
||||
when :custom_hook
|
||||
# Custom hooks may return messages via either stdout or stderr which have a specific prefix. If
|
||||
# that prefix is present we'll want to print the hook's output, otherwise we'll want to print the
|
||||
# Gitaly error as a fallback.
|
||||
custom_hook_error = detailed_error.custom_hook
|
||||
custom_hook_output = custom_hook_error.stderr.presence || custom_hook_error.stdout
|
||||
error_message = EncodingHelper.encode!(custom_hook_output)
|
||||
|
||||
raise Gitlab::Git::PreReceiveError.new(error_message, fallback_message: e.details)
|
||||
raise Gitlab::Git::PreReceiveError.new(custom_hook_error_message(detailed_error.custom_hook),
|
||||
fallback_message: e.details)
|
||||
when :reference_update
|
||||
# We simply ignore any reference update errors which are typically an
|
||||
# indicator of multiple RPC calls trying to update the same reference
|
||||
|
@ -308,10 +312,6 @@ module Gitlab
|
|||
timeout: GitalyClient.long_timeout
|
||||
)
|
||||
|
||||
if response.git_error.presence
|
||||
raise Gitlab::Git::Repository::GitError, response.git_error
|
||||
end
|
||||
|
||||
response.squash_sha
|
||||
rescue GRPC::BadStatus => e
|
||||
detailed_error = decode_detailed_error(e)
|
||||
|
@ -550,6 +550,14 @@ module Gitlab
|
|||
# Error Class might not be known to ruby yet
|
||||
nil
|
||||
end
|
||||
|
||||
def custom_hook_error_message(custom_hook_error)
|
||||
# Custom hooks may return messages via either stdout or stderr which have a specific prefix. If
|
||||
# that prefix is present we'll want to print the hook's output, otherwise we'll want to print the
|
||||
# Gitaly error as a fallback.
|
||||
custom_hook_output = custom_hook_error.stderr.presence || custom_hook_error.stdout
|
||||
EncodingHelper.encode!(custom_hook_output)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -207,7 +207,7 @@
|
|||
"@babel/plugin-transform-modules-commonjs": "^7.10.1",
|
||||
"@gitlab/eslint-plugin": "12.1.0",
|
||||
"@gitlab/stylelint-config": "4.0.0",
|
||||
"@graphql-eslint/eslint-plugin": "3.10.3",
|
||||
"@graphql-eslint/eslint-plugin": "3.10.4",
|
||||
"@testing-library/dom": "^7.16.2",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@vue/test-utils": "1.3.0",
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Resolvers::WorkItemsResolver do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:reporter) { create(:user) }
|
||||
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
let_it_be(:other_project) { create(:project, group: group) }
|
||||
|
||||
let_it_be(:item1) do
|
||||
create(:work_item, project: project, state: :opened, created_at:
|
||||
3.hours.ago, updated_at: 3.hours.ago)
|
||||
end
|
||||
|
||||
let_it_be(:item2) do
|
||||
create(:work_item, project: project, state: :closed, title: 'foo',
|
||||
created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at:
|
||||
1.hour.ago)
|
||||
end
|
||||
|
||||
let_it_be(:item3) do
|
||||
create(:work_item, project: other_project, state: :closed, title: 'foo',
|
||||
created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at:
|
||||
1.hour.ago)
|
||||
end
|
||||
|
||||
let_it_be(:item4) { create(:work_item) }
|
||||
|
||||
specify do
|
||||
expect(described_class).to have_nullable_graphql_type(Types::WorkItemType.connection_type)
|
||||
end
|
||||
|
||||
context "with a project" do
|
||||
before_all do
|
||||
project.add_developer(current_user)
|
||||
project.add_reporter(reporter)
|
||||
end
|
||||
|
||||
describe '#resolve' do
|
||||
it 'finds all items' do
|
||||
expect(resolve_items).to contain_exactly(item1, item2)
|
||||
end
|
||||
|
||||
it 'filters by state' do
|
||||
expect(resolve_items(state: 'opened')).to contain_exactly(item1)
|
||||
expect(resolve_items(state: 'closed')).to contain_exactly(item2)
|
||||
end
|
||||
|
||||
context 'when searching items' do
|
||||
it 'returns correct items' do
|
||||
expect(resolve_items(search: 'foo')).to contain_exactly(item2)
|
||||
end
|
||||
|
||||
it 'uses project search optimization' do
|
||||
expected_arguments = a_hash_including(
|
||||
search: 'foo',
|
||||
attempt_project_search_optimizations: true
|
||||
)
|
||||
expect(::WorkItems::WorkItemsFinder).to receive(:new).with(anything, expected_arguments).and_call_original
|
||||
|
||||
resolve_items(search: 'foo')
|
||||
end
|
||||
|
||||
context 'with anonymous user' do
|
||||
let_it_be(:public_project) { create(:project, :public) }
|
||||
let_it_be(:public_item) { create(:work_item, project: public_project, title: 'Test item') }
|
||||
|
||||
context 'with disable_anonymous_search enabled' do
|
||||
before do
|
||||
stub_feature_flags(disable_anonymous_search: true)
|
||||
end
|
||||
|
||||
it 'generates an error' do
|
||||
error_message = "User must be authenticated to include the `search` argument."
|
||||
|
||||
expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, error_message) do
|
||||
resolve(described_class, obj: public_project, args: { search: 'test' }, ctx: { current_user: nil })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with disable_anonymous_search disabled' do
|
||||
before do
|
||||
stub_feature_flags(disable_anonymous_search: false)
|
||||
end
|
||||
|
||||
it 'returns correct items' do
|
||||
expect(
|
||||
resolve(described_class, obj: public_project, args: { search: 'test' }, ctx: { current_user: nil })
|
||||
).to contain_exactly(public_item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'sorting' do
|
||||
context 'when sorting by created' do
|
||||
it 'sorts items ascending' do
|
||||
expect(resolve_items(sort: 'created_asc').to_a).to eq [item1, item2]
|
||||
end
|
||||
|
||||
it 'sorts items descending' do
|
||||
expect(resolve_items(sort: 'created_desc').to_a).to eq [item2, item1]
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sorting by title' do
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
let_it_be(:item1) { create(:work_item, project: project, title: 'foo') }
|
||||
let_it_be(:item2) { create(:work_item, project: project, title: 'bar') }
|
||||
let_it_be(:item3) { create(:work_item, project: project, title: 'baz') }
|
||||
let_it_be(:item4) { create(:work_item, project: project, title: 'Baz 2') }
|
||||
|
||||
it 'sorts items ascending' do
|
||||
expect(resolve_items(sort: :title_asc).to_a).to eq [item2, item3, item4, item1]
|
||||
end
|
||||
|
||||
it 'sorts items descending' do
|
||||
expect(resolve_items(sort: :title_desc).to_a).to eq [item1, item4, item3, item2]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns items user can see' do
|
||||
project.add_guest(current_user)
|
||||
|
||||
create(:work_item, confidential: true)
|
||||
|
||||
expect(resolve_items).to contain_exactly(item1, item2)
|
||||
end
|
||||
|
||||
it 'batches queries that only include IIDs', :request_store do
|
||||
result = batch_sync(max_queries: 7) do
|
||||
[item1, item2]
|
||||
.map { |item| resolve_items(iid: item.iid.to_s) }
|
||||
.flat_map(&:to_a)
|
||||
end
|
||||
|
||||
expect(result).to contain_exactly(item1, item2)
|
||||
end
|
||||
|
||||
it 'finds a specific item with iids', :request_store do
|
||||
result = batch_sync(max_queries: 7) do
|
||||
resolve_items(iids: [item1.iid]).to_a
|
||||
end
|
||||
|
||||
expect(result).to contain_exactly(item1)
|
||||
end
|
||||
|
||||
it 'finds multiple items with iids' do
|
||||
create(:work_item, project: project, author: current_user)
|
||||
|
||||
expect(batch_sync { resolve_items(iids: [item1.iid, item2.iid]).to_a })
|
||||
.to contain_exactly(item1, item2)
|
||||
end
|
||||
|
||||
it 'finds only the items within the project we are looking at' do
|
||||
another_project = create(:project)
|
||||
iids = [item1, item2].map(&:iid)
|
||||
|
||||
iids.each do |iid|
|
||||
create(:work_item, project: another_project, iid: iid)
|
||||
end
|
||||
|
||||
expect(batch_sync { resolve_items(iids: iids).to_a }).to contain_exactly(item1, item2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when passing a non existent, batch loaded project" do
|
||||
let!(:project) do
|
||||
BatchLoader::GraphQL.for("non-existent-path").batch do |_fake_paths, loader, _|
|
||||
loader.call("non-existent-path", nil)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns nil without breaking" do
|
||||
expect(resolve_items(iids: ["don't", "break"])).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_items(args = {}, context = { current_user: current_user })
|
||||
resolve(described_class, obj: project, args: args, ctx: context)
|
||||
end
|
||||
end
|
|
@ -170,6 +170,65 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
|
|||
Gitlab::Git::PreReceiveError, "something failed")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a custom hook error' do
|
||||
let(:stdout) { nil }
|
||||
let(:stderr) { nil }
|
||||
let(:error_message) { "error_message" }
|
||||
let(:custom_hook_error) do
|
||||
new_detailed_error(
|
||||
GRPC::Core::StatusCodes::PERMISSION_DENIED,
|
||||
error_message,
|
||||
Gitaly::UserDeleteBranchError.new(
|
||||
custom_hook: Gitaly::CustomHookError.new(
|
||||
stdout: stdout,
|
||||
stderr: stderr,
|
||||
hook_type: Gitaly::CustomHookError::HookType::HOOK_TYPE_PRERECEIVE
|
||||
)))
|
||||
end
|
||||
|
||||
shared_examples 'a failed branch deletion' do
|
||||
it 'raises a PreRecieveError' do
|
||||
expect_any_instance_of(Gitaly::OperationService::Stub)
|
||||
.to receive(:user_delete_branch).with(request, kind_of(Hash))
|
||||
.and_raise(custom_hook_error)
|
||||
|
||||
expect { subject }.to raise_error do |error|
|
||||
expect(error).to be_a(Gitlab::Git::PreReceiveError)
|
||||
expect(error.message).to eq(expected_message)
|
||||
expect(error.raw_message).to eq(expected_raw_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when details contain stderr' do
|
||||
let(:stderr) { "something" }
|
||||
let(:stdout) { "GL-HOOK-ERR: stdout is overridden by stderr" }
|
||||
let(:expected_message) { error_message }
|
||||
let(:expected_raw_message) { stderr }
|
||||
|
||||
it_behaves_like 'a failed branch deletion'
|
||||
end
|
||||
|
||||
context 'when details contain stdout' do
|
||||
let(:stderr) { " \n" }
|
||||
let(:stdout) { "something" }
|
||||
let(:expected_message) { error_message }
|
||||
let(:expected_raw_message) { stdout }
|
||||
|
||||
it_behaves_like 'a failed branch deletion'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a non-detailed error' do
|
||||
it 'raises a GRPC error' do
|
||||
expect_any_instance_of(Gitaly::OperationService::Stub)
|
||||
.to receive(:user_delete_branch).with(request, kind_of(Hash))
|
||||
.and_raise(GRPC::Internal.new('non-detailed error'))
|
||||
|
||||
expect { subject }.to raise_error(GRPC::Internal)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#user_merge_branch' do
|
||||
|
@ -635,21 +694,6 @@ RSpec.describe Gitlab::GitalyClient::OperationService do
|
|||
expect(subject).to eq(squash_sha)
|
||||
end
|
||||
|
||||
context "when git_error is present" do
|
||||
let(:response) do
|
||||
Gitaly::UserSquashResponse.new(git_error: "something failed")
|
||||
end
|
||||
|
||||
it "raises a GitError exception" do
|
||||
expect_any_instance_of(Gitaly::OperationService::Stub)
|
||||
.to receive(:user_squash).with(request, kind_of(Hash))
|
||||
.and_return(response)
|
||||
|
||||
expect { subject }.to raise_error(
|
||||
Gitlab::Git::Repository::GitError, "something failed")
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples '#user_squash with an error' do
|
||||
it 'raises a GitError exception' do
|
||||
expect_any_instance_of(Gitaly::OperationService::Stub)
|
||||
|
|
|
@ -6,84 +6,84 @@ RSpec.describe Integrations::HasDataFields do
|
|||
let(:url) { 'http://url.com' }
|
||||
let(:username) { 'username_one' }
|
||||
let(:properties) do
|
||||
{ url: url, username: username }
|
||||
{ url: url, username: username, jira_issue_transition_automatic: false }
|
||||
end
|
||||
|
||||
shared_examples 'data fields' do
|
||||
describe '#arg' do
|
||||
it 'returns an argument correctly' do
|
||||
expect(service.url).to eq(url)
|
||||
it 'returns the expected values' do
|
||||
expect(integration).to have_attributes(properties)
|
||||
end
|
||||
end
|
||||
|
||||
describe '{arg}_changed?' do
|
||||
it 'returns false when the property has not been assigned a new value' do
|
||||
service.username = 'new_username'
|
||||
service.validate
|
||||
expect(service.url_changed?).to be_falsy
|
||||
integration.username = 'new_username'
|
||||
integration.validate
|
||||
expect(integration.url_changed?).to be_falsy
|
||||
end
|
||||
|
||||
it 'returns true when the property has been assigned a different value' do
|
||||
service.url = "http://example.com"
|
||||
service.validate
|
||||
expect(service.url_changed?).to be_truthy
|
||||
integration.url = "http://example.com"
|
||||
integration.validate
|
||||
expect(integration.url_changed?).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns true when the property has been assigned a different value twice' do
|
||||
service.url = "http://example.com"
|
||||
service.url = "http://example.com"
|
||||
service.validate
|
||||
expect(service.url_changed?).to be_truthy
|
||||
integration.url = "http://example.com"
|
||||
integration.url = "http://example.com"
|
||||
integration.validate
|
||||
expect(integration.url_changed?).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false when the property has been re-assigned the same value' do
|
||||
service.url = 'http://url.com'
|
||||
service.validate
|
||||
expect(service.url_changed?).to be_falsy
|
||||
integration.url = 'http://url.com'
|
||||
integration.validate
|
||||
expect(integration.url_changed?).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
describe '{arg}_touched?' do
|
||||
it 'returns false when the property has not been assigned a new value' do
|
||||
service.username = 'new_username'
|
||||
service.validate
|
||||
expect(service.url_changed?).to be_falsy
|
||||
integration.username = 'new_username'
|
||||
integration.validate
|
||||
expect(integration.url_changed?).to be_falsy
|
||||
end
|
||||
|
||||
it 'returns true when the property has been assigned a different value' do
|
||||
service.url = "http://example.com"
|
||||
service.validate
|
||||
expect(service.url_changed?).to be_truthy
|
||||
integration.url = "http://example.com"
|
||||
integration.validate
|
||||
expect(integration.url_changed?).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns true when the property has been assigned a different value twice' do
|
||||
service.url = "http://example.com"
|
||||
service.url = "http://example.com"
|
||||
service.validate
|
||||
expect(service.url_changed?).to be_truthy
|
||||
integration.url = "http://example.com"
|
||||
integration.url = "http://example.com"
|
||||
integration.validate
|
||||
expect(integration.url_changed?).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns true when the property has been re-assigned the same value' do
|
||||
service.url = 'http://url.com'
|
||||
expect(service.url_touched?).to be_truthy
|
||||
integration.url = 'http://url.com'
|
||||
expect(integration.url_touched?).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false when the property has been re-assigned the same value' do
|
||||
service.url = 'http://url.com'
|
||||
service.validate
|
||||
expect(service.url_changed?).to be_falsy
|
||||
integration.url = 'http://url.com'
|
||||
integration.validate
|
||||
expect(integration.url_changed?).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
describe 'data_fields_present?' do
|
||||
it 'returns true from the issue tracker service' do
|
||||
expect(service.data_fields_present?).to be true
|
||||
it 'returns true from the issue tracker integration' do
|
||||
expect(integration.data_fields_present?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when data are stored in data_fields' do
|
||||
let(:service) do
|
||||
let(:integration) do
|
||||
create(:jira_integration, url: url, username: username)
|
||||
end
|
||||
|
||||
|
@ -91,21 +91,21 @@ RSpec.describe Integrations::HasDataFields do
|
|||
|
||||
describe '{arg}_was?' do
|
||||
it 'returns nil' do
|
||||
service.url = 'http://example.com'
|
||||
service.validate
|
||||
expect(service.url_was).to be_nil
|
||||
integration.url = 'http://example.com'
|
||||
integration.validate
|
||||
expect(integration.url_was).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when service and data_fields are not persisted' do
|
||||
let(:service) do
|
||||
context 'when integration and data_fields are not persisted' do
|
||||
let(:integration) do
|
||||
Integrations::Jira.new
|
||||
end
|
||||
|
||||
describe 'data_fields_present?' do
|
||||
it 'returns true' do
|
||||
expect(service.data_fields_present?).to be true
|
||||
expect(integration.data_fields_present?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -113,9 +113,7 @@ RSpec.describe Integrations::HasDataFields do
|
|||
context 'when data are stored in properties' do
|
||||
let(:integration) { create(:jira_integration, :without_properties_callback, properties: properties) }
|
||||
|
||||
it_behaves_like 'data fields' do
|
||||
let(:service) { integration }
|
||||
end
|
||||
it_behaves_like 'data fields'
|
||||
|
||||
describe '{arg}_was?' do
|
||||
it 'returns nil when the property has not been assigned a new value' do
|
||||
|
@ -148,9 +146,7 @@ RSpec.describe Integrations::HasDataFields do
|
|||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'data fields' do
|
||||
let(:service) { integration }
|
||||
end
|
||||
it_behaves_like 'data fields'
|
||||
|
||||
describe '{arg}_was?' do
|
||||
it 'returns nil' do
|
||||
|
|
|
@ -67,6 +67,16 @@ RSpec.describe Integrations::Harbor do
|
|||
harbor_integration.update!(active: false)
|
||||
expect(harbor_integration.ci_variables).to match_array([])
|
||||
end
|
||||
|
||||
context 'with robot username' do
|
||||
it 'returns username variable with $$' do
|
||||
harbor_integration.username = 'robot$project+user'
|
||||
|
||||
expect(harbor_integration.ci_variables).to include(
|
||||
{ key: 'HARBOR_USERNAME', value: 'robot$$project+user' }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'before_validation :reset_username_and_password' do
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'getting an work item list for a project' do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, :repository, :public, group: group) }
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
|
||||
let_it_be(:item1) { create(:work_item, project: project, discussion_locked: true, title: 'item1') }
|
||||
let_it_be(:item2) { create(:work_item, project: project, title: 'item2') }
|
||||
let_it_be(:confidential_item) { create(:work_item, confidential: true, project: project, title: 'item3') }
|
||||
let_it_be(:other_item) { create(:work_item) }
|
||||
|
||||
let(:items_data) { graphql_data['project']['workItems']['edges'] }
|
||||
let(:item_filter_params) { {} }
|
||||
|
||||
let(:fields) do
|
||||
<<~QUERY
|
||||
edges {
|
||||
node {
|
||||
#{all_graphql_fields_for('workItems'.classify)}
|
||||
}
|
||||
}
|
||||
QUERY
|
||||
end
|
||||
|
||||
let(:query) do
|
||||
graphql_query_for(
|
||||
'project',
|
||||
{ 'fullPath' => project.full_path },
|
||||
query_graphql_field('workItems', item_filter_params, fields)
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'a working graphql query' do
|
||||
before do
|
||||
post_graphql(query, current_user: current_user)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user does not have access to the item' do
|
||||
before do
|
||||
project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
|
||||
end
|
||||
|
||||
it 'returns an empty list' do
|
||||
post_graphql(query)
|
||||
|
||||
expect(items_data).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when work_items flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(work_items: false)
|
||||
end
|
||||
|
||||
it 'returns an empty list' do
|
||||
post_graphql(query)
|
||||
|
||||
expect(items_data).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns only items visible to user' do
|
||||
post_graphql(query, current_user: current_user)
|
||||
|
||||
expect(item_ids).to eq([item2.to_global_id.to_s, item1.to_global_id.to_s])
|
||||
end
|
||||
|
||||
context 'when the user can see confidential items' do
|
||||
before do
|
||||
project.add_developer(current_user)
|
||||
end
|
||||
|
||||
it 'returns also confidential items' do
|
||||
post_graphql(query, current_user: current_user)
|
||||
|
||||
expect(item_ids).to eq([confidential_item.to_global_id.to_s, item2.to_global_id.to_s, item1.to_global_id.to_s])
|
||||
end
|
||||
end
|
||||
|
||||
describe 'sorting and pagination' do
|
||||
let(:data_path) { [:project, :work_items] }
|
||||
|
||||
def pagination_query(params)
|
||||
graphql_query_for(
|
||||
'project',
|
||||
{ 'fullPath' => project.full_path },
|
||||
query_graphql_field('workItems', params, "#{page_info} nodes { id }")
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
project.add_developer(current_user)
|
||||
end
|
||||
|
||||
context 'when sorting by title ascending' do
|
||||
it_behaves_like 'sorted paginated query' do
|
||||
let(:sort_param) { :TITLE_ASC }
|
||||
let(:first_param) { 2 }
|
||||
let(:all_records) { [item1, item2, confidential_item].map { |item| item.to_global_id.to_s } }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sorting by title descending' do
|
||||
it_behaves_like 'sorted paginated query' do
|
||||
let(:sort_param) { :TITLE_DESC }
|
||||
let(:first_param) { 2 }
|
||||
let(:all_records) { [confidential_item, item2, item1].map { |item| item.to_global_id.to_s } }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def item_ids
|
||||
graphql_dig_at(items_data, :node, :id)
|
||||
end
|
||||
end
|
|
@ -55,33 +55,49 @@ RSpec.describe API::Integrations do
|
|||
describe "PUT /projects/:id/#{endpoint}/#{integration.dasherize}" do
|
||||
include_context integration
|
||||
|
||||
# NOTE: Some attributes are not supported for PUT requests, even though in most cases they should be.
|
||||
# For some of them the problem is somewhere else, i.e. most chat integrations don't support the `*_channel`
|
||||
# fields but they're incorrectly included in `#fields`.
|
||||
#
|
||||
# We can fix these manually, or with a generic approach like https://gitlab.com/gitlab-org/gitlab/-/issues/348208
|
||||
let(:missing_channel_attributes) { %i[push_channel issue_channel confidential_issue_channel merge_request_channel note_channel confidential_note_channel tag_push_channel pipeline_channel wiki_page_channel] }
|
||||
let(:missing_attributes) do
|
||||
{
|
||||
datadog: %i[archive_trace_events],
|
||||
discord: missing_channel_attributes + %i[branches_to_be_notified notify_only_broken_pipelines],
|
||||
hangouts_chat: missing_channel_attributes + %i[notify_only_broken_pipelines],
|
||||
jira: %i[issues_enabled project_key vulnerabilities_enabled vulnerabilities_issuetype],
|
||||
mattermost: %i[deployment_channel labels_to_be_notified],
|
||||
microsoft_teams: missing_channel_attributes,
|
||||
mock_ci: %i[enable_ssl_verification],
|
||||
prometheus: %i[manual_configuration],
|
||||
slack: %i[alert_events alert_channel deployment_channel labels_to_be_notified],
|
||||
unify_circuit: missing_channel_attributes + %i[branches_to_be_notified notify_only_broken_pipelines],
|
||||
webex_teams: missing_channel_attributes + %i[branches_to_be_notified notify_only_broken_pipelines]
|
||||
}
|
||||
end
|
||||
|
||||
it "updates #{integration} settings and returns the correct fields" do
|
||||
put api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user), params: integration_attrs
|
||||
supported_attrs = integration_attrs.without(missing_attributes.fetch(integration.to_sym, []))
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
||||
current_integration = project.integrations.first
|
||||
events = current_integration.event_names.empty? ? ["foo"].freeze : current_integration.event_names
|
||||
query_strings = []
|
||||
events.map(&:to_sym).each do |event|
|
||||
event_value = !current_integration[event]
|
||||
query_strings << "#{event}=#{event_value}"
|
||||
integration_attrs[event] = event_value if integration_attrs[event].present?
|
||||
end
|
||||
query_strings = query_strings.join('&')
|
||||
|
||||
put api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}?#{query_strings}", user), params: integration_attrs
|
||||
put api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user), params: supported_attrs
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response['slug']).to eq(dashed_integration)
|
||||
events.each do |event|
|
||||
next if event == "foo"
|
||||
|
||||
expect(project.integrations.first[event]).not_to eq(current_integration[event]),
|
||||
"expected #{!current_integration[event]} for event #{event} for #{endpoint} #{current_integration.title}, got #{current_integration[event]}"
|
||||
current_integration = project.integrations.first
|
||||
expect(current_integration).to have_attributes(supported_attrs)
|
||||
assert_correct_response_fields(json_response['properties'].keys, current_integration)
|
||||
|
||||
# Flip all booleans and verify that we can set these too
|
||||
flipped_attrs = supported_attrs.transform_values do |value|
|
||||
[true, false].include?(value) ? !value : value
|
||||
end
|
||||
|
||||
assert_correct_response_fields(json_response['properties'].keys, current_integration)
|
||||
put api("/projects/#{project.id}/#{endpoint}/#{dashed_integration}", user), params: flipped_attrs
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(project.integrations.first).to have_attributes(flipped_attrs)
|
||||
end
|
||||
|
||||
it "returns if required fields missing" do
|
||||
|
|
|
@ -22,6 +22,18 @@ RSpec.describe ::Packages::Maven::Metadata::AppendPackageFileService do
|
|||
expect_file("#{metadata_file_name}.sha256")
|
||||
expect_file("#{metadata_file_name}.sha512")
|
||||
end
|
||||
|
||||
context 'with FIPS mode', :fips_mode do
|
||||
it 'does not generate file_md5' do
|
||||
expect { subject }.to change { package.package_files.count }.by(4)
|
||||
expect(subject).to be_success
|
||||
|
||||
expect_file(metadata_file_name, with_content: content, with_content_type: 'application/xml', fips: true)
|
||||
expect_file("#{metadata_file_name}.sha1", fips: true)
|
||||
expect_file("#{metadata_file_name}.sha256", fips: true)
|
||||
expect_file("#{metadata_file_name}.sha512", fips: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with nil content' do
|
||||
|
@ -36,17 +48,22 @@ RSpec.describe ::Packages::Maven::Metadata::AppendPackageFileService do
|
|||
it_behaves_like 'returning an error service response', message: 'package is not set'
|
||||
end
|
||||
|
||||
def expect_file(file_name, with_content: nil, with_content_type: '')
|
||||
def expect_file(file_name, fips: false, with_content: nil, with_content_type: '')
|
||||
package_file = package.package_files.recent.with_file_name(file_name).first
|
||||
|
||||
expect(package_file.file).to be_present
|
||||
expect(package_file.file_name).to eq(file_name)
|
||||
expect(package_file.size).to be > 0
|
||||
expect(package_file.file_md5).to be_present
|
||||
expect(package_file.file_sha1).to be_present
|
||||
expect(package_file.file_sha256).to be_present
|
||||
expect(package_file.file.content_type).to eq(with_content_type)
|
||||
|
||||
if fips
|
||||
expect(package_file.file_md5).not_to be_present
|
||||
else
|
||||
expect(package_file.file_md5).to be_present
|
||||
end
|
||||
|
||||
if with_content
|
||||
expect(package_file.file.read).to eq(with_content)
|
||||
end
|
||||
|
|
|
@ -8,8 +8,36 @@ Integration.available_integration_names.each do |integration|
|
|||
let(:integration_method) { Project.integration_association_name(integration) }
|
||||
let(:integration_klass) { Integration.integration_name_to_model(integration) }
|
||||
let(:integration_instance) { integration_klass.new }
|
||||
let(:integration_fields) { integration_instance.fields }
|
||||
let(:integration_attrs_list) { integration_fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
|
||||
|
||||
# Build a list of all attributes that an integration supports.
|
||||
let(:integration_attrs_list) do
|
||||
integration_fields + integration_events + custom_attributes.fetch(integration.to_sym, [])
|
||||
end
|
||||
|
||||
# Attributes defined as fields.
|
||||
let(:integration_fields) do
|
||||
integration_instance.fields.map { _1[:name].to_sym }
|
||||
end
|
||||
|
||||
# Attributes for configurable event triggers.
|
||||
let(:integration_events) do
|
||||
integration_instance.configurable_events.map { IntegrationsHelper.integration_event_field_name(_1).to_sym }
|
||||
end
|
||||
|
||||
# Other special cases, this list might be incomplete.
|
||||
#
|
||||
# Some of these won't be needed anymore after we've converted them to use the field DSL
|
||||
# in https://gitlab.com/gitlab-org/gitlab/-/issues/354899.
|
||||
#
|
||||
# Others like `comment_on_event_disabled` are actual columns on `integrations`, maybe we should migrate
|
||||
# these to fields as well.
|
||||
let(:custom_attributes) do
|
||||
{
|
||||
jira: %i[comment_on_event_enabled jira_issue_transition_automatic jira_issue_transition_id project_key
|
||||
issues_enabled vulnerabilities_enabled vulnerabilities_issuetype]
|
||||
}
|
||||
end
|
||||
|
||||
let(:integration_attrs) do
|
||||
integration_attrs_list.inject({}) do |hash, k|
|
||||
if k =~ /^(token*|.*_token|.*_key)/
|
||||
|
@ -32,9 +60,11 @@ Integration.available_integration_names.each do |integration|
|
|||
hash.merge!(k => 1234)
|
||||
elsif integration == 'jira' && k == :jira_issue_transition_id
|
||||
hash.merge!(k => '1,2,3')
|
||||
elsif integration == 'jira' && k == :jira_issue_transition_automatic
|
||||
hash.merge!(k => true)
|
||||
elsif integration == 'emails_on_push' && k == :recipients
|
||||
hash.merge!(k => 'foo@bar.com')
|
||||
elsif integration == 'slack' || integration == 'mattermost' && k == :labels_to_be_notified_behavior
|
||||
elsif (integration == 'slack' || integration == 'mattermost') && k == :labels_to_be_notified_behavior
|
||||
hash.merge!(k => "match_any")
|
||||
else
|
||||
hash.merge!(k => "someword")
|
||||
|
|
|
@ -1,8 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
require_relative '../../../tooling/quality/test_level'
|
||||
|
||||
RSpec.describe Quality::TestLevel do
|
||||
describe 'TEST_LEVEL_FOLDERS constant' do
|
||||
it 'all directories it refers to exists', :aggregate_failures do
|
||||
ee_only_directories = %w[
|
||||
lib/ee/gitlab/background_migration
|
||||
elastic
|
||||
elastic_integration
|
||||
replicators
|
||||
]
|
||||
|
||||
described_class::TEST_LEVEL_FOLDERS.values.flatten.each do |dir|
|
||||
next if ee_only_directories.include?(dir) && !Gitlab.ee?
|
||||
|
||||
spec_directory = if ee_only_directories.include?(dir)
|
||||
File.join('ee', 'spec', dir)
|
||||
else
|
||||
File.join('spec', dir)
|
||||
end
|
||||
|
||||
expect(File.exist?(spec_directory)).to eq(true), "#{spec_directory} does not exist!"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#pattern' do
|
||||
context 'when level is all' do
|
||||
it 'returns a pattern' do
|
||||
|
@ -21,7 +46,7 @@ RSpec.describe Quality::TestLevel do
|
|||
context 'when level is unit' do
|
||||
it 'returns a pattern' do
|
||||
expect(subject.pattern(:unit))
|
||||
.to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,events,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,metrics_server,models,policies,presenters,rack_servers,replicators,routing,rubocop,scripts,serializers,services,sidekiq,sidekiq_cluster,spam,support_specs,tasks,uploaders,validators,views,workers,tooling,component}{,/**/}*_spec.rb")
|
||||
.to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,events,factories,finders,frontend,graphql,haml_lint,helpers,initializers,lib,metrics_server,models,policies,presenters,rack_servers,replicators,routing,rubocop,scripts,serializers,services,sidekiq,sidekiq_cluster,spam,support_specs,tasks,uploaders,validators,views,workers,tooling,components}{,/**/}*_spec.rb")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -89,56 +114,56 @@ RSpec.describe Quality::TestLevel do
|
|||
context 'when level is frontend_fixture' do
|
||||
it 'returns a regexp' do
|
||||
expect(subject.regexp(:frontend_fixture))
|
||||
.to eq(%r{spec/(frontend/fixtures)})
|
||||
.to eq(%r{spec/(frontend/fixtures)/})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when level is unit' do
|
||||
it 'returns a regexp' do
|
||||
expect(subject.regexp(:unit))
|
||||
.to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|events|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|metrics_server|models|policies|presenters|rack_servers|replicators|routing|rubocop|scripts|serializers|services|sidekiq|sidekiq_cluster|spam|support_specs|tasks|uploaders|validators|views|workers|tooling|component)})
|
||||
.to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|events|factories|finders|frontend|graphql|haml_lint|helpers|initializers|lib|metrics_server|models|policies|presenters|rack_servers|replicators|routing|rubocop|scripts|serializers|services|sidekiq|sidekiq_cluster|spam|support_specs|tasks|uploaders|validators|views|workers|tooling|components)/})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when level is migration' do
|
||||
it 'returns a regexp' do
|
||||
expect(subject.regexp(:migration))
|
||||
.to eq(%r{spec/(migrations|lib/gitlab/background_migration|lib/ee/gitlab/background_migration)})
|
||||
.to eq(%r{spec/(migrations|lib/gitlab/background_migration|lib/ee/gitlab/background_migration)/})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when level is background_migration' do
|
||||
it 'returns a regexp' do
|
||||
expect(subject.regexp(:background_migration))
|
||||
.to eq(%r{spec/(lib/gitlab/background_migration|lib/ee/gitlab/background_migration)})
|
||||
.to eq(%r{spec/(lib/gitlab/background_migration|lib/ee/gitlab/background_migration)/})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when level is integration' do
|
||||
it 'returns a regexp' do
|
||||
expect(subject.regexp(:integration))
|
||||
.to eq(%r{spec/(commands|controllers|mailers|requests)})
|
||||
.to eq(%r{spec/(commands|controllers|mailers|requests)/})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when level is system' do
|
||||
it 'returns a regexp' do
|
||||
expect(subject.regexp(:system))
|
||||
.to eq(%r{spec/(features)})
|
||||
.to eq(%r{spec/(features)/})
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a prefix' do
|
||||
it 'returns a regexp' do
|
||||
expect(described_class.new('ee/').regexp(:system))
|
||||
.to eq(%r{(ee/)spec/(features)})
|
||||
.to eq(%r{(ee/)spec/(features)/})
|
||||
end
|
||||
end
|
||||
|
||||
context 'with several prefixes' do
|
||||
it 'returns a regexp' do
|
||||
expect(described_class.new(['', 'ee/', 'jh/']).regexp(:system))
|
||||
.to eq(%r{(|ee/|jh/)spec/(features)})
|
||||
.to eq(%r{(|ee/|jh/)spec/(features)/})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -160,4 +160,10 @@ RSpec.describe GitlabUploader do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.version' do
|
||||
subject { uploader_class.version }
|
||||
|
||||
it { expect { subject }.to raise_error(RuntimeError, /not supported/) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,7 +32,6 @@ module Quality
|
|||
haml_lint
|
||||
helpers
|
||||
initializers
|
||||
javascripts
|
||||
lib
|
||||
metrics_server
|
||||
models
|
||||
|
@ -55,7 +54,7 @@ module Quality
|
|||
views
|
||||
workers
|
||||
tooling
|
||||
component
|
||||
components
|
||||
],
|
||||
integration: %w[
|
||||
commands
|
||||
|
@ -82,6 +81,10 @@ module Quality
|
|||
@regexps[level] ||= Regexp.new("#{prefixes_for_regex}spec/#{folders_regex(level)}").freeze
|
||||
end
|
||||
|
||||
def legacy_factories_regexp
|
||||
@legacy_factories_regexp ||= %r{spec/factories_spec.rb}.freeze
|
||||
end
|
||||
|
||||
def level_for(file_path)
|
||||
case file_path
|
||||
# Detect migration first since some background migration tests are under
|
||||
|
@ -97,6 +100,8 @@ module Quality
|
|||
:integration
|
||||
when regexp(:system)
|
||||
:system
|
||||
when legacy_factories_regexp
|
||||
:unit
|
||||
else
|
||||
raise UnknownTestLevelError, "Test level for #{file_path} couldn't be set. Please rename the file properly or change the test level detection regexes in #{__FILE__}."
|
||||
end
|
||||
|
@ -149,11 +154,11 @@ module Quality
|
|||
def folders_regex(level)
|
||||
case level
|
||||
when :migration
|
||||
"(#{migration_and_background_migration_folders.join('|')})"
|
||||
"(#{migration_and_background_migration_folders.join('|')})/"
|
||||
when :all
|
||||
''
|
||||
else
|
||||
"(#{TEST_LEVEL_FOLDERS.fetch(level).join('|')})"
|
||||
"(#{TEST_LEVEL_FOLDERS.fetch(level).join('|')})/"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -987,10 +987,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/visual-review-tools/-/visual-review-tools-1.7.3.tgz#9ea641146436da388ffbad25d7f2abe0df52c235"
|
||||
integrity sha512-NMV++7Ew1FSBDN1xiZaauU9tfeSfgDHcOLpn+8bGpP+O5orUPm2Eu66R5eC5gkjBPaXosNAxNWtriee+aFk4+g==
|
||||
|
||||
"@graphql-eslint/eslint-plugin@3.10.3":
|
||||
version "3.10.3"
|
||||
resolved "https://registry.yarnpkg.com/@graphql-eslint/eslint-plugin/-/eslint-plugin-3.10.3.tgz#5e77f18b18769fe68565a76cc33a094576809b5c"
|
||||
integrity sha512-QQiTBw5T28INXayJHHp8lsfiJoanJIYuTqqA0axKlSyDMtNlbfsL3Kc+a+hth29Fw7zyi9XMGAhTDmIOkpskhg==
|
||||
"@graphql-eslint/eslint-plugin@3.10.4":
|
||||
version "3.10.4"
|
||||
resolved "https://registry.yarnpkg.com/@graphql-eslint/eslint-plugin/-/eslint-plugin-3.10.4.tgz#fcc3b54ef1d6d38e89daf848d93be2d5359fef98"
|
||||
integrity sha512-+Vmi1E5uSWOgtylosHS3HYWHF+x0GMgaMOHmSfOflTE51VXnLM2tvGKMd3lpyngCEZG1wjvqdlB5rn2iwhhzqA==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.16.7"
|
||||
"@graphql-tools/code-file-loader" "^7.2.14"
|
||||
|
|
Loading…
Reference in New Issue