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
a44e178a
Commit
a44e178a
authored
Oct 23, 2020
by
Felipe Artur
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Allow to create todo on GraphQL
Add CreateTodo mutation
parent
66e18a31
Changes
21
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
412 additions
and
6 deletions
+412
-6
app/graphql/mutations/todos/create.rb
app/graphql/mutations/todos/create.rb
+39
-0
app/graphql/types/mutation_type.rb
app/graphql/types/mutation_type.rb
+1
-0
app/models/concerns/todoable.rb
app/models/concerns/todoable.rb
+10
-0
app/models/design_management/design.rb
app/models/design_management/design.rb
+1
-0
app/models/issue.rb
app/models/issue.rb
+1
-0
app/models/merge_request.rb
app/models/merge_request.rb
+1
-0
app/policies/issue_policy.rb
app/policies/issue_policy.rb
+4
-0
app/policies/merge_request_policy.rb
app/policies/merge_request_policy.rb
+4
-0
changelogs/unreleased/issue_233479-add-graphql-create-todo-mutation.yml
...eleased/issue_233479-add-graphql-create-todo-mutation.yml
+5
-0
doc/api/graphql/reference/gitlab_schema.graphql
doc/api/graphql/reference/gitlab_schema.graphql
+41
-0
doc/api/graphql/reference/gitlab_schema.json
doc/api/graphql/reference/gitlab_schema.json
+139
-0
doc/api/graphql/reference/index.md
doc/api/graphql/reference/index.md
+10
-0
ee/app/models/ee/epic.rb
ee/app/models/ee/epic.rb
+1
-0
ee/app/policies/epic_policy.rb
ee/app/policies/epic_policy.rb
+4
-0
ee/spec/graphql/mutations/todos/create_spec.rb
ee/spec/graphql/mutations/todos/create_spec.rb
+18
-0
ee/spec/policies/epic_policy_spec.rb
ee/spec/policies/epic_policy_spec.rb
+6
-5
spec/graphql/mutations/todos/create_spec.rb
spec/graphql/mutations/todos/create_spec.rb
+44
-0
spec/policies/issue_policy_spec.rb
spec/policies/issue_policy_spec.rb
+6
-1
spec/policies/merge_request_policy_spec.rb
spec/policies/merge_request_policy_spec.rb
+13
-0
spec/requests/api/graphql/mutations/todos/create_spec.rb
spec/requests/api/graphql/mutations/todos/create_spec.rb
+38
-0
spec/support/shared_examples/graphql/mutations/create_todo_shared_examples.rb
...examples/graphql/mutations/create_todo_shared_examples.rb
+26
-0
No files found.
app/graphql/mutations/todos/create.rb
0 → 100644
View file @
a44e178a
# frozen_string_literal: true
module
Mutations
module
Todos
class
Create
<
::
Mutations
::
Todos
::
Base
graphql_name
'TodoCreate'
authorize
:create_todo
argument
:target_id
,
Types
::
GlobalIDType
[
Todoable
],
required:
true
,
description:
"The global ID of the to-do item's parent. Issues, merge requests, designs and epics are supported"
field
:todo
,
Types
::
TodoType
,
null:
true
,
description:
'The to-do created'
def
resolve
(
target_id
:)
id
=
::
Types
::
GlobalIDType
[
Todoable
].
coerce_isolated_input
(
target_id
)
target
=
authorized_find!
(
id
)
todo
=
TodoService
.
new
.
mark_todo
(
target
,
current_user
)
&
.
first
errors
=
errors_on_object
(
todo
)
if
todo
{
todo:
todo
,
errors:
errors
}
end
private
def
find_object
(
id
)
GitlabSchema
.
find_by_gid
(
id
)
end
end
end
end
app/graphql/types/mutation_type.rb
View file @
a44e178a
...
...
@@ -57,6 +57,7 @@ module Types
mount_mutation
Mutations
::
Terraform
::
State
::
Delete
mount_mutation
Mutations
::
Terraform
::
State
::
Lock
mount_mutation
Mutations
::
Terraform
::
State
::
Unlock
mount_mutation
Mutations
::
Todos
::
Create
mount_mutation
Mutations
::
Todos
::
MarkDone
mount_mutation
Mutations
::
Todos
::
Restore
mount_mutation
Mutations
::
Todos
::
MarkAllDone
...
...
app/models/concerns/todoable.rb
0 → 100644
View file @
a44e178a
# frozen_string_literal: true
# == Todoable concern
#
# Specify object types that supports todos.
#
# Used by Issue, MergeRequest, Design and Epic.
#
module
Todoable
end
app/models/design_management/design.rb
View file @
a44e178a
...
...
@@ -10,6 +10,7 @@ module DesignManagement
include
Mentionable
include
WhereComposite
include
RelativePositioning
include
Todoable
belongs_to
:project
,
inverse_of: :designs
belongs_to
:issue
...
...
app/models/issue.rb
View file @
a44e178a
...
...
@@ -21,6 +21,7 @@ class Issue < ApplicationRecord
include
IdInOrdered
include
Presentable
include
IssueAvailableFeatures
include
Todoable
DueDateStruct
=
Struct
.
new
(
:title
,
:name
).
freeze
NoDueDate
=
DueDateStruct
.
new
(
'No Due Date'
,
'0'
).
freeze
...
...
app/models/merge_request.rb
View file @
a44e178a
...
...
@@ -22,6 +22,7 @@ class MergeRequest < ApplicationRecord
include
StateEventable
include
ApprovableBase
include
IdInOrdered
include
Todoable
extend
::
Gitlab
::
Utils
::
Override
...
...
app/policies/issue_policy.rb
View file @
a44e178a
...
...
@@ -35,6 +35,10 @@ class IssuePolicy < IssuablePolicy
rule
{
~
can?
(
:read_design
)
}.
policy
do
prevent
:move_design
end
rule
{
~
anonymous
&
can?
(
:read_issue
)
}.
policy
do
enable
:create_todo
end
end
IssuePolicy
.
prepend_if_ee
(
'EE::IssuePolicy'
)
app/policies/merge_request_policy.rb
View file @
a44e178a
...
...
@@ -14,6 +14,10 @@ class MergeRequestPolicy < IssuablePolicy
rule
{
can?
(
:update_merge_request
)
}.
policy
do
enable
:approve_merge_request
end
rule
{
~
anonymous
&
can?
(
:read_merge_request
)
}.
policy
do
enable
:create_todo
end
end
MergeRequestPolicy
.
prepend_if_ee
(
'EE::MergeRequestPolicy'
)
changelogs/unreleased/issue_233479-add-graphql-create-todo-mutation.yml
0 → 100644
View file @
a44e178a
---
title
:
Allow to create todo on GraphQL
merge_request
:
46029
author
:
type
:
added
doc/api/graphql/reference/gitlab_schema.graphql
View file @
a44e178a
...
...
@@ -12856,6 +12856,7 @@ type Mutation {
terraformStateDelete
(
input
:
TerraformStateDeleteInput
!):
TerraformStateDeletePayload
terraformStateLock
(
input
:
TerraformStateLockInput
!):
TerraformStateLockPayload
terraformStateUnlock
(
input
:
TerraformStateUnlockInput
!):
TerraformStateUnlockPayload
todoCreate
(
input
:
TodoCreateInput
!):
TodoCreatePayload
todoMarkDone
(
input
:
TodoMarkDoneInput
!):
TodoMarkDonePayload
todoRestore
(
input
:
TodoRestoreInput
!):
TodoRestorePayload
todoRestoreMany
(
input
:
TodoRestoreManyInput
!):
TodoRestoreManyPayload
...
...
@@ -19812,6 +19813,41 @@ type TodoConnection {
pageInfo
:
PageInfo
!
}
"""
Autogenerated input type of TodoCreate
"""
input
TodoCreateInput
{
"""
A
unique
identifier
for
the
client
performing
the
mutation
.
"""
clientMutationId
:
String
"""
The
global
ID
of
the
to
-
do
item
'
s
parent
.
Issues
,
merge
requests
,
designs
and
epics
are
supported
"""
targetId
:
TodoableID
!
}
"""
Autogenerated return type of TodoCreate
"""
type
TodoCreatePayload
{
"""
A
unique
identifier
for
the
client
performing
the
mutation
.
"""
clientMutationId
:
String
"""
Errors
encountered
during
execution
of
the
mutation
.
"""
errors
:
[
String
!]!
"""
The
to
-
do
created
"""
todo
:
Todo
}
"""
An edge in a connection.
"""
...
...
@@ -19979,6 +20015,11 @@ enum TodoTargetEnum {
MERGEREQUEST
}
"""
Identifier of Todoable
"""
scalar
TodoableID
"""
Autogenerated input type of TodosMarkAllDone
"""
...
...
doc/api/graphql/reference/gitlab_schema.json
View file @
a44e178a
...
...
@@ -37344,6 +37344,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "todoCreate",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "TodoCreateInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "TodoCreatePayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "todoMarkDone",
"description": null,
...
...
@@ -57572,6 +57599,108 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "TodoCreateInput",
"description": "Autogenerated input type of TodoCreate",
"fields": null,
"inputFields": [
{
"name": "targetId",
"description": "The global ID of the to-do item's parent. Issues, merge requests, designs and epics are supported",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "TodoableID",
"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": "TodoCreatePayload",
"description": "Autogenerated return type of TodoCreate",
"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": "Errors encountered during execution of the mutation.",
"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": "todo",
"description": "The to-do created",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Todo",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "TodoEdge",
...
...
@@ -58057,6 +58186,16 @@
],
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "TodoableID",
"description": "Identifier of Todoable",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "TodosMarkAllDoneInput",
doc/api/graphql/reference/index.md
View file @
a44e178a
...
...
@@ -2768,6 +2768,16 @@ Representing a todo entry.
|
`state`
| TodoStateEnum! | State of the todo |
|
`targetType`
| TodoTargetEnum! | Target type of the todo |
### TodoCreatePayload
Autogenerated return type of TodoCreate.
| Field | Type | Description |
| ----- | ---- | ----------- |
|
`clientMutationId`
| String | A unique identifier for the client performing the mutation. |
|
`errors`
| String! => Array | Errors encountered during execution of the mutation. |
|
`todo`
| Todo | The to-do created |
### TodoMarkDonePayload
Autogenerated return type of TodoMarkDone.
...
...
ee/app/models/ee/epic.rb
View file @
a44e178a
...
...
@@ -18,6 +18,7 @@ module EE
include
EpicTreeSorting
include
Presentable
include
IdInOrdered
include
Todoable
enum
state_id:
{
opened:
::
Epic
.
available_states
[
:opened
],
...
...
ee/app/policies/epic_policy.rb
View file @
a44e178a
...
...
@@ -31,4 +31,8 @@ class EpicPolicy < BasePolicy
prevent
:award_emoji
prevent
:read_note
end
rule
{
~
anonymous
&
can?
(
:read_epic
)
}.
policy
do
enable
:create_todo
end
end
ee/spec/graphql/mutations/todos/create_spec.rb
0 → 100644
View file @
a44e178a
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
Mutations
::
Todos
::
Create
do
include
GraphqlHelpers
context
'with epics as target'
do
before
do
stub_licensed_features
(
epics:
true
)
end
it_behaves_like
'create todo mutation'
do
let_it_be
(
:group
)
{
create
(
:group
,
:private
)
}
let_it_be
(
:target
)
{
create
(
:epic
,
group:
group
)
}
end
end
end
ee/spec/policies/epic_policy_spec.rb
View file @
a44e178a
...
...
@@ -28,21 +28,21 @@ RSpec.describe EpicPolicy do
shared_examples
'can only read epics'
do
it
do
is_expected
.
to
be_allowed
(
:read_epic
,
:read_epic_iid
,
:read_note
)
is_expected
.
to
be_allowed
(
:read_epic
,
:read_epic_iid
,
:read_note
,
:create_todo
)
is_expected
.
to
be_disallowed
(
:update_epic
,
:destroy_epic
,
:admin_epic
,
:create_epic
)
end
end
shared_examples
'can manage epics'
do
it
{
is_expected
.
to
be_allowed
(
:read_epic
,
:read_epic_iid
,
:read_note
,
:update_epic
,
:admin_epic
,
:create_epic
)
}
it
{
is_expected
.
to
be_allowed
(
:read_epic
,
:read_epic_iid
,
:read_note
,
:update_epic
,
:admin_epic
,
:create_epic
,
:create_todo
)
}
end
shared_examples
'all epic permissions disabled'
do
it
{
is_expected
.
to
be_disallowed
(
:read_epic
,
:read_epic_iid
,
:update_epic
,
:destroy_epic
,
:admin_epic
,
:create_epic
,
:create_note
,
:award_emoji
,
:read_note
)
}
it
{
is_expected
.
to
be_disallowed
(
:read_epic
,
:read_epic_iid
,
:update_epic
,
:destroy_epic
,
:admin_epic
,
:create_epic
,
:create_note
,
:award_emoji
,
:read_note
,
:create_todo
)
}
end
shared_examples
'all reporter epic permissions enabled'
do
it
{
is_expected
.
to
be_allowed
(
:read_epic
,
:read_epic_iid
,
:update_epic
,
:admin_epic
,
:create_epic
,
:create_note
,
:award_emoji
,
:read_note
)
}
it
{
is_expected
.
to
be_allowed
(
:read_epic
,
:read_epic_iid
,
:update_epic
,
:admin_epic
,
:create_epic
,
:create_note
,
:award_emoji
,
:read_note
,
:create_todo
)
}
end
shared_examples
'group member permissions'
do
...
...
@@ -153,7 +153,8 @@ RSpec.describe EpicPolicy do
context
'anonymous user'
do
let
(
:user
)
{
nil
}
it_behaves_like
'can only read epics'
it
{
is_expected
.
to
be_allowed
(
:read_epic
,
:read_epic_iid
,
:read_note
)
}
it
{
is_expected
.
to
be_disallowed
(
:create_todo
)
}
it_behaves_like
'cannot comment on epics'
end
...
...
spec/graphql/mutations/todos/create_spec.rb
0 → 100644
View file @
a44e178a
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
Mutations
::
Todos
::
Create
do
include
GraphqlHelpers
include
DesignManagementTestHelpers
describe
'#resolve'
do
context
'when target does not support todos'
do
it
'raises error'
do
current_user
=
create
(
:user
)
mutation
=
described_class
.
new
(
object:
nil
,
context:
{
current_user:
current_user
},
field:
nil
)
target
=
create
(
:milestone
)
expect
{
mutation
.
resolve
(
target_id:
global_id_of
(
target
))
}
.
to
raise_error
(
GraphQL
::
CoercionError
)
end
end
context
'with issue as target'
do
it_behaves_like
'create todo mutation'
do
let_it_be
(
:target
)
{
create
(
:issue
)
}
end
end
context
'with merge request as target'
do
it_behaves_like
'create todo mutation'
do
let_it_be
(
:target
)
{
create
(
:merge_request
)
}
end
end
context
'with design as target'
do
before
do
enable_design_management
end
it_behaves_like
'create todo mutation'
do
let_it_be
(
:target
)
{
create
(
:design
)
}
end
end
end
end
spec/policies/issue_policy_spec.rb
View file @
a44e178a
...
...
@@ -139,8 +139,13 @@ RSpec.describe IssuePolicy do
create
(
:project_group_link
,
group:
group
,
project:
project
)
end
it
'does not allow guest to create todos'
do
expect
(
permissions
(
nil
,
issue
)).
to
be_allowed
(
:read_issue
)
expect
(
permissions
(
nil
,
issue
)).
to
be_disallowed
(
:create_todo
)
end
it
'allows guests to read issues'
do
expect
(
permissions
(
guest
,
issue
)).
to
be_allowed
(
:read_issue
,
:read_issue_iid
)
expect
(
permissions
(
guest
,
issue
)).
to
be_allowed
(
:read_issue
,
:read_issue_iid
,
:create_todo
)
expect
(
permissions
(
guest
,
issue
)).
to
be_disallowed
(
:update_issue
,
:admin_issue
,
:reopen_issue
)
expect
(
permissions
(
guest
,
issue_no_assignee
)).
to
be_allowed
(
:read_issue
,
:read_issue_iid
)
...
...
spec/policies/merge_request_policy_spec.rb
View file @
a44e178a
...
...
@@ -24,6 +24,7 @@ RSpec.describe MergeRequestPolicy do
mr_perms
=
%i[create_merge_request_in
create_merge_request_from
read_merge_request
create_todo
approve_merge_request
create_note]
.
freeze
...
...
@@ -47,6 +48,18 @@ RSpec.describe MergeRequestPolicy do
end
end
context
'when merge request is public'
do
context
'and user is anonymous'
do
let
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
,
target_project:
project
,
author:
author
)
}
subject
{
permissions
(
nil
,
merge_request
)
}
it
do
is_expected
.
to
be_disallowed
(
:create_todo
)
end
end
end
context
'when merge requests have been disabled'
do
let!
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
,
target_project:
project
,
author:
author
)
}
...
...
spec/requests/api/graphql/mutations/todos/create_spec.rb
0 → 100644
View file @
a44e178a
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
'Create a todo'
do
include
GraphqlHelpers
let_it_be
(
:current_user
)
{
create
(
:user
)
}
let_it_be
(
:target
)
{
create
(
:issue
)
}
let
(
:input
)
do
{
'targetId'
=>
target
.
to_global_id
.
to_s
}
end
let
(
:mutation
)
{
graphql_mutation
(
:todoCreate
,
input
)
}
let
(
:mutation_response
)
{
graphql_mutation_response
(
:todoCreate
)
}
context
'the user is not allowed to create todo'
do
it_behaves_like
'a mutation that returns a top-level access error'
end
context
'when user has permissions to create todo'
do
before
do
target
.
project
.
add_guest
(
current_user
)
end
it
'creates todo'
do
post_graphql_mutation
(
mutation
,
current_user:
current_user
)
expect
(
response
).
to
have_gitlab_http_status
(
:success
)
expect
(
mutation_response
[
'todo'
][
'body'
]).
to
eq
(
target
.
title
)
expect
(
mutation_response
[
'todo'
][
'state'
]).
to
eq
(
'pending'
)
end
end
end
spec/support/shared_examples/graphql/mutations/create_todo_shared_examples.rb
0 → 100644
View file @
a44e178a
# frozen_string_literal: true
RSpec
.
shared_examples
'create todo mutation'
do
let_it_be
(
:current_user
)
{
create
(
:user
)
}
let
(
:mutation
)
{
described_class
.
new
(
object:
nil
,
context:
{
current_user:
current_user
},
field:
nil
)
}
context
'when user does not have permission to create todo'
do
it
'raises error'
do
expect
{
mutation
.
resolve
(
target_id:
global_id_of
(
target
))
}
.
to
raise_error
(
Gitlab
::
Graphql
::
Errors
::
ResourceNotAvailable
)
end
end
context
'when user has permission to create todo'
do
it
'creates a todo'
do
target
.
resource_parent
.
add_reporter
(
current_user
)
result
=
mutation
.
resolve
(
target_id:
global_id_of
(
target
))
expect
(
result
[
:todo
]).
to
be_valid
expect
(
result
[
:todo
].
target
).
to
eq
(
target
)
expect
(
result
[
:todo
].
state
).
to
eq
(
'pending'
)
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