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
54451c02
Commit
54451c02
authored
May 28, 2019
by
GitLab Bot
Browse files
Options
Browse Files
Download
Plain Diff
Automatic merge of gitlab-org/gitlab-ce master
parents
9b10b9f2
4fe81054
Changes
17
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
282 additions
and
47 deletions
+282
-47
app/controllers/graphql_controller.rb
app/controllers/graphql_controller.rb
+39
-7
app/graphql/gitlab_schema.rb
app/graphql/gitlab_schema.rb
+13
-2
app/helpers/labels_helper.rb
app/helpers/labels_helper.rb
+2
-9
app/presenters/label_presenter.rb
app/presenters/label_presenter.rb
+8
-0
app/views/shared/_delete_label_modal.html.haml
app/views/shared/_delete_label_modal.html.haml
+1
-1
app/views/shared/_label.html.haml
app/views/shared/_label.html.haml
+1
-1
changelogs/unreleased/bvl-graphql-multiplex.yml
changelogs/unreleased/bvl-graphql-multiplex.yml
+5
-0
changelogs/unreleased/jp-label-fix.yml
changelogs/unreleased/jp-label-fix.yml
+5
-0
doc/api/graphql/index.md
doc/api/graphql/index.md
+8
-0
spec/features/projects/labels/user_promotes_label_spec.rb
spec/features/projects/labels/user_promotes_label_spec.rb
+34
-0
spec/features/projects/labels/user_removes_labels_spec.rb
spec/features/projects/labels/user_removes_labels_spec.rb
+4
-1
spec/graphql/gitlab_schema_spec.rb
spec/graphql/gitlab_schema_spec.rb
+2
-2
spec/helpers/labels_helper_spec.rb
spec/helpers/labels_helper_spec.rb
+18
-1
spec/presenters/label_presenter_spec.rb
spec/presenters/label_presenter_spec.rb
+28
-0
spec/requests/api/graphql/gitlab_schema_spec.rb
spec/requests/api/graphql/gitlab_schema_spec.rb
+63
-22
spec/requests/api/graphql/multiplexed_queries_spec.rb
spec/requests/api/graphql/multiplexed_queries_spec.rb
+39
-0
spec/support/helpers/graphql_helpers.rb
spec/support/helpers/graphql_helpers.rb
+12
-1
No files found.
app/controllers/graphql_controller.rb
View file @
54451c02
...
...
@@ -16,13 +16,8 @@ class GraphqlController < ApplicationController
before_action
(
only:
[
:execute
])
{
authenticate_sessionless_user!
(
:api
)
}
def
execute
variables
=
Gitlab
::
Graphql
::
Variables
.
new
(
params
[
:variables
]).
to_h
query
=
params
[
:query
]
operation_name
=
params
[
:operationName
]
context
=
{
current_user:
current_user
}
result
=
GitlabSchema
.
execute
(
query
,
variables:
variables
,
context:
context
,
operation_name:
operation_name
)
result
=
multiplex?
?
execute_multiplex
:
execute_query
render
json:
result
end
...
...
@@ -38,6 +33,43 @@ class GraphqlController < ApplicationController
private
def
execute_multiplex
GitlabSchema
.
multiplex
(
multiplex_queries
,
context:
context
)
end
def
execute_query
variables
=
build_variables
(
params
[
:variables
])
operation_name
=
params
[
:operationName
]
GitlabSchema
.
execute
(
query
,
variables:
variables
,
context:
context
,
operation_name:
operation_name
)
end
def
query
params
[
:query
]
end
def
multiplex_queries
params
[
:_json
].
map
do
|
single_query_info
|
{
query:
single_query_info
[
:query
],
variables:
build_variables
(
single_query_info
[
:variables
]),
operation_name:
single_query_info
[
:operationName
]
}
end
end
def
context
@context
||=
{
current_user:
current_user
}
end
def
build_variables
(
variable_info
)
Gitlab
::
Graphql
::
Variables
.
new
(
variable_info
).
to_h
end
def
multiplex?
params
[
:_json
].
present?
end
def
authorize_access_api!
access_denied!
(
"API not accessible for user."
)
unless
can?
(
current_user
,
:access_api
)
end
...
...
app/graphql/gitlab_schema.rb
View file @
54451c02
...
...
@@ -7,7 +7,7 @@ class GitlabSchema < GraphQL::Schema
AUTHENTICATED_COMPLEXITY
=
250
ADMIN_COMPLEXITY
=
300
ANONYMOUS
_MAX_DEPTH
=
10
DEFAULT
_MAX_DEPTH
=
10
AUTHENTICATED_MAX_DEPTH
=
15
use
BatchLoader
::
GraphQL
...
...
@@ -23,10 +23,21 @@ class GitlabSchema < GraphQL::Schema
default_max_page_size
100
max_complexity
DEFAULT_MAX_COMPLEXITY
max_depth
DEFAULT_MAX_DEPTH
mutation
(
Types
::
MutationType
)
class
<<
self
def
multiplex
(
queries
,
**
kwargs
)
kwargs
[
:max_complexity
]
||=
max_query_complexity
(
kwargs
[
:context
])
queries
.
each
do
|
query
|
query
[
:max_depth
]
=
max_query_depth
(
kwargs
[
:context
])
end
super
(
queries
,
**
kwargs
)
end
def
execute
(
query_str
=
nil
,
**
kwargs
)
kwargs
[
:max_complexity
]
||=
max_query_complexity
(
kwargs
[
:context
])
kwargs
[
:max_depth
]
||=
max_query_depth
(
kwargs
[
:context
])
...
...
@@ -54,7 +65,7 @@ class GitlabSchema < GraphQL::Schema
if
current_user
AUTHENTICATED_MAX_DEPTH
else
ANONYMOUS
_MAX_DEPTH
DEFAULT
_MAX_DEPTH
end
end
end
...
...
app/helpers/labels_helper.rb
View file @
54451c02
...
...
@@ -5,7 +5,7 @@ module LabelsHelper
include
ActionView
::
Helpers
::
TagHelper
def
show_label_issuables_link?
(
label
,
issuables_type
,
current_user:
nil
,
project:
nil
)
return
true
if
label
.
is_a?
(
GroupLabel
)
return
true
unless
label
.
project_label?
return
true
unless
project
project
.
feature_available?
(
issuables_type
,
current_user
)
...
...
@@ -159,13 +159,6 @@ module LabelsHelper
label
.
subscribed?
(
current_user
,
project
)
?
'Unsubscribe'
:
'Subscribe'
end
def
label_deletion_confirm_text
(
label
)
case
label
when
GroupLabel
then
_
(
'Remove this label? This will affect all projects within the group. Are you sure?'
)
when
ProjectLabel
then
_
(
'Remove this label? Are you sure?'
)
end
end
def
create_label_title
(
subject
)
case
subject
when
Group
...
...
@@ -200,7 +193,7 @@ module LabelsHelper
end
def
label_status_tooltip
(
label
,
status
)
type
=
label
.
is_a?
(
ProjectLabel
)
?
'project'
:
'group'
type
=
label
.
project_label?
?
'project'
:
'group'
level
=
status
.
unsubscribed?
?
type
:
status
.
sub
(
'-level'
,
''
)
action
=
status
.
unsubscribed?
?
'Subscribe'
:
'Unsubscribe'
...
...
app/presenters/label_presenter.rb
View file @
54451c02
...
...
@@ -35,6 +35,14 @@ class LabelPresenter < Gitlab::View::Presenter::Delegated
issuable_subject
.
is_a?
(
Project
)
&&
label
.
is_a?
(
GroupLabel
)
end
def
project_label?
label
.
is_a?
(
ProjectLabel
)
end
def
subject_name
label
.
subject
.
name
end
private
def
context_subject
...
...
app/views/shared/_delete_label_modal.html.haml
View file @
54451c02
...
...
@@ -9,7 +9,7 @@
.modal-body
%p
%strong
=
label
.
name
%span
will be permanently deleted from
#{
label
.
subject
.
name
}
. This cannot be undone.
%span
will be permanently deleted from
#{
label
.
subject
_
name
}
. This cannot be undone.
.modal-footer
%a
{
href:
'#'
,
data:
{
dismiss:
'modal'
},
class:
'btn btn-default'
}
Cancel
...
...
app/views/shared/_label.html.haml
View file @
54451c02
...
...
@@ -30,7 +30,7 @@
=
sprite_icon
(
'ellipsis_v'
)
.dropdown-menu.dropdown-open-left
%ul
-
if
label
.
is_a?
(
ProjectLabel
)
&&
label
.
project
.
group
&&
can?
(
current_user
,
:admin_label
,
label
.
project
.
group
)
-
if
label
.
project_label?
&&
label
.
project
.
group
&&
can?
(
current_user
,
:admin_label
,
label
.
project
.
group
)
%li
%button
.js-promote-project-label-button.btn.btn-transparent.btn-action
{
disabled:
true
,
type:
'button'
,
data:
{
url:
promote_project_label_path
(
label
.
project
,
label
),
...
...
changelogs/unreleased/bvl-graphql-multiplex.yml
0 → 100644
View file @
54451c02
---
title
:
Support multiplex GraphQL queries
merge_request
:
28273
author
:
type
:
added
changelogs/unreleased/jp-label-fix.yml
0 → 100644
View file @
54451c02
---
title
:
Fix display of 'Promote to group label' button.
merge_request
:
author
:
type
:
fixed
doc/api/graphql/index.md
View file @
54451c02
...
...
@@ -48,6 +48,14 @@ A first iteration of a GraphQL API includes the following queries
1.
`project`
: Within a project it is also possible to fetch a
`mergeRequest`
by IID.
1.
`group`
: Only basic group information is currently supported.
### Multiplex queries
GitLab supports batching queries into a single request using
[
apollo-link-batch-http
](
https://www.apollographql.com/docs/link/links/batch-http
)
. More
info about multiplexed queries is also available for
[
graphql-ruby
](
https://graphql-ruby.org/queries/multiplex.html
)
the
library GitLab uses on the backend.
## GraphiQL
The API can be explored by using the GraphiQL IDE, it is available on your
...
...
spec/features/projects/labels/user_promotes_label_spec.rb
0 → 100644
View file @
54451c02
# frozen_string_literal: true
require
'spec_helper'
describe
'User promotes label'
do
set
(
:group
)
{
create
(
:group
)
}
set
(
:user
)
{
create
(
:user
)
}
set
(
:project
)
{
create
(
:project
,
namespace:
group
)
}
set
(
:label
)
{
create
(
:label
,
project:
project
)
}
context
'when user can admin group labels'
do
before
do
group
.
add_developer
(
user
)
sign_in
(
user
)
visit
(
project_labels_path
(
project
))
end
it
"shows label promote button"
do
expect
(
page
).
to
have_selector
(
'.js-promote-project-label-button'
)
end
end
context
'when user cannot admin group labels'
do
before
do
project
.
add_developer
(
user
)
sign_in
(
user
)
visit
(
project_labels_path
(
project
))
end
it
"does not show label promote button"
do
expect
(
page
).
not_to
have_selector
(
'.js-promote-project-label-button'
)
end
end
end
spec/features/projects/labels/user_removes_labels_spec.rb
View file @
54451c02
...
...
@@ -21,8 +21,11 @@ describe "User removes labels" do
page
.
first
(
".label-list-item"
)
do
first
(
'.js-label-options-dropdown'
).
click
first
(
".remove-row"
).
click
first
(
:link
,
"Delete label"
).
click
end
expect
(
page
).
to
have_content
(
"
#{
label
.
title
}
will be permanently deleted from
#{
project
.
name
}
. This cannot be undone."
)
first
(
:link
,
"Delete label"
).
click
end
expect
(
page
).
to
have_content
(
"Label was removed"
).
and
have_no_content
(
label
.
title
)
...
...
spec/graphql/gitlab_schema_spec.rb
View file @
54451c02
...
...
@@ -56,10 +56,10 @@ describe GitlabSchema do
described_class
.
execute
(
'query'
,
context:
{})
end
it
'returns
ANONYMOUS
_MAX_DEPTH'
do
it
'returns
DEFAULT
_MAX_DEPTH'
do
expect
(
GraphQL
::
Schema
)
.
to
receive
(
:execute
)
.
with
(
'query'
,
hash_including
(
max_depth:
GitlabSchema
::
ANONYMOUS
_MAX_DEPTH
))
.
with
(
'query'
,
hash_including
(
max_depth:
GitlabSchema
::
DEFAULT
_MAX_DEPTH
))
described_class
.
execute
(
'query'
,
context:
{})
end
...
...
spec/helpers/labels_helper_spec.rb
View file @
54451c02
...
...
@@ -6,7 +6,7 @@ describe LabelsHelper do
let
(
:context_project
)
{
project
}
context
"when asking for a
#{
issuables_type
}
link"
do
subject
{
show_label_issuables_link?
(
label
,
issuables_type
,
project:
context_project
)
}
subject
{
show_label_issuables_link?
(
label
.
present
(
issuable_subject:
nil
)
,
issuables_type
,
project:
context_project
)
}
context
"when
#{
issuables_type
}
are enabled for the project"
do
let
(
:project
)
{
create
(
:project
,
"
#{
issuables_type
}
_access_level"
:
ProjectFeature
::
ENABLED
)
}
...
...
@@ -279,4 +279,21 @@ describe LabelsHelper do
expect
(
label
.
color
).
to
eq
(
'bar'
)
end
end
describe
'#label_status_tooltip'
do
let
(
:status
)
{
'unsubscribed'
.
inquiry
}
subject
{
label_status_tooltip
(
label
.
present
(
issuable_subject:
nil
),
status
)
}
context
'with a project label'
do
let
(
:label
)
{
create
(
:label
,
title:
'bug'
)
}
it
{
is_expected
.
to
eq
(
'Subscribe at project level'
)
}
end
context
'with a group label'
do
let
(
:label
)
{
create
(
:group_label
,
title:
'bug'
)
}
it
{
is_expected
.
to
eq
(
'Subscribe at group level'
)
}
end
end
end
spec/presenters/label_presenter_spec.rb
View file @
54451c02
...
...
@@ -62,4 +62,32 @@ describe LabelPresenter do
expect
(
label
.
can_subscribe_to_label_in_different_levels?
).
to
be_falsey
end
end
describe
'#project_label?'
do
context
'with group label'
do
subject
{
group_label
.
project_label?
}
it
{
is_expected
.
to
be_falsey
}
end
context
'with project label'
do
subject
{
label
.
project_label?
}
it
{
is_expected
.
to
be_truthy
}
end
end
describe
'#subject_name'
do
context
'with group label'
do
subject
{
group_label
.
subject_name
}
it
{
is_expected
.
to
eq
(
group_label
.
group
.
name
)
}
end
context
'with project label'
do
subject
{
label
.
subject_name
}
it
{
is_expected
.
to
eq
(
label
.
project
.
name
)
}
end
end
end
spec/requests/api/graphql/gitlab_schema_spec.rb
View file @
54451c02
...
...
@@ -3,41 +3,82 @@ require 'spec_helper'
describe
'GitlabSchema configurations'
do
include
GraphqlHelpers
let
(
:project
)
{
create
(
:project
,
:repository
)
}
let
(
:query
)
{
graphql_query_for
(
'project'
,
{
'fullPath'
=>
project
.
full_path
},
%w(id name description)
)
}
let
(
:current_user
)
{
create
(
:user
)
}
let
(
:project
)
{
create
(
:project
)
}
describe
'#max_complexity'
do
context
'when complexity is too high'
do
it
'shows an error'
do
allow
(
GitlabSchema
).
to
receive
(
:max_query_complexity
).
and_return
1
shared_examples
'imposing query limits'
do
describe
'#max_complexity'
do
context
'when complexity is too high'
do
it
'shows an error'
do
allow
(
GitlabSchema
).
to
receive
(
:max_query_complexity
).
and_return
1
post_graphql
(
query
,
current_user:
nil
)
subject
expect
(
graphql_errors
.
first
[
'message'
]).
to
include
(
'which exceeds max complexity of 1'
)
expect
(
graphql_errors
.
flatten
.
first
[
'message'
]).
to
include
(
'which exceeds max complexity of 1'
)
end
end
end
end
describe
'#max_depth'
do
context
'when query depth is too high'
do
it
'shows error'
do
errors
=
[{
"message"
=>
"Query has depth of 2, which exceeds max depth of 1"
}]
allow
(
GitlabSchema
).
to
receive
(
:max_query_depth
).
and_return
1
describe
'#max_depth'
do
context
'when query depth is too high'
do
it
'shows error'
do
errors
=
{
"message"
=>
"Query has depth of 2, which exceeds max depth of 1"
}
allow
(
GitlabSchema
).
to
receive
(
:max_query_depth
).
and_return
1
post_graphql
(
query
)
subject
expect
(
graphql_errors
).
to
eq
(
errors
)
expect
(
graphql_errors
.
flatten
).
to
include
(
errors
)
end
end
context
'when query depth is within range'
do
it
'has no error'
do
allow
(
GitlabSchema
).
to
receive
(
:max_query_depth
).
and_return
5
subject
expect
(
Array
.
wrap
(
graphql_errors
).
compact
).
to
be_empty
end
end
end
end
context
'regular queries'
do
subject
do
query
=
graphql_query_for
(
'project'
,
{
'fullPath'
=>
project
.
full_path
},
%w(id name description)
)
post_graphql
(
query
)
end
context
'when query depth is within range'
do
it
'has no error'
do
allow
(
GitlabSchema
).
to
receive
(
:max_query_depth
).
and_return
5
it_behaves_like
'imposing query limits'
end
context
'multiplexed queries'
do
subject
do
queries
=
[
{
query:
graphql_query_for
(
'project'
,
{
'fullPath'
=>
project
.
full_path
},
%w(id name description)
)
},
{
query:
graphql_query_for
(
'echo'
,
{
'text'
=>
"$test"
},
[]),
variables:
{
"test"
=>
"Hello world"
}
}
]
post_multiplex
(
queries
)
end
it_behaves_like
'imposing query limits'
do
it
"fails all queries when only one of the queries is too complex"
do
# The `project` query above has a complexity of 5
allow
(
GitlabSchema
).
to
receive
(
:max_query_complexity
).
and_return
4
subject
post_graphql
(
query
)
# Expect a response for each query, even though it will be empty
expect
(
json_response
.
size
).
to
eq
(
2
)
json_response
.
each
do
|
single_query_response
|
expect
(
single_query_response
).
not_to
have_key
(
'data'
)
end
expect
(
graphql_errors
).
to
be_nil
# Expect errors for each query
expect
(
graphql_errors
.
size
).
to
eq
(
2
)
graphql_errors
.
each
do
|
single_query_errors
|
expect
(
single_query_errors
.
first
[
'message'
]).
to
include
(
'which exceeds max complexity of 4'
)
end
end
end
end
...
...
spec/requests/api/graphql/multiplexed_queries_spec.rb
0 → 100644
View file @
54451c02
# frozen_string_literal: true
require
'spec_helper'
describe
'Multiplexed queries'
do
include
GraphqlHelpers
it
'returns responses for multiple queries'
do
queries
=
[
{
query:
'query($text: String) { echo(text: $text) }'
,
variables:
{
'text'
=>
'Hello'
}
},
{
query:
'query($text: String) { echo(text: $text) }'
,
variables:
{
'text'
=>
'World'
}
}
]
post_multiplex
(
queries
)
first_response
=
json_response
.
first
[
'data'
][
'echo'
]
second_response
=
json_response
.
last
[
'data'
][
'echo'
]
expect
(
first_response
).
to
eq
(
'nil says: Hello'
)
expect
(
second_response
).
to
eq
(
'nil says: World'
)
end
it
'returns error and data combinations'
do
queries
=
[
{
query:
'query($text: String) { broken query }'
},
{
query:
'query working($text: String) { echo(text: $text) }'
,
variables:
{
'text'
=>
'World'
}
}
]
post_multiplex
(
queries
)
first_response
=
json_response
.
first
[
'errors'
]
second_response
=
json_response
.
last
[
'data'
][
'echo'
]
expect
(
first_response
).
not_to
be_empty
expect
(
second_response
).
to
eq
(
'nil says: World'
)
end
end
spec/support/helpers/graphql_helpers.rb
View file @
54451c02
...
...
@@ -134,6 +134,10 @@ module GraphqlHelpers
end
.
join
(
", "
)
end
def
post_multiplex
(
queries
,
current_user:
nil
,
headers:
{})
post
api
(
'/'
,
current_user
,
version:
'graphql'
),
params:
{
_json:
queries
},
headers:
headers
end
def
post_graphql
(
query
,
current_user:
nil
,
variables:
nil
,
headers:
{})
post
api
(
'/'
,
current_user
,
version:
'graphql'
),
params:
{
query:
query
,
variables:
variables
},
headers:
headers
end
...
...
@@ -147,7 +151,14 @@ module GraphqlHelpers
end
def
graphql_errors
json_response
[
'errors'
]
case
json_response
when
Hash
# regular query
json_response
[
'errors'
]
when
Array
# multiplexed queries
json_response
.
map
{
|
response
|
response
[
'errors'
]
}
else
raise
"Unkown GraphQL response type
#{
json_response
.
class
}
"
end
end
def
graphql_mutation_response
(
mutation_name
)
...
...
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