Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-06-13 12:08:29 +00:00
parent 8dae4070d2
commit cdd5eba514
39 changed files with 736 additions and 159 deletions

View File

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

View File

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

View File

@ -60,7 +60,7 @@ export default {
},
modalCloseButton: {
text: __('Close'),
attributes: [{ variant: 'info' }],
attributes: [{ variant: 'confirm' }],
},
};
</script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
= form_errors(@group)
= form_errors(@group, pajamas_alert: true)
= render 'shared/group_form', f: f, autofocus: true
.row

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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