Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
a4bd5f29
Commit
a4bd5f29
authored
Mar 03, 2021
by
GitLab Bot
Browse files
Options
Browse Files
Download
Plain Diff
Automatic merge of gitlab-org/gitlab master
parents
bfed9bca
fbaac5e7
Changes
20
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
306 additions
and
129 deletions
+306
-129
app/graphql/types/todo_action_enum.rb
app/graphql/types/todo_action_enum.rb
+9
-7
doc/api/graphql/reference/index.md
doc/api/graphql/reference/index.md
+9
-7
ee/app/graphql/ee/resolvers/namespace_projects_resolver.rb
ee/app/graphql/ee/resolvers/namespace_projects_resolver.rb
+7
-1
ee/app/models/ee/project.rb
ee/app/models/ee/project.rb
+4
-0
ee/changelogs/unreleased/mo-filter-group-projects-by-ids-graphql.yml
...gs/unreleased/mo-filter-group-projects-by-ids-graphql.yml
+5
-0
ee/spec/graphql/ee/resolvers/namespace_projects_resolver_spec.rb
.../graphql/ee/resolvers/namespace_projects_resolver_spec.rb
+21
-2
ee/spec/models/project_spec.rb
ee/spec/models/project_spec.rb
+13
-0
qa/qa/ee/page/project/secure/license_compliance.rb
qa/qa/ee/page/project/secure/license_compliance.rb
+2
-2
spec/graphql/features/authorization_spec.rb
spec/graphql/features/authorization_spec.rb
+21
-16
spec/graphql/features/feature_flag_spec.rb
spec/graphql/features/feature_flag_spec.rb
+2
-1
spec/graphql/mutations/release_asset_links/create_spec.rb
spec/graphql/mutations/release_asset_links/create_spec.rb
+5
-5
spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb
...graphql/resolvers/concerns/caching_array_resolver_spec.rb
+1
-2
spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
...l/resolvers/error_tracking/sentry_errors_resolver_spec.rb
+3
-3
spec/graphql/resolvers/group_labels_resolver_spec.rb
spec/graphql/resolvers/group_labels_resolver_spec.rb
+1
-1
spec/graphql/resolvers/issues_resolver_spec.rb
spec/graphql/resolvers/issues_resolver_spec.rb
+4
-4
spec/graphql/resolvers/labels_resolver_spec.rb
spec/graphql/resolvers/labels_resolver_spec.rb
+11
-25
spec/graphql/resolvers/merge_requests_resolver_spec.rb
spec/graphql/resolvers/merge_requests_resolver_spec.rb
+1
-1
spec/graphql/resolvers/release_milestones_resolver_spec.rb
spec/graphql/resolvers/release_milestones_resolver_spec.rb
+1
-1
spec/support/graphql/resolver_factories.rb
spec/support/graphql/resolver_factories.rb
+40
-0
spec/support/helpers/graphql_helpers.rb
spec/support/helpers/graphql_helpers.rb
+146
-51
No files found.
app/graphql/types/todo_action_enum.rb
View file @
a4bd5f29
...
...
@@ -2,12 +2,14 @@
module
Types
class
TodoActionEnum
<
BaseEnum
value
'assigned'
,
value:
1
value
'mentioned'
,
value:
2
value
'build_failed'
,
value:
3
value
'marked'
,
value:
4
value
'approval_required'
,
value:
5
value
'unmergeable'
,
value:
6
value
'directly_addressed'
,
value:
7
value
'assigned'
,
value:
1
,
description:
'User was assigned.'
value
'mentioned'
,
value:
2
,
description:
'User was mentioned.'
value
'build_failed'
,
value:
3
,
description:
'Build triggered by the user failed.'
value
'marked'
,
value:
4
,
description:
'User added a TODO.'
value
'approval_required'
,
value:
5
,
description:
'User was set as an approver.'
value
'unmergeable'
,
value:
6
,
description:
'Merge request authored by the user could not be merged.'
value
'directly_addressed'
,
value:
7
,
description:
'User was directly addressed.'
value
'merge_train_removed'
,
value:
8
,
description:
'Merge request authored by the user was removed from the merge train.'
value
'review_requested'
,
value:
9
,
description:
'Review was requested from the user.'
end
end
doc/api/graphql/reference/index.md
View file @
a4bd5f29
...
...
@@ -5858,13 +5858,15 @@ State of a test report.
| Value | Description |
| ----- | ----------- |
|
`approval_required`
| |
|
`assigned`
| |
|
`build_failed`
| |
|
`directly_addressed`
| |
|
`marked`
| |
|
`mentioned`
| |
|
`unmergeable`
| |
|
`approval_required`
| User was set as an approver. |
|
`assigned`
| User was assigned. |
|
`build_failed`
| Build triggered by the user failed. |
|
`directly_addressed`
| User was directly addressed. |
|
`marked`
| User added a TODO. |
|
`mentioned`
| User was mentioned. |
|
`merge_train_removed`
| Merge request authored by the user was removed from the merge train. |
|
`review_requested`
| Review was requested from the user. |
|
`unmergeable`
| Merge request authored by the user could not be merged. |
### TodoStateEnum
...
...
ee/app/graphql/ee/resolvers/namespace_projects_resolver.rb
View file @
a4bd5f29
...
...
@@ -6,15 +6,21 @@ module EE
extend
ActiveSupport
::
Concern
prepended
do
argument
:has_code_coverage
,
GraphQL
::
BOOLEAN_TYPE
,
required:
false
,
default_value:
false
,
description:
'Returns only the projects which have code coverage.'
argument
:has_vulnerabilities
,
GraphQL
::
BOOLEAN_TYPE
,
required:
false
,
default_value:
false
,
description:
'Returns only the projects which have vulnerabilities.'
end
def
resolve
(
include_subgroups
:,
search
:,
sort
:,
has_vulnerabilities:
false
)
def
resolve
(
include_subgroups
:,
search
:,
sort
:,
has_vulnerabilities:
false
,
has_code_coverage:
false
)
projects
=
super
(
include_subgroups:
include_subgroups
,
search:
search
,
sort:
sort
)
projects
=
projects
.
has_vulnerabilities
if
has_vulnerabilities
projects
=
projects
.
with_code_coverage
if
has_code_coverage
projects
=
projects
.
order_by_total_repository_size_excess_desc
(
namespace
.
actual_size_limit
)
if
sort
==
:storage
projects
end
...
...
ee/app/models/ee/project.rb
View file @
a4bd5f29
...
...
@@ -130,6 +130,10 @@ module EE
.
limit
(
limit
)
end
scope
:with_code_coverage
,
->
do
joins
(
:daily_build_group_report_results
).
merge
(
::
Ci
::
DailyBuildGroupReportResult
.
with_coverage
.
with_default_branch
).
group
(
:id
)
end
scope
:including_project
,
->
(
project
)
{
where
(
id:
project
)
}
scope
:with_wiki_enabled
,
->
{
with_feature_enabled
(
:wiki
)
}
scope
:within_shards
,
->
(
shard_names
)
{
where
(
repository_storage:
Array
(
shard_names
))
}
...
...
ee/changelogs/unreleased/mo-filter-group-projects-by-ids-graphql.yml
0 → 100644
View file @
a4bd5f29
---
title
:
Query group projects by code coverage with GraphQL
merge_request
:
55182
author
:
type
:
added
ee/spec/graphql/ee/resolvers/namespace_projects_resolver_spec.rb
View file @
a4bd5f29
...
...
@@ -57,15 +57,34 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
it
{
is_expected
.
to
eq
([
project_1
,
project_2
,
project_3
])
}
end
end
context
'has_code_coverage'
do
subject
(
:projects
)
{
resolve_projects
(
has_code_coverage:
has_code_coverage
)
}
let!
(
:coverage_1
)
{
create
(
:ci_daily_build_group_report_result
,
project:
project_1
)
}
context
'when has_code_coverage is false'
do
let
(
:has_code_coverage
)
{
false
}
it
{
is_expected
.
to
contain_exactly
(
project_1
,
project_2
)
}
end
context
'when has_code_coverage is true'
do
let
(
:has_code_coverage
)
{
true
}
it
{
is_expected
.
to
contain_exactly
(
project_1
)
}
end
end
end
end
def
resolve_projects
(
has_vulnerabilities:
false
,
sort: :similarity
)
def
resolve_projects
(
has_vulnerabilities:
false
,
sort: :similarity
,
has_code_coverage:
false
)
args
=
{
include_subgroups:
false
,
has_vulnerabilities:
has_vulnerabilities
,
sort:
sort
,
search:
nil
search:
nil
,
has_code_coverage:
has_code_coverage
}
resolve
(
described_class
,
obj:
group
,
args:
args
,
ctx:
{
current_user:
current_user
})
...
...
ee/spec/models/project_spec.rb
View file @
a4bd5f29
...
...
@@ -363,6 +363,19 @@ RSpec.describe Project do
it
{
is_expected
.
to
eq
([
project_2
,
project_3
,
project_1
])
}
end
describe
'.with_code_coverage'
do
let_it_be
(
:project_1
)
{
create
(
:project
)
}
let_it_be
(
:project_2
)
{
create
(
:project
)
}
let_it_be
(
:project_3
)
{
create
(
:project
)
}
let!
(
:coverage_1
)
{
create
(
:ci_daily_build_group_report_result
,
project:
project_1
)
}
let!
(
:coverage_2
)
{
create
(
:ci_daily_build_group_report_result
,
project:
project_2
)
}
subject
{
described_class
.
with_code_coverage
}
it
{
is_expected
.
to
contain_exactly
(
project_1
,
project_2
)
}
end
end
describe
'validations'
do
...
...
qa/qa/ee/page/project/secure/license_compliance.rb
View file @
a4bd5f29
...
...
@@ -29,7 +29,7 @@ module QA
click_element
:license_add_button
expand_select_list
search_and_select_exact
license
click_element
:approved_license_radio
find
(
'.custom-control-label'
,
text:
'Allow'
).
click
click_element
:add_license_submit_button
has_approved_license?
license
...
...
@@ -46,7 +46,7 @@ module QA
click_element
:license_add_button
expand_select_list
search_and_select_exact
license
click_element
:blacklisted_license_radio
find
(
'.custom-control-label'
,
text:
'Deny'
).
click
click_element
:add_license_submit_button
has_denied_license?
license
...
...
spec/graphql/features/authorization_spec.rb
View file @
a4bd5f29
...
...
@@ -2,17 +2,22 @@
require
'spec_helper'
RSpec
.
describe
'Gitlab::Graphql::Authoriz
ation
'
do
RSpec
.
describe
'Gitlab::Graphql::Authoriz
e
'
do
include
GraphqlHelpers
include
Graphql
::
ResolverFactories
let_it_be
(
:user
)
{
create
(
:user
)
}
let
(
:permission_single
)
{
:foo
}
let
(
:permission_collection
)
{
[
:foo
,
:bar
]
}
let
(
:test_object
)
{
double
(
name:
'My name'
)
}
let
(
:query_string
)
{
'{ item { name } }'
}
let
(
:result
)
{
execute_query
(
query_type
)[
'data'
]
}
let
(
:result
)
do
schema
=
empty_schema
schema
.
use
(
Gitlab
::
Graphql
::
Authorize
)
execute_query
(
query_type
,
schema:
schema
)
end
subject
{
result
[
'item'
]
}
subject
{
result
.
dig
(
'data'
,
'item'
)
}
shared_examples
'authorization with a single permission'
do
it
'returns the protected field when user has permission'
do
...
...
@@ -55,7 +60,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
describe
'with a single permission'
do
let
(
:query_type
)
do
query_factory
do
|
query
|
query
.
field
:item
,
type
,
null:
true
,
resolver:
simple
_resolver
(
test_object
),
authorize:
permission_single
query
.
field
:item
,
type
,
null:
true
,
resolver:
new
_resolver
(
test_object
),
authorize:
permission_single
end
end
...
...
@@ -66,7 +71,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
let
(
:query_type
)
do
permissions
=
permission_collection
query_factory
do
|
qt
|
qt
.
field
:item
,
type
,
null:
true
,
resolver:
simple
_resolver
(
test_object
)
do
qt
.
field
:item
,
type
,
null:
true
,
resolver:
new
_resolver
(
test_object
)
do
authorize
permissions
end
end
...
...
@@ -79,7 +84,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
describe
'Field authorizations when field is a built in type'
do
let
(
:query_type
)
do
query_factory
do
|
query
|
query
.
field
:item
,
type
,
null:
true
,
resolver:
simple
_resolver
(
test_object
)
query
.
field
:item
,
type
,
null:
true
,
resolver:
new
_resolver
(
test_object
)
end
end
...
...
@@ -132,7 +137,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
describe
'Type authorizations'
do
let
(
:query_type
)
do
query_factory
do
|
query
|
query
.
field
:item
,
type
,
null:
true
,
resolver:
simple
_resolver
(
test_object
)
query
.
field
:item
,
type
,
null:
true
,
resolver:
new
_resolver
(
test_object
)
end
end
...
...
@@ -169,7 +174,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
let
(
:query_type
)
do
query_factory
do
|
query
|
query
.
field
:item
,
type
,
null:
true
,
resolver:
simple
_resolver
(
test_object
),
authorize:
permission_2
query
.
field
:item
,
type
,
null:
true
,
resolver:
new
_resolver
(
test_object
),
authorize:
permission_2
end
end
...
...
@@ -188,11 +193,11 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
let
(
:query_type
)
do
query_factory
do
|
query
|
query
.
field
:item
,
type
.
connection_type
,
null:
true
,
resolver:
simple
_resolver
([
test_object
,
second_test_object
])
query
.
field
:item
,
type
.
connection_type
,
null:
true
,
resolver:
new
_resolver
([
test_object
,
second_test_object
])
end
end
subject
{
result
.
dig
(
'item'
,
'edges'
)
}
subject
{
result
.
dig
(
'
data'
,
'
item'
,
'edges'
)
}
it
'returns only the elements visible to the user'
do
permit
(
permission_single
)
...
...
@@ -208,7 +213,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
describe
'limiting connections with multiple objects'
do
let
(
:query_type
)
do
query_factory
do
|
query
|
query
.
field
:item
,
type
.
connection_type
,
null:
true
,
resolver:
simple
_resolver
([
test_object
,
second_test_object
])
query
.
field
:item
,
type
.
connection_type
,
null:
true
,
resolver:
new
_resolver
([
test_object
,
second_test_object
])
end
end
...
...
@@ -232,11 +237,11 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
let
(
:query_type
)
do
query_factory
do
|
query
|
query
.
field
:item
,
[
type
],
null:
true
,
resolver:
simple
_resolver
([
test_object
])
query
.
field
:item
,
[
type
],
null:
true
,
resolver:
new
_resolver
([
test_object
])
end
end
subject
{
result
[
'item'
].
first
}
subject
{
result
.
dig
(
'data'
,
'item'
,
0
)
}
include_examples
'authorization with a single permission'
end
...
...
@@ -260,13 +265,13 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
type_factory
do
|
type
|
type
.
graphql_name
'FakeProjectType'
type
.
field
:test_issues
,
issue_type
.
connection_type
,
null:
false
,
resolver:
simple
_resolver
(
Issue
.
where
(
project:
[
visible_project
,
other_project
]).
order
(
id: :asc
))
resolver:
new
_resolver
(
Issue
.
where
(
project:
[
visible_project
,
other_project
]).
order
(
id: :asc
))
end
end
let
(
:query_type
)
do
query_factory
do
|
query
|
query
.
field
:test_project
,
project_type
,
null:
false
,
resolver:
simple
_resolver
(
visible_project
)
query
.
field
:test_project
,
project_type
,
null:
false
,
resolver:
new
_resolver
(
visible_project
)
end
end
...
...
@@ -281,7 +286,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
end
it
'renders the issues the user has access to'
do
issue_edges
=
result
[
'testProject'
][
'testIssues'
][
'edges'
]
issue_edges
=
result
.
dig
(
'data'
,
'testProject'
,
'testIssues'
,
'edges'
)
issue_ids
=
issue_edges
.
map
{
|
issue_edge
|
issue_edge
[
'node'
]
&
.
fetch
(
'id'
)
}
expect
(
issue_edges
.
size
).
to
eq
(
visible_issues
.
size
)
...
...
spec/graphql/features/feature_flag_spec.rb
View file @
a4bd5f29
...
...
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec
.
describe
'Graphql Field feature flags'
do
include
GraphqlHelpers
include
Graphql
::
ResolverFactories
let_it_be
(
:user
)
{
create
(
:user
)
}
...
...
@@ -23,7 +24,7 @@ RSpec.describe 'Graphql Field feature flags' do
let
(
:query_type
)
do
query_factory
do
|
query
|
query
.
field
:item
,
type
,
null:
true
,
feature_flag:
feature_flag
,
resolver:
simple
_resolver
(
test_object
)
query
.
field
:item
,
type
,
null:
true
,
feature_flag:
feature_flag
,
resolver:
new
_resolver
(
test_object
)
end
end
...
...
spec/graphql/mutations/release_asset_links/create_spec.rb
View file @
a4bd5f29
...
...
@@ -21,9 +21,9 @@ RSpec.describe Mutations::ReleaseAssetLinks::Create do
let
(
:args
)
do
{
project_path:
project_path
,
tag:
tag
,
tag
_name
:
tag
,
name:
name
,
file
path:
filepath
,
direct_asset_
path:
filepath
,
url:
url
}
end
...
...
@@ -44,9 +44,9 @@ RSpec.describe Mutations::ReleaseAssetLinks::Create do
expect
(
release
.
links
.
length
).
to
be
(
1
)
expect
(
last_release_link
.
name
).
to
eq
(
args
[
:name
]
)
expect
(
last_release_link
.
url
).
to
eq
(
args
[
:url
]
)
expect
(
last_release_link
.
filepath
).
to
eq
(
args
[
:filepath
]
)
expect
(
last_release_link
.
name
).
to
eq
(
name
)
expect
(
last_release_link
.
url
).
to
eq
(
url
)
expect
(
last_release_link
.
filepath
).
to
eq
(
filepath
)
end
end
...
...
spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb
View file @
a4bd5f29
...
...
@@ -9,7 +9,6 @@ RSpec.describe ::CachingArrayResolver do
let_it_be
(
:admins
)
{
create_list
(
:user
,
4
,
admin:
true
)
}
let
(
:query_context
)
{
{
current_user:
admins
.
first
}
}
let
(
:max_page_size
)
{
10
}
let
(
:field
)
{
double
(
'Field'
,
max_page_size:
max_page_size
)
}
let
(
:schema
)
do
Class
.
new
(
GitlabSchema
)
do
default_max_page_size
3
...
...
@@ -210,6 +209,6 @@ RSpec.describe ::CachingArrayResolver do
args
=
{
is_admin:
admin
}
opts
=
resolver
.
field_options
allow
(
resolver
).
to
receive
(
:field_options
).
and_return
(
opts
.
merge
(
max_page_size:
max_page_size
))
resolve
(
resolver
,
args:
args
,
ctx:
query_context
,
schema:
schema
,
field:
field
)
resolve
(
resolver
,
args:
args
,
ctx:
query_context
,
schema:
schema
)
end
end
spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
View file @
a4bd5f29
...
...
@@ -19,7 +19,7 @@ RSpec.describe Resolvers::ErrorTracking::SentryErrorsResolver do
end
describe
'#resolve'
do
context
'insufficient user permission'
do
context
'
with
insufficient user permission'
do
let
(
:user
)
{
create
(
:user
)
}
it
'returns nil'
do
...
...
@@ -29,7 +29,7 @@ RSpec.describe Resolvers::ErrorTracking::SentryErrorsResolver do
end
end
context
'
user with
permission'
do
context
'
with sufficient
permission'
do
before
do
project
.
add_developer
(
current_user
)
...
...
@@ -93,7 +93,7 @@ RSpec.describe Resolvers::ErrorTracking::SentryErrorsResolver do
end
it
'returns an externally paginated array'
do
expect
(
resolve_errors
).
to
be_a
Gitlab
::
Graphql
::
ExternallyPaginatedArray
expect
(
resolve_errors
).
to
be_a
Gitlab
::
Graphql
::
Pagination
::
ExternallyPaginatedArrayConnection
end
end
end
...
...
spec/graphql/resolvers/group_labels_resolver_spec.rb
View file @
a4bd5f29
...
...
@@ -42,7 +42,7 @@ RSpec.describe Resolvers::GroupLabelsResolver do
context
'without parent'
do
it
'returns no labels'
do
expect
(
resolve_labels
(
nil
)).
to
eq
(
Label
.
none
)
expect
(
resolve_labels
(
nil
)).
to
be_empty
end
end
...
...
spec/graphql/resolvers/issues_resolver_spec.rb
View file @
a4bd5f29
...
...
@@ -264,7 +264,7 @@ RSpec.describe Resolvers::IssuesResolver do
end
it
'finds a specific issue with iid'
,
:request_store
do
result
=
batch_sync
(
max_queries:
4
)
{
resolve_issues
(
iid:
issue1
.
iid
)
}
result
=
batch_sync
(
max_queries:
4
)
{
resolve_issues
(
iid:
issue1
.
iid
)
.
to_a
}
expect
(
result
).
to
contain_exactly
(
issue1
)
end
...
...
@@ -281,7 +281,7 @@ RSpec.describe Resolvers::IssuesResolver do
it
'finds a specific issue with iids'
,
:request_store
do
result
=
batch_sync
(
max_queries:
4
)
do
resolve_issues
(
iids:
[
issue1
.
iid
])
resolve_issues
(
iids:
[
issue1
.
iid
])
.
to_a
end
expect
(
result
).
to
contain_exactly
(
issue1
)
...
...
@@ -290,7 +290,7 @@ RSpec.describe Resolvers::IssuesResolver do
it
'finds multiple issues with iids'
do
create
(
:issue
,
project:
project
,
author:
current_user
)
expect
(
batch_sync
{
resolve_issues
(
iids:
[
issue1
.
iid
,
issue2
.
iid
])
})
expect
(
batch_sync
{
resolve_issues
(
iids:
[
issue1
.
iid
,
issue2
.
iid
])
.
to_a
})
.
to
contain_exactly
(
issue1
,
issue2
)
end
...
...
@@ -302,7 +302,7 @@ RSpec.describe Resolvers::IssuesResolver do
create
(
:issue
,
project:
another_project
,
iid:
iid
)
end
expect
(
batch_sync
{
resolve_issues
(
iids:
iids
)
}).
to
contain_exactly
(
issue1
,
issue2
)
expect
(
batch_sync
{
resolve_issues
(
iids:
iids
)
.
to_a
}).
to
contain_exactly
(
issue1
,
issue2
)
end
end
end
...
...
spec/graphql/resolvers/labels_resolver_spec.rb
View file @
a4bd5f29
...
...
@@ -42,50 +42,36 @@ RSpec.describe Resolvers::LabelsResolver do
context
'without parent'
do
it
'returns no labels'
do
expect
(
resolve_labels
(
nil
)).
to
eq
(
Label
.
none
)
expect
(
resolve_labels
(
nil
)).
to
be_empty
end
end
context
'
at project level
'
do
context
'
with a parent project
'
do
before_all
do
group
.
add_developer
(
current_user
)
end
# because :include_ancestor_groups, :include_descendant_groups, :only_group_labels default to false
# the `nil` value would be equivalent to passing in `false` so just check for `nil` option
where
(
:include_ancestor_groups
,
:include_descendant_groups
,
:only_group_labels
,
:search_term
,
:test
)
do
nil
|
nil
|
nil
|
nil
|
->
{
expect
(
subject
).
to
contain_exactly
(
label1
,
label2
,
subgroup_label1
,
subgroup_label2
)
}
nil
|
nil
|
true
|
nil
|
->
{
expect
(
subject
).
to
contain_exactly
(
label1
,
label2
,
subgroup_label1
,
subgroup_label2
)
}
nil
|
true
|
nil
|
nil
|
->
{
expect
(
subject
).
to
contain_exactly
(
label1
,
label2
,
subgroup_label1
,
subgroup_label2
,
sub_subgroup_label1
,
sub_subgroup_label2
)
}
nil
|
true
|
true
|
nil
|
->
{
expect
(
subject
).
to
contain_exactly
(
label1
,
label2
,
subgroup_label1
,
subgroup_label2
,
sub_subgroup_label1
,
sub_subgroup_label2
)
}
true
|
nil
|
nil
|
nil
|
->
{
expect
(
subject
).
to
contain_exactly
(
label1
,
label2
,
group_label1
,
group_label2
,
subgroup_label1
,
subgroup_label2
)
}
true
|
nil
|
true
|
nil
|
->
{
expect
(
subject
).
to
contain_exactly
(
label1
,
label2
,
group_label1
,
group_label2
,
subgroup_label1
,
subgroup_label2
)
}
true
|
true
|
nil
|
nil
|
->
{
expect
(
subject
).
to
contain_exactly
(
label1
,
label2
,
group_label1
,
group_label2
,
subgroup_label1
,
subgroup_label2
,
sub_subgroup_label1
,
sub_subgroup_label2
)
}
true
|
true
|
true
|
nil
|
->
{
expect
(
subject
).
to
contain_exactly
(
label1
,
label2
,
group_label1
,
group_label2
,
subgroup_label1
,
subgroup_label2
,
sub_subgroup_label1
,
sub_subgroup_label2
)
}
nil
|
nil
|
nil
|
'new'
|
->
{
expect
(
subject
).
to
contain_exactly
(
label2
,
subgroup_label2
)
}
nil
|
nil
|
true
|
'new'
|
->
{
expect
(
subject
).
to
contain_exactly
(
label2
,
subgroup_label2
)
}
nil
|
true
|
nil
|
'new'
|
->
{
expect
(
subject
).
to
contain_exactly
(
label2
,
subgroup_label2
,
sub_subgroup_label2
)
}
nil
|
true
|
true
|
'new'
|
->
{
expect
(
subject
).
to
contain_exactly
(
label2
,
subgroup_label2
,
sub_subgroup_label2
)
}
true
|
nil
|
nil
|
'new'
|
->
{
expect
(
subject
).
to
contain_exactly
(
label2
,
group_label2
,
subgroup_label2
)
}
true
|
nil
|
true
|
'new'
|
->
{
expect
(
subject
).
to
contain_exactly
(
label2
,
group_label2
,
subgroup_label2
)
}
true
|
true
|
nil
|
'new'
|
->
{
expect
(
subject
).
to
contain_exactly
(
label2
,
group_label2
,
subgroup_label2
,
sub_subgroup_label2
)
}
true
|
true
|
true
|
'new'
|
->
{
expect
(
subject
).
to
contain_exactly
(
label2
,
group_label2
,
subgroup_label2
,
sub_subgroup_label2
)
}
# the expected result is wrapped in a lambda to get around the phase restrictions of RSpec::Parameterized
where
(
:include_ancestor_groups
,
:search_term
,
:expected_labels
)
do
nil
|
nil
|
->
{
[
label1
,
label2
,
subgroup_label1
,
subgroup_label2
]
}
false
|
nil
|
->
{
[
label1
,
label2
,
subgroup_label1
,
subgroup_label2
]
}
true
|
nil
|
->
{
[
label1
,
label2
,
group_label1
,
group_label2
,
subgroup_label1
,
subgroup_label2
]
}
nil
|
'new'
|
->
{
[
label2
,
subgroup_label2
]
}
false
|
'new'
|
->
{
[
label2
,
subgroup_label2
]
}
true
|
'new'
|
->
{
[
label2
,
group_label2
,
subgroup_label2
]
}
end
with_them
do
let
(
:params
)
do
{
include_ancestor_groups:
include_ancestor_groups
,
include_descendant_groups:
include_descendant_groups
,
only_group_labels:
only_group_labels
,
search_term:
search_term
}
end
subject
{
resolve_labels
(
project
,
params
)
}
it
{
self
.
instance_exec
(
&
test
)
}
specify
{
expect
(
subject
).
to
match_array
(
instance_exec
(
&
expected_labels
)
)
}
end
end
end
...
...
spec/graphql/resolvers/merge_requests_resolver_spec.rb
View file @
a4bd5f29
...
...
@@ -69,7 +69,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do
it
'batch-resolves by target project full path and IIDS'
,
:request_store
do
result
=
batch_sync
(
max_queries:
queries_per_project
)
do
resolve_mr
(
project
,
iids:
[
iid_1
,
iid_2
])
resolve_mr
(
project
,
iids:
[
iid_1
,
iid_2
])
.
to_a
end
expect
(
result
).
to
contain_exactly
(
merge_request_1
,
merge_request_2
)
...
...
spec/graphql/resolvers/release_milestones_resolver_spec.rb
View file @
a4bd5f29
...
...
@@ -14,7 +14,7 @@ RSpec.describe Resolvers::ReleaseMilestonesResolver do
describe
'#resolve'
do
it
"uses offset-pagination"
do
expect
(
resolved
).
to
be_a
(
::
Gitlab
::
Graphql
::
Pagination
::
Offset
PaginatedRela
tion
)
expect
(
resolved
).
to
be_a
(
::
Gitlab
::
Graphql
::
Pagination
::
Offset
ActiveRecordRelationConnec
tion
)
end
it
"includes the release's milestones in the returned OffsetActiveRecordRelationConnection"
do
...
...
spec/support/graphql/resolver_factories.rb
0 → 100644
View file @
a4bd5f29
# frozen_string_literal: true
module
Graphql
module
ResolverFactories
def
new_resolver
(
resolved_value
=
'Resolved value'
,
method: :resolve
)
case
method
when
:resolve
simple_resolver
(
resolved_value
)
when
:find_object
find_object_resolver
(
resolved_value
)
else
raise
"Cannot build a resolver for
#{
method
}
"
end
end
private
def
simple_resolver
(
resolved_value
=
'Resolved value'
)
Class
.
new
(
Resolvers
::
BaseResolver
)
do
define_method
:resolve
do
|**
_args
|
resolved_value
end
end
end
def
find_object_resolver
(
resolved_value
=
'Found object'
)
Class
.
new
(
Resolvers
::
BaseResolver
)
do
include
::
Gitlab
::
Graphql
::
Authorize
::
AuthorizeResource
def
resolve
(
**
args
)
authorized_find!
(
**
args
)
end
define_method
:find_object
do
|**
_args
|
resolved_value
end
end
end
end
end
spec/support/helpers/graphql_helpers.rb
View file @
a4bd5f29
...
...
@@ -16,32 +16,127 @@ module GraphqlHelpers
underscored_field_name
.
to_s
.
camelize
(
:lower
)
end
# Run a loader's named resolver in a way that closely mimics the framework.
def
self
.
deep_fieldnamerize
(
map
)
map
.
to_h
do
|
k
,
v
|
[
fieldnamerize
(
k
),
v
.
is_a?
(
Hash
)
?
deep_fieldnamerize
(
v
)
:
v
]
end
end
# Run this resolver exactly as it would be called in the framework. This
# includes all authorization hooks, all argument processing and all result
# wrapping.
# see: GraphqlHelpers#resolve_field
def
resolve
(
resolver_class
,
# [Class[<= BaseResolver]] The resolver at test.
obj:
nil
,
# [Any] The BaseObject#object for the resolver (available as `#object` in the resolver).
args:
{},
# [Hash] The arguments to the resolver (using client names).
ctx:
{},
# [#to_h] The current context values.
schema:
GitlabSchema
,
# [GraphQL::Schema] Schema to use during execution.
parent: :not_given
,
# A GraphQL query node to be passed as the `:parent` extra.
lookahead: :not_given
# A GraphQL lookahead object to be passed as the `:lookahead` extra.
)
# All resolution goes through fields, so we need to create one here that
# uses our resolver. Thankfully, apart from the field name, resolvers
# contain all the configuration needed to define one.
field_options
=
resolver_class
.
field_options
.
merge
(
name:
'field_value'
)
field
=
::
Types
::
BaseField
.
new
(
**
field_options
)
# All mutations accept a single `:input` argument. Wrap arguments here.
# See the unwrapping below in GraphqlHelpers#resolve_field
args
=
{
input:
args
}
if
resolver_class
<=
::
Mutations
::
BaseMutation
&&
!
args
.
key?
(
:input
)
resolve_field
(
field
,
obj
,
args:
args
,
ctx:
ctx
,
schema:
schema
,
object_type:
resolver_parent
,
extras:
{
parent:
parent
,
lookahead:
lookahead
})
end
# Resolve the value of a field on an object.
#
# Use this method to test individual fields within type specs.
#
# e.g.
#
# issue = create(:issue)
# user = issue.author
# project = issue.project
#
#
First the `ready?` method is called. If it turns out that the resolver is not
#
ready, then the early return is returned instead.
#
resolve_field(:author, issue, current_user: user, object_type: ::Types::IssueType)
#
resolve_field(:issue, project, args: { iid: issue.iid }, current_user: user, object_type: ::Types::ProjectType)
#
# Then the resolve method is called.
def
resolve
(
resolver_class
,
args:
{},
lookahead: :not_given
,
parent: :not_given
,
**
resolver_args
)
args
=
aliased_args
(
resolver_class
,
args
)
args
[
:parent
]
=
parent
unless
parent
==
:not_given
args
[
:lookahead
]
=
lookahead
unless
lookahead
==
:not_given
resolver
=
resolver_instance
(
resolver_class
,
**
resolver_args
)
ready
,
early_return
=
sync_all
{
resolver
.
ready?
(
**
args
)
}
# The `object_type` defaults to the `described_class`, so when called from type specs,
# the above can be written as:
#
# # In project_type_spec.rb
# resolve_field(:author, issue, current_user: user)
#
# # In issue_type_spec.rb
# resolve_field(:issue, project, args: { iid: issue.iid }, current_user: user)
#
# NB: Arguments are passed from the client's perspective. If there is an argument
# `foo` aliased as `bar`, then we would pass `args: { bar: the_value }`, and
# types are checked before resolution.
def
resolve_field
(
field
,
# An instance of `BaseField`, or the name of a field on the current described_class
object
,
# The current object of the `BaseObject` this field 'belongs' to
args:
{},
# Field arguments (keys will be fieldnamerized)
ctx:
{},
# Context values (important ones are :current_user)
extras:
{},
# Stub values for field extras (parent and lookahead)
current_user: :not_given
,
# The current user (specified explicitly, overrides ctx[:current_user])
schema:
GitlabSchema
,
# A specific schema instance
object_type:
described_class
# The `BaseObject` type this field belongs to
)
field
=
to_base_field
(
field
,
object_type
)
ctx
[
:current_user
]
=
current_user
unless
current_user
==
:not_given
query
=
GraphQL
::
Query
.
new
(
schema
,
context:
ctx
.
to_h
)
extras
[
:lookahead
]
=
negative_lookahead
if
extras
[
:lookahead
]
==
:not_given
&&
field
.
extras
.
include?
(
:lookahead
)
query_ctx
=
query
.
context
mock_extras
(
query_ctx
,
**
extras
)
parent
=
object_type
.
authorized_new
(
object
,
query_ctx
)
raise
UnauthorizedObject
unless
parent
# TODO: This will need to change when we move to the interpreter:
# At that point, arguments will be a plain ruby hash rather than
# an Arguments object
# see: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27536
# https://gitlab.com/gitlab-org/gitlab/-/issues/210556
arguments
=
field
.
to_graphql
.
arguments_class
.
new
(
GraphqlHelpers
.
deep_fieldnamerize
(
args
),
context:
query_ctx
,
defaults_used:
[]
)
# we enable the request store so we can track gitaly calls.
::
Gitlab
::
WithRequestStore
.
with_request_store
do
# TODO: This will need to change when we move to the interpreter - at that
# point we will call `field#resolve`
return
early_return
unless
ready
# Unwrap the arguments to mutations. This pairs with the wrapping in GraphqlHelpers#resolve
# If arguments are not wrapped first, then arguments processing will raise.
# If arguments are not unwrapped here, then the resolve method of the mutation will raise argument errors.
arguments
=
arguments
.
to_kwargs
[
:input
]
if
field
.
resolver
&&
field
.
resolver
<=
::
Mutations
::
BaseMutation
resolver
.
resolve
(
**
args
)
field
.
resolve_field
(
parent
,
arguments
,
query_ctx
)
end
end
# TODO: Remove this method entirely when GraphqlHelpers uses real resolve_field
# see: https://gitlab.com/gitlab-org/gitlab/-/issues/287791
def
aliased_args
(
resolver
,
args
)
definitions
=
resolver
.
arguments
def
mock_extras
(
context
,
parent: :not_given
,
lookahead: :not_given
)
allow
(
context
).
to
receive
(
:parent
).
and_return
(
parent
)
unless
parent
==
:not_given
allow
(
context
).
to
receive
(
:lookahead
).
and_return
(
lookahead
)
unless
lookahead
==
:not_given
end
args
.
transform_keys
do
|
k
|
definitions
[
GraphqlHelpers
.
fieldnamerize
(
k
)]
&
.
keyword
||
k
end
# a synthetic BaseObject type to be used in resolver specs. See `GraphqlHelpers#resolve`
def
resolver_parent
@resolver_parent
||=
fresh_object_type
(
'ResolverParent'
)
end
def
fresh_object_type
(
name
=
'Object'
)
Class
.
new
(
::
Types
::
BaseObject
)
{
graphql_name
name
}
end
def
resolver_instance
(
resolver_class
,
obj:
nil
,
ctx:
{},
field:
nil
,
schema:
GitlabSchema
)
...
...
@@ -124,9 +219,9 @@ module GraphqlHelpers
lazy_vals
.
is_a?
(
Array
)
?
lazy_vals
.
map
{
|
val
|
sync
(
val
)
}
:
sync
(
lazy_vals
)
end
def
graphql_query_for
(
name
,
a
ttributes
=
{},
fields
=
nil
)
def
graphql_query_for
(
name
,
a
rgs
=
{},
selection
=
nil
)
type
=
GitlabSchema
.
types
[
'Query'
].
fields
[
GraphqlHelpers
.
fieldnamerize
(
name
)]
&
.
type
wrap_query
(
query_graphql_field
(
name
,
a
ttributes
,
fields
,
type
))
wrap_query
(
query_graphql_field
(
name
,
a
rgs
,
selection
,
type
))
end
def
wrap_query
(
query
)
...
...
@@ -171,25 +266,6 @@ module GraphqlHelpers
::
Gitlab
::
Utils
::
MergeHash
.
merge
(
Array
.
wrap
(
variables
).
map
(
&
:to_h
)).
to_json
end
def
resolve_field
(
name
,
object
,
args
=
{},
current_user:
nil
)
q
=
GraphQL
::
Query
.
new
(
GitlabSchema
)
context
=
GraphQL
::
Query
::
Context
.
new
(
query:
q
,
object:
object
,
values:
{
current_user:
current_user
})
allow
(
context
).
to
receive
(
:parent
).
and_return
(
nil
)
field
=
described_class
.
fields
.
fetch
(
GraphqlHelpers
.
fieldnamerize
(
name
))
instance
=
described_class
.
authorized_new
(
object
,
context
)
raise
UnauthorizedObject
unless
instance
field
.
resolve_field
(
instance
,
args
,
context
)
end
def
simple_resolver
(
resolved_value
=
'Resolved value'
)
Class
.
new
(
Resolvers
::
BaseResolver
)
do
define_method
:resolve
do
|**
_args
|
resolved_value
end
end
end
# Recursively convert a Hash with Ruby-style keys to GraphQL fieldname-style keys
#
# prepare_input_for_mutation({ 'my_key' => 1 })
...
...
@@ -558,24 +634,26 @@ module GraphqlHelpers
end
end
def
execute_query
(
query_type
)
schema
=
Class
.
new
(
GraphQL
::
Schema
)
do
use
GraphQL
::
Pagination
::
Connections
use
Gitlab
::
Graphql
::
Authorize
use
Gitlab
::
Graphql
::
Pagination
::
Connections
lazy_resolve
::
Gitlab
::
Graphql
::
Lazy
,
:force
query
(
query_type
)
end
# assumes query_string to be let-bound in the current context
def
execute_query
(
query_type
,
schema:
empty_schema
,
graphql:
query_string
)
schema
.
query
(
query_type
)
schema
.
execute
(
query_string
,
graphql
,
context:
{
current_user:
user
},
variables:
{}
)
end
def
empty_schema
Class
.
new
(
GraphQL
::
Schema
)
do
use
GraphQL
::
Pagination
::
Connections
use
Gitlab
::
Graphql
::
Pagination
::
Connections
lazy_resolve
::
Gitlab
::
Graphql
::
Lazy
,
:force
end
end
# A lookahead that selects everything
def
positive_lookahead
double
(
selects?:
true
).
tap
do
|
selection
|
...
...
@@ -589,6 +667,23 @@ module GraphqlHelpers
allow
(
selection
).
to
receive
(
:selection
).
and_return
(
selection
)
end
end
private
def
to_base_field
(
name_or_field
,
object_type
)
case
name_or_field
when
::
Types
::
BaseField
name_or_field
else
field_by_name
(
name_or_field
,
object_type
)
end
end
def
field_by_name
(
name
,
object_type
)
name
=
::
GraphqlHelpers
.
fieldnamerize
(
name
)
object_type
.
fields
[
name
]
||
(
raise
ArgumentError
,
"Unknown field
#{
name
}
for
#{
described_class
.
graphql_name
}
"
)
end
end
# This warms our schema, doing this as part of loading the helpers to avoid
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment