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
9b71d27b
Commit
9b71d27b
authored
Mar 23, 2020
by
Jan Provaznik
Committed by
Grzegorz Bizon
Mar 23, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Allow updating of requirements
Requirement's title and state attributes can be updated through GraphQL API.
parent
99b76451
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
529 additions
and
2 deletions
+529
-2
doc/api/graphql/reference/gitlab_schema.graphql
doc/api/graphql/reference/gitlab_schema.graphql
+51
-0
doc/api/graphql/reference/gitlab_schema.json
doc/api/graphql/reference/gitlab_schema.json
+163
-0
doc/api/graphql/reference/index.md
doc/api/graphql/reference/index.md
+10
-0
ee/app/graphql/ee/types/mutation_type.rb
ee/app/graphql/ee/types/mutation_type.rb
+1
-0
ee/app/graphql/mutations/requirements/create.rb
ee/app/graphql/mutations/requirements/create.rb
+1
-2
ee/app/graphql/mutations/requirements/update.rb
ee/app/graphql/mutations/requirements/update.rb
+70
-0
ee/app/services/requirements/update_service.rb
ee/app/services/requirements/update_service.rb
+20
-0
ee/spec/graphql/mutations/requirements/update_spec.rb
ee/spec/graphql/mutations/requirements/update_spec.rb
+66
-0
ee/spec/requests/api/graphql/mutations/requirements/update_spec.rb
...equests/api/graphql/mutations/requirements/update_spec.rb
+98
-0
ee/spec/services/requirements/update_service_spec.rb
ee/spec/services/requirements/update_service_spec.rb
+49
-0
No files found.
doc/api/graphql/reference/gitlab_schema.graphql
View file @
9b71d27b
...
@@ -5046,6 +5046,7 @@ type Mutation {
...
@@ -5046,6 +5046,7 @@ type Mutation {
will
be
destroyed
during
the
update
,
and
no
Note
will
be
returned
will
be
destroyed
during
the
update
,
and
no
Note
will
be
returned
"""
"""
updateNote
(
input
:
UpdateNoteInput
!):
UpdateNotePayload
updateNote
(
input
:
UpdateNoteInput
!):
UpdateNotePayload
updateRequirement
(
input
:
UpdateRequirementInput
!):
UpdateRequirementPayload
updateSnippet
(
input
:
UpdateSnippetInput
!):
UpdateSnippetPayload
updateSnippet
(
input
:
UpdateSnippetInput
!):
UpdateSnippetPayload
}
}
...
@@ -8498,6 +8499,56 @@ type UpdateNotePayload {
...
@@ -8498,6 +8499,56 @@ type UpdateNotePayload {
note
:
Note
note
:
Note
}
}
"""
Autogenerated input type of UpdateRequirement
"""
input
UpdateRequirementInput
{
"""
A
unique
identifier
for
the
client
performing
the
mutation
.
"""
clientMutationId
:
String
"""
The
iid
of
the
requirement
to
update
"""
iid
:
String
!
"""
The
project
full
path
the
requirement
is
associated
with
"""
projectPath
:
ID
!
"""
State
of
the
requirement
"""
state
:
RequirementState
"""
Title
of
the
requirement
"""
title
:
String
}
"""
Autogenerated return type of UpdateRequirement
"""
type
UpdateRequirementPayload
{
"""
A
unique
identifier
for
the
client
performing
the
mutation
.
"""
clientMutationId
:
String
"""
Reasons
why
the
mutation
failed
.
"""
errors
:
[
String
!]!
"""
The
requirement
after
mutation
"""
requirement
:
Requirement
}
"""
"""
Autogenerated input type of UpdateSnippet
Autogenerated input type of UpdateSnippet
"""
"""
...
...
doc/api/graphql/reference/gitlab_schema.json
View file @
9b71d27b
...
@@ -15331,6 +15331,33 @@
...
@@ -15331,6 +15331,33 @@
"isDeprecated"
:
false
,
"isDeprecated"
:
false
,
"deprecationReason"
:
null
"deprecationReason"
:
null
},
},
{
"name"
:
"updateRequirement"
,
"description"
:
null
,
"args"
:
[
{
"name"
:
"input"
,
"description"
:
null
,
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"INPUT_OBJECT"
,
"name"
:
"UpdateRequirementInput"
,
"ofType"
:
null
}
},
"defaultValue"
:
null
}
],
"type"
:
{
"kind"
:
"OBJECT"
,
"name"
:
"UpdateRequirementPayload"
,
"ofType"
:
null
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
{
"name"
:
"updateSnippet"
,
"name"
:
"updateSnippet"
,
"description"
:
null
,
"description"
:
null
,
...
@@ -25662,6 +25689,142 @@
...
@@ -25662,6 +25689,142 @@
"enumValues"
:
null
,
"enumValues"
:
null
,
"possibleTypes"
:
null
"possibleTypes"
:
null
},
},
{
"kind"
:
"INPUT_OBJECT"
,
"name"
:
"UpdateRequirementInput"
,
"description"
:
"Autogenerated input type of UpdateRequirement"
,
"fields"
:
null
,
"inputFields"
:
[
{
"name"
:
"title"
,
"description"
:
"Title of the requirement"
,
"type"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
},
"defaultValue"
:
null
},
{
"name"
:
"state"
,
"description"
:
"State of the requirement"
,
"type"
:
{
"kind"
:
"ENUM"
,
"name"
:
"RequirementState"
,
"ofType"
:
null
},
"defaultValue"
:
null
},
{
"name"
:
"iid"
,
"description"
:
"The iid of the requirement to update"
,
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
}
},
"defaultValue"
:
null
},
{
"name"
:
"projectPath"
,
"description"
:
"The project full path the requirement is associated with"
,
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"ID"
,
"ofType"
:
null
}
},
"defaultValue"
:
null
},
{
"name"
:
"clientMutationId"
,
"description"
:
"A unique identifier for the client performing the mutation."
,
"type"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
},
"defaultValue"
:
null
}
],
"interfaces"
:
null
,
"enumValues"
:
null
,
"possibleTypes"
:
null
},
{
"kind"
:
"OBJECT"
,
"name"
:
"UpdateRequirementPayload"
,
"description"
:
"Autogenerated return type of UpdateRequirement"
,
"fields"
:
[
{
"name"
:
"clientMutationId"
,
"description"
:
"A unique identifier for the client performing the mutation."
,
"args"
:
[
],
"type"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"errors"
,
"description"
:
"Reasons why the mutation failed."
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"LIST"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
}
}
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"requirement"
,
"description"
:
"The requirement after mutation"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"OBJECT"
,
"name"
:
"Requirement"
,
"ofType"
:
null
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
}
],
"inputFields"
:
null
,
"interfaces"
:
[
],
"enumValues"
:
null
,
"possibleTypes"
:
null
},
{
{
"kind"
:
"INPUT_OBJECT"
,
"kind"
:
"INPUT_OBJECT"
,
"name"
:
"UpdateSnippetInput"
,
"name"
:
"UpdateSnippetInput"
,
...
...
doc/api/graphql/reference/index.md
View file @
9b71d27b
...
@@ -1372,6 +1372,16 @@ Autogenerated return type of UpdateNote
...
@@ -1372,6 +1372,16 @@ Autogenerated return type of UpdateNote
|
`errors`
| String! => Array | Reasons why the mutation failed. |
|
`errors`
| String! => Array | Reasons why the mutation failed. |
|
`note`
| Note | The note after mutation |
|
`note`
| Note | The note after mutation |
## UpdateRequirementPayload
Autogenerated return type of UpdateRequirement
| Name | Type | Description |
| --- | ---- | ---------- |
|
`clientMutationId`
| String | A unique identifier for the client performing the mutation. |
|
`errors`
| String! => Array | Reasons why the mutation failed. |
|
`requirement`
| Requirement | The requirement after mutation |
## UpdateSnippetPayload
## UpdateSnippetPayload
Autogenerated return type of UpdateSnippet
Autogenerated return type of UpdateSnippet
...
...
ee/app/graphql/ee/types/mutation_type.rb
View file @
9b71d27b
...
@@ -15,6 +15,7 @@ module EE
...
@@ -15,6 +15,7 @@ module EE
mount_mutation
::
Mutations
::
Epics
::
SetSubscription
mount_mutation
::
Mutations
::
Epics
::
SetSubscription
mount_mutation
::
Mutations
::
Epics
::
AddIssue
mount_mutation
::
Mutations
::
Epics
::
AddIssue
mount_mutation
::
Mutations
::
Requirements
::
Create
mount_mutation
::
Mutations
::
Requirements
::
Create
mount_mutation
::
Mutations
::
Requirements
::
Update
end
end
end
end
end
end
...
...
ee/app/graphql/mutations/requirements/create.rb
View file @
9b71d27b
...
@@ -9,8 +9,7 @@ module Mutations
...
@@ -9,8 +9,7 @@ module Mutations
authorize
:create_requirement
authorize
:create_requirement
field
:requirement
,
field
:requirement
,
Types
::
RequirementType
,
Types
::
RequirementType
,
null:
true
,
null:
true
,
description:
'The requirement after mutation'
description:
'The requirement after mutation'
...
...
ee/app/graphql/mutations/requirements/update.rb
0 → 100644
View file @
9b71d27b
# frozen_string_literal: true
module
Mutations
module
Requirements
class
Update
<
BaseMutation
include
Mutations
::
ResolvesProject
graphql_name
'UpdateRequirement'
authorize
:update_requirement
field
:requirement
,
Types
::
RequirementType
,
null:
true
,
description:
'The requirement after mutation'
argument
:title
,
GraphQL
::
STRING_TYPE
,
required:
false
,
description:
'Title of the requirement'
argument
:state
,
Types
::
RequirementStateEnum
,
required:
false
,
description:
'State of the requirement'
argument
:iid
,
GraphQL
::
STRING_TYPE
,
required:
true
,
description:
'The iid of the requirement to update'
argument
:project_path
,
GraphQL
::
ID_TYPE
,
required:
true
,
description:
'The project full path the requirement is associated with'
def
ready?
(
**
args
)
if
args
.
values_at
(
:title
,
:state
).
compact
.
blank?
raise
Gitlab
::
Graphql
::
Errors
::
ArgumentError
,
'title or state argument is required'
end
super
end
def
resolve
(
args
)
project_path
=
args
.
delete
(
:project_path
)
requirement_iid
=
args
.
delete
(
:iid
)
requirement
=
authorized_find!
(
project_path:
project_path
,
iid:
requirement_iid
)
requirement
=
::
Requirements
::
UpdateService
.
new
(
requirement
.
project
,
context
[
:current_user
],
args
).
execute
(
requirement
)
{
requirement:
requirement
.
reset
,
errors:
errors_on_object
(
requirement
)
}
end
private
def
find_object
(
project_path
:,
iid
:)
project
=
resolve_project
(
full_path:
project_path
)
resolver
=
Resolvers
::
RequirementsResolver
.
single
.
new
(
object:
project
,
context:
context
,
field:
nil
)
resolver
.
resolve
(
iid:
iid
)
end
end
end
end
ee/app/services/requirements/update_service.rb
0 → 100644
View file @
9b71d27b
# frozen_string_literal: true
module
Requirements
class
UpdateService
<
BaseService
def
execute
(
requirement
)
raise
Gitlab
::
Access
::
AccessDeniedError
unless
can?
(
current_user
,
:update_requirement
,
project
)
attrs
=
whitelisted_requirement_params
requirement
.
update
(
attrs
)
requirement
end
private
def
whitelisted_requirement_params
params
.
slice
(
:title
,
:state
)
end
end
end
ee/spec/graphql/mutations/requirements/update_spec.rb
0 → 100644
View file @
9b71d27b
# frozen_string_literal: true
require
'spec_helper'
describe
Mutations
::
Requirements
::
Update
do
let_it_be
(
:project
)
{
create
(
:project
)
}
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:requirement
)
{
create
(
:requirement
,
project:
project
)
}
subject
(
:mutation
)
{
described_class
.
new
(
object:
nil
,
context:
{
current_user:
user
},
field:
nil
)
}
describe
'#resolve'
do
shared_examples
'requirements not available'
do
it
'raises a not accessible error'
do
expect
{
subject
}.
to
raise_error
(
Gitlab
::
Graphql
::
Errors
::
ResourceNotAvailable
)
end
end
subject
do
mutation
.
resolve
(
project_path:
project
.
full_path
,
iid:
requirement
.
iid
.
to_s
,
title:
'foo'
,
state:
'archived'
)
end
it_behaves_like
'requirements not available'
context
'when the user can update the epic'
do
before
do
project
.
add_developer
(
user
)
end
context
'when requirements feature is available'
do
before
do
stub_licensed_features
(
requirements:
true
)
end
it
'updates new requirement'
,
:aggregate_failures
do
expect
(
subject
[
:requirement
]).
to
have_attributes
(
title:
'foo'
,
state:
'archived'
)
expect
(
subject
[
:errors
]).
to
be_empty
end
context
'when requirements_management flag is disabled'
do
before
do
stub_feature_flags
(
requirements_management:
false
)
end
it_behaves_like
'requirements not available'
end
end
context
'when requirements feature is disabled'
do
before
do
stub_licensed_features
(
requirements:
false
)
end
it_behaves_like
'requirements not available'
end
end
end
end
ee/spec/requests/api/graphql/mutations/requirements/update_spec.rb
0 → 100644
View file @
9b71d27b
# frozen_string_literal: true
require
'spec_helper'
describe
'Updating a Requirement'
do
include
GraphqlHelpers
let_it_be
(
:current_user
)
{
create
(
:user
)
}
let_it_be
(
:project
)
{
create
(
:project
)
}
let_it_be
(
:requirement
)
{
create
(
:requirement
,
project:
project
)
}
let
(
:attributes
)
{
{
title:
'title'
,
state:
'ARCHIVED'
}
}
let
(
:mutation
)
do
params
=
{
project_path:
project
.
full_path
,
iid:
requirement
.
iid
.
to_s
}.
merge
(
attributes
)
graphql_mutation
(
:update_requirement
,
params
)
end
shared_examples
'requirement update fails'
do
it_behaves_like
'a mutation that returns top-level errors'
,
errors:
[
'The resource that you are attempting to access does not exist '
\
'or you don\'t have permission to perform this action'
]
it
'does not update requirement'
do
expect
do
post_graphql_mutation
(
mutation
,
current_user:
current_user
)
end
.
not_to
change
{
requirement
.
reload
}
end
end
def
mutation_response
graphql_mutation_response
(
:update_requirement
)
end
context
'when the user does not have permission'
do
before
do
stub_licensed_features
(
requirements:
true
)
end
it_behaves_like
'requirement update fails'
end
context
'when the user has permission'
do
before
do
project
.
add_reporter
(
current_user
)
end
context
'when requirements are disabled'
do
before
do
stub_licensed_features
(
requirements:
false
)
end
it_behaves_like
'requirement update fails'
end
context
'when requirements are enabled'
do
before
do
stub_licensed_features
(
requirements:
true
)
end
it
'updates the requirement'
,
:aggregate_failures
do
post_graphql_mutation
(
mutation
,
current_user:
current_user
)
requirement_hash
=
mutation_response
[
'requirement'
]
expect
(
requirement_hash
[
'title'
]).
to
eq
(
'title'
)
expect
(
requirement_hash
[
'state'
]).
to
eq
(
'ARCHIVED'
)
end
context
'when there are ActiveRecord validation errors'
do
let
(
:attributes
)
{
{
title:
''
}
}
it_behaves_like
'a mutation that returns errors in the response'
,
errors:
[
'Title can\'t be blank'
]
it
'does not update the requirement'
do
expect
do
post_graphql_mutation
(
mutation
,
current_user:
current_user
)
end
.
not_to
change
{
requirement
.
reload
}
end
end
context
'when there are no update params'
do
let
(
:attributes
)
{
{}
}
it_behaves_like
'a mutation that returns top-level errors'
,
errors:
[
'title or state argument is required'
]
end
context
'when requirements_management flag is disabled'
do
before
do
stub_feature_flags
(
requirements_management:
false
)
end
it_behaves_like
'requirement update fails'
end
end
end
end
ee/spec/services/requirements/update_service_spec.rb
0 → 100644
View file @
9b71d27b
# frozen_string_literal: true
require
'spec_helper'
describe
Requirements
::
UpdateService
do
let_it_be
(
:project
)
{
create
(
:project
)}
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:requirement
)
{
create
(
:requirement
,
project:
project
)
}
let
(
:params
)
do
{
title:
'foo'
,
state:
'archived'
,
created_at:
2
.
days
.
ago
,
author_id:
create
(
:user
).
id
}
end
subject
{
described_class
.
new
(
project
,
user
,
params
).
execute
(
requirement
)
}
describe
'#execute'
do
before
do
stub_licensed_features
(
requirements:
true
)
end
context
'when user can update requirements'
do
before
do
project
.
add_reporter
(
user
)
end
it
'updates the requirement with only permitted params'
,
:aggregate_failures
do
is_expected
.
to
have_attributes
(
errors:
be_empty
,
title:
params
[
:title
],
state:
params
[
:state
]
)
is_expected
.
not_to
have_attributes
(
created_at:
params
[
:created_at
],
author_id:
params
[
:author_id
]
)
end
end
context
'when user is not allowed to update requirements'
do
it
'raises an exception'
do
expect
{
subject
}.
to
raise_exception
(
Gitlab
::
Access
::
AccessDeniedError
)
end
end
end
end
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