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
507430cd
Commit
507430cd
authored
May 01, 2018
by
Jan Provaznik
Committed by
Douwe Maan
May 01, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[EE] Discussion API for merge requests and commits
parent
f2bf6717
Changes
17
Show whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
963 additions
and
72 deletions
+963
-72
app/controllers/projects/notes_controller.rb
app/controllers/projects/notes_controller.rb
+1
-3
app/models/commit.rb
app/models/commit.rb
+4
-0
app/services/notes/resolve_service.rb
app/services/notes/resolve_service.rb
+9
-0
changelogs/unreleased/jprovazn-commit-notes-api.yml
changelogs/unreleased/jprovazn-commit-notes-api.yml
+5
-0
doc/api/discussions.md
doc/api/discussions.md
+586
-8
doc/api/notes.md
doc/api/notes.md
+6
-3
lib/api/discussions.rb
lib/api/discussions.rb
+63
-25
lib/api/entities.rb
lib/api/entities.rb
+19
-0
lib/api/helpers.rb
lib/api/helpers.rb
+4
-0
lib/api/helpers/notes_helpers.rb
lib/api/helpers/notes_helpers.rb
+49
-7
lib/api/notes.rb
lib/api/notes.rb
+13
-17
lib/gitlab/diff/position.rb
lib/gitlab/diff/position.rb
+4
-0
spec/fixtures/api/schemas/public_api/v4/notes.json
spec/fixtures/api/schemas/public_api/v4/notes.json
+4
-1
spec/requests/api/discussions_spec.rb
spec/requests/api/discussions_spec.rb
+29
-8
spec/services/notes/resolve_service_spec.rb
spec/services/notes/resolve_service_spec.rb
+23
-0
spec/support/shared_examples/requests/api/diff_discussions.rb
.../support/shared_examples/requests/api/diff_discussions.rb
+57
-0
spec/support/shared_examples/requests/api/resolvable_discussions.rb
...rt/shared_examples/requests/api/resolvable_discussions.rb
+87
-0
No files found.
app/controllers/projects/notes_controller.rb
View file @
507430cd
...
@@ -33,9 +33,7 @@ class Projects::NotesController < Projects::ApplicationController
...
@@ -33,9 +33,7 @@ class Projects::NotesController < Projects::ApplicationController
def
resolve
def
resolve
return
render_404
unless
note
.
resolvable?
return
render_404
unless
note
.
resolvable?
note
.
resolve!
(
current_user
)
Notes
::
ResolveService
.
new
(
project
,
current_user
).
execute
(
note
)
MergeRequests
::
ResolvedDiscussionNotificationService
.
new
(
project
,
current_user
).
execute
(
note
.
noteable
)
discussion
=
note
.
discussion
discussion
=
note
.
discussion
...
...
app/models/commit.rb
View file @
507430cd
...
@@ -105,6 +105,10 @@ class Commit
...
@@ -105,6 +105,10 @@ class Commit
end
end
end
end
end
end
def
parent_class
::
Project
end
end
end
attr_accessor
:raw
attr_accessor
:raw
...
...
app/services/notes/resolve_service.rb
0 → 100644
View file @
507430cd
module
Notes
class
ResolveService
<
::
BaseService
def
execute
(
note
)
note
.
resolve!
(
current_user
)
::
MergeRequests
::
ResolvedDiscussionNotificationService
.
new
(
project
,
current_user
).
execute
(
note
.
noteable
)
end
end
end
changelogs/unreleased/jprovazn-commit-notes-api.yml
0 → 100644
View file @
507430cd
---
title
:
Add discussion API for merge requests and commits
merge_request
:
author
:
type
:
added
doc/api/discussions.md
View file @
507430cd
# Discussions API
# Discussions API
Discussions are set of related notes on snippets, issues
or epic
s.
Discussions are set of related notes on snippets, issues
, epics, merge requests or commit
s.
## Issues
## Issues
...
@@ -61,7 +61,8 @@ GET /projects/:id/issues/:issue_iid/discussions
...
@@ -61,7 +61,8 @@ GET /projects/:id/issues/:issue_iid/discussions
"system"
:
false
,
"system"
:
false
,
"noteable_id"
:
3
,
"noteable_id"
:
3
,
"noteable_type"
:
"Issue"
,
"noteable_type"
:
"Issue"
,
"noteable_iid"
:
null
"noteable_iid"
:
null
,
"resolvable"
:
false
}
}
]
]
},
},
...
@@ -87,7 +88,8 @@ GET /projects/:id/issues/:issue_iid/discussions
...
@@ -87,7 +88,8 @@ GET /projects/:id/issues/:issue_iid/discussions
"system"
:
false
,
"system"
:
false
,
"noteable_id"
:
3
,
"noteable_id"
:
3
,
"noteable_type"
:
"Issue"
,
"noteable_type"
:
"Issue"
,
"noteable_iid"
:
null
"noteable_iid"
:
null
,
"resolvable"
:
false
}
}
]
]
}
}
...
@@ -265,7 +267,8 @@ GET /projects/:id/snippets/:snippet_id/discussions
...
@@ -265,7 +267,8 @@ GET /projects/:id/snippets/:snippet_id/discussions
"system"
:
false
,
"system"
:
false
,
"noteable_id"
:
3
,
"noteable_id"
:
3
,
"noteable_type"
:
"Snippet"
,
"noteable_type"
:
"Snippet"
,
"noteable_id"
:
null
"noteable_id"
:
null
,
"resolvable"
:
false
}
}
]
]
},
},
...
@@ -291,7 +294,8 @@ GET /projects/:id/snippets/:snippet_id/discussions
...
@@ -291,7 +294,8 @@ GET /projects/:id/snippets/:snippet_id/discussions
"system"
:
false
,
"system"
:
false
,
"noteable_id"
:
3
,
"noteable_id"
:
3
,
"noteable_type"
:
"Snippet"
,
"noteable_type"
:
"Snippet"
,
"noteable_id"
:
null
"noteable_id"
:
null
,
"resolvable"
:
false
}
}
]
]
}
}
...
@@ -449,7 +453,8 @@ GET /groups/:id/epics/:epic_id/discussions
...
@@ -449,7 +453,8 @@ GET /groups/:id/epics/:epic_id/discussions
"system"
:
false
,
"system"
:
false
,
"noteable_id"
:
3
,
"noteable_id"
:
3
,
"noteable_type"
:
"Epic"
,
"noteable_type"
:
"Epic"
,
"noteable_id"
:
null
"noteable_id"
:
null
,
"resolvable"
:
false
},
},
{
{
"id"
:
1129
,
"id"
:
1129
,
...
@@ -469,7 +474,8 @@ GET /groups/:id/epics/:epic_id/discussions
...
@@ -469,7 +474,8 @@ GET /groups/:id/epics/:epic_id/discussions
"system"
:
false
,
"system"
:
false
,
"noteable_id"
:
3
,
"noteable_id"
:
3
,
"noteable_type"
:
"Epic"
,
"noteable_type"
:
"Epic"
,
"noteable_id"
:
null
"noteable_id"
:
null
,
"resolvable"
:
false
}
}
]
]
},
},
...
@@ -495,7 +501,8 @@ GET /groups/:id/epics/:epic_id/discussions
...
@@ -495,7 +501,8 @@ GET /groups/:id/epics/:epic_id/discussions
"system"
:
false
,
"system"
:
false
,
"noteable_id"
:
3
,
"noteable_id"
:
3
,
"noteable_type"
:
"Epic"
,
"noteable_type"
:
"Epic"
,
"noteable_id"
:
null
"noteable_id"
:
null
,
"resolvable"
:
false
}
}
]
]
}
}
...
@@ -613,3 +620,574 @@ Parameters:
...
@@ -613,3 +620,574 @@ Parameters:
```
bash
```
bash
curl
--request
DELETE
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/groups/5/epics/11/discussions/636
curl
--request
DELETE
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/groups/5/epics/11/discussions/636
```
```
## Merge requests
### List project merge request discussions
Gets a list of all discussions for a single merge request.
```
GET /projects/:id/merge_requests/:merge_request_iid/discussions
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ------------ |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
|
|
`merge_request_iid`
| integer | yes | The IID of a merge request |
```
json
[
{
"id"
:
"6a9c1750b37d513a43987b574953fceb50b03ce7"
,
"individual_note"
:
false
,
"notes"
:
[
{
"id"
:
1126
,
"type"
:
"DiscussionNote"
,
"body"
:
"discussion text"
,
"attachment"
:
null
,
"author"
:
{
"id"
:
1
,
"name"
:
"root"
,
"username"
:
"root"
,
"state"
:
"active"
,
"avatar_url"
:
"https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon"
,
"web_url"
:
"http://localhost:3000/root"
},
"created_at"
:
"2018-03-03T21:54:39.668Z"
,
"updated_at"
:
"2018-03-03T21:54:39.668Z"
,
"system"
:
false
,
"noteable_id"
:
3
,
"noteable_type"
:
"Merge request"
,
"noteable_iid"
:
null
,
"resolved"
:
false
,
"resolvable"
:
true
,
"resolved_by"
:
null
},
{
"id"
:
1129
,
"type"
:
"DiscussionNote"
,
"body"
:
"reply to the discussion"
,
"attachment"
:
null
,
"author"
:
{
"id"
:
1
,
"name"
:
"root"
,
"username"
:
"root"
,
"state"
:
"active"
,
"avatar_url"
:
"https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon"
,
"web_url"
:
"http://localhost:3000/root"
},
"created_at"
:
"2018-03-04T13:38:02.127Z"
,
"updated_at"
:
"2018-03-04T13:38:02.127Z"
,
"system"
:
false
,
"noteable_id"
:
3
,
"noteable_type"
:
"Merge request"
,
"noteable_iid"
:
null
,
"resolved"
:
false
,
"resolvable"
:
true
,
"resolved_by"
:
null
}
]
},
{
"id"
:
"87805b7c09016a7058e91bdbe7b29d1f284a39e6"
,
"individual_note"
:
true
,
"notes"
:
[
{
"id"
:
1128
,
"type"
:
null
,
"body"
:
"a single comment"
,
"attachment"
:
null
,
"author"
:
{
"id"
:
1
,
"name"
:
"root"
,
"username"
:
"root"
,
"state"
:
"active"
,
"avatar_url"
:
"https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon"
,
"web_url"
:
"http://localhost:3000/root"
},
"created_at"
:
"2018-03-04T09:17:22.520Z"
,
"updated_at"
:
"2018-03-04T09:17:22.520Z"
,
"system"
:
false
,
"noteable_id"
:
3
,
"noteable_type"
:
"Merge request"
,
"noteable_iid"
:
null
,
"resolved"
:
false
,
"resolvable"
:
true
,
"resolved_by"
:
null
}
]
}
]
```
Diff comments contain also position:
```
json
[
{
"id"
:
"87805b7c09016a7058e91bdbe7b29d1f284a39e6"
,
"individual_note"
:
false
,
"notes"
:
[
{
"id"
:
1128
,
"type"
:
DiffNote
,
"body"
:
"diff comment"
,
"attachment"
:
null
,
"author"
:
{
"id"
:
1
,
"name"
:
"root"
,
"username"
:
"root"
,
"state"
:
"active"
,
"avatar_url"
:
"https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon"
,
"web_url"
:
"http://localhost:3000/root"
},
"created_at"
:
"2018-03-04T09:17:22.520Z"
,
"updated_at"
:
"2018-03-04T09:17:22.520Z"
,
"system"
:
false
,
"noteable_id"
:
3
,
"noteable_type"
:
"Merge request"
,
"noteable_iid"
:
null
,
"position"
:
{
"base_sha"
:
"b5d6e7b1613fca24d250fa8e5bc7bcc3dd6002ef"
,
"start_sha"
:
"7c9c2ead8a320fb7ba0b4e234bd9529a2614e306"
,
"head_sha"
:
"4803c71e6b1833ca72b8b26ef2ecd5adc8a38031"
,
"old_path"
:
"package.json"
,
"new_path"
:
"package.json"
,
"position_type"
:
"text"
,
"old_line"
:
27
,
"new_line"
:
27
},
"resolved"
:
false
,
"resolvable"
:
true
,
"resolved_by"
:
null
}
]
}
]
```
```
bash
curl
--request
GET
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions
```
### Get single merge request discussion
Returns a single discussion for a specific project merge request
```
GET /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id
```
Parameters:
| Attribute | Type | Required | Description |
| ------------------- | -------------- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
|
|
`merge_request_iid`
| integer | yes | The IID of a merge request |
|
`discussion_id`
| integer | yes | The ID of a discussion |
```
bash
curl
--request
GET
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
```
### Create new merge request discussion
Creates a new discussion to a single project merge request. This is similar to creating
a note but but another comments (replies) can be added to it later.
```
POST /projects/:id/merge_requests/:merge_request_iid/discussions
```
Parameters:
| Attribute | Type | Required | Description |
| ------------------------- | -------------- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
|
|
`merge_request_iid`
| integer | yes | The IID of a merge request |
|
`body`
| string | yes | The content of a discussion |
|
`created_at`
| string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z |
|
`position`
| hash | no | Position when creating a diff note |
|
`position[base_sha]`
| string | yes | Base commit SHA in the source branch |
|
`position[start_sha]`
| string | yes | SHA referencing commit in target branch |
|
`position[head_sha]`
| string | yes | SHA referencing HEAD of this merge request |
|
`position[position_type]`
| string | yes | Type of the position reference', allowed values: 'text' or 'image' |
|
`position[new_path]`
| string | no | File path after change |
|
`position[new_line]`
| integer | no | Line number after change (for 'text' diff notes) |
|
`position[old_path]`
| string | no | File path before change |
|
`position[old_line]`
| integer | no | Line number before change (for 'text' diff notes) |
|
`position[width]`
| integer | no | Width of the image (for 'image' diff notes) |
|
`position[height]`
| integer | no | Height of the image (for 'image' diff notes) |
|
`position[x]`
| integer | no | X coordinate (for 'image' diff notes) |
|
`position[y]`
| integer | no | Y coordinate (for 'image' diff notes) |
```
bash
curl
--request
POST
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions?body
=
comment
```
### Resolve a merge request discussion
Resolve/unresolve whole discussion of a merge request.
```
PUT /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id
```
Parameters:
| Attribute | Type | Required | Description |
| ------------------- | -------------- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
|
|
`merge_request_iid`
| integer | yes | The IID of a merge request |
|
`discussion_id`
| integer | yes | The ID of a discussion |
|
`resolved`
| boolean | yes | Resolve/unresolve the discussion |
```
bash
curl
--request
PUT
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7?resolved
=
true
```
### Add note to existing merge request discussion
Adds a new note to the discussion.
```
POST /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id/notes
```
Parameters:
| Attribute | Type | Required | Description |
| ------------------- | -------------- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
|
|
`merge_request_iid`
| integer | yes | The IID of a merge request |
|
`discussion_id`
| integer | yes | The ID of a discussion |
|
`note_id`
| integer | yes | The ID of a discussion note |
|
`body`
| string | yes | The content of a discussion |
|
`created_at`
| string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z |
```
bash
curl
--request
POST
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body
=
comment
```
### Modify an existing merge request discussion note
Modify or resolve an existing discussion note of a merge request.
```
PUT /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id/notes/:note_id
```
Parameters:
| Attribute | Type | Required | Description |
| ------------------- | -------------- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
|
|
`merge_request_iid`
| integer | yes | The IID of a merge request |
|
`discussion_id`
| integer | yes | The ID of a discussion |
|
`note_id`
| integer | yes | The ID of a discussion note |
|
`body`
| string | no | The content of a discussion (exactly one of
`body`
or
`resolved`
must be set |
|
`resolved`
| boolean | no | Resolve/unresolve the note (exactly one of
`body`
or
`resolved`
must be set |
```
bash
curl
--request
PUT
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body
=
comment
```
Resolving a note:
```
bash
curl
--request
PUT
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?resolved
=
true
```
### Delete a merge request discussion note
Deletes an existing discussion note of a merge request.
```
DELETE /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id/notes/:note_id
```
Parameters:
| Attribute | Type | Required | Description |
| ------------------- | -------------- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
|
|
`merge_request_iid`
| integer | yes | The IID of a merge request |
|
`discussion_id`
| integer | yes | The ID of a discussion |
|
`note_id`
| integer | yes | The ID of a discussion note |
```
bash
curl
--request
DELETE
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/636
```
## Commits
### List project commit discussions
Gets a list of all discussions for a single commit.
```
GET /projects/:id/commits/:commit_id/discussions
```
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ------------ |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
|
|
`commit_id`
| integer | yes | The ID of a commit |
```
json
[
{
"id"
:
"6a9c1750b37d513a43987b574953fceb50b03ce7"
,
"individual_note"
:
false
,
"notes"
:
[
{
"id"
:
1126
,
"type"
:
"DiscussionNote"
,
"body"
:
"discussion text"
,
"attachment"
:
null
,
"author"
:
{
"id"
:
1
,
"name"
:
"root"
,
"username"
:
"root"
,
"state"
:
"active"
,
"avatar_url"
:
"https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon"
,
"web_url"
:
"http://localhost:3000/root"
},
"created_at"
:
"2018-03-03T21:54:39.668Z"
,
"updated_at"
:
"2018-03-03T21:54:39.668Z"
,
"system"
:
false
,
"noteable_id"
:
3
,
"noteable_type"
:
"Commit"
,
"noteable_iid"
:
null
,
"resolvable"
:
false
},
{
"id"
:
1129
,
"type"
:
"DiscussionNote"
,
"body"
:
"reply to the discussion"
,
"attachment"
:
null
,
"author"
:
{
"id"
:
1
,
"name"
:
"root"
,
"username"
:
"root"
,
"state"
:
"active"
,
"avatar_url"
:
"https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon"
,
"web_url"
:
"http://localhost:3000/root"
},
"created_at"
:
"2018-03-04T13:38:02.127Z"
,
"updated_at"
:
"2018-03-04T13:38:02.127Z"
,
"system"
:
false
,
"noteable_id"
:
3
,
"noteable_type"
:
"Commit"
,
"noteable_iid"
:
null
,
"resolvable"
:
false
}
]
},
{
"id"
:
"87805b7c09016a7058e91bdbe7b29d1f284a39e6"
,
"individual_note"
:
true
,
"notes"
:
[
{
"id"
:
1128
,
"type"
:
null
,
"body"
:
"a single comment"
,
"attachment"
:
null
,
"author"
:
{
"id"
:
1
,
"name"
:
"root"
,
"username"
:
"root"
,
"state"
:
"active"
,
"avatar_url"
:
"https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon"
,
"web_url"
:
"http://localhost:3000/root"
},
"created_at"
:
"2018-03-04T09:17:22.520Z"
,
"updated_at"
:
"2018-03-04T09:17:22.520Z"
,
"system"
:
false
,
"noteable_id"
:
3
,
"noteable_type"
:
"Commit"
,
"noteable_iid"
:
null
,
"resolvable"
:
false
}
]
}
]
```
Diff comments contain also position:
```
json
[
{
"id"
:
"87805b7c09016a7058e91bdbe7b29d1f284a39e6"
,
"individual_note"
:
false
,
"notes"
:
[
{
"id"
:
1128
,
"type"
:
DiffNote
,
"body"
:
"diff comment"
,
"attachment"
:
null
,
"author"
:
{
"id"
:
1
,
"name"
:
"root"
,
"username"
:
"root"
,
"state"
:
"active"
,
"avatar_url"
:
"https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon"
,
"web_url"
:
"http://localhost:3000/root"
},
"created_at"
:
"2018-03-04T09:17:22.520Z"
,
"updated_at"
:
"2018-03-04T09:17:22.520Z"
,
"system"
:
false
,
"noteable_id"
:
3
,
"noteable_type"
:
"Commit"
,
"noteable_iid"
:
null
,
"position"
:
{
"base_sha"
:
"b5d6e7b1613fca24d250fa8e5bc7bcc3dd6002ef"
,
"start_sha"
:
"7c9c2ead8a320fb7ba0b4e234bd9529a2614e306"
,
"head_sha"
:
"4803c71e6b1833ca72b8b26ef2ecd5adc8a38031"
,
"old_path"
:
"package.json"
,
"new_path"
:
"package.json"
,
"position_type"
:
"text"
,
"old_line"
:
27
,
"new_line"
:
27
},
"resolvable"
:
false
}
]
}
]
```
```
bash
curl
--request
GET
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/5/commits/11/discussions
```
### Get single commit discussion
Returns a single discussion for a specific project commit
```
GET /projects/:id/commits/:commit_id/discussions/:discussion_id
```
Parameters:
| Attribute | Type | Required | Description |
| ------------------- | -------------- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
|
|
`commit_id`
| integer | yes | The ID of a commit |
|
`discussion_id`
| integer | yes | The ID of a discussion |
```
bash
curl
--request
GET
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
```
### Create new commit discussion
Creates a new discussion to a single project commit. This is similar to creating
a note but but another comments (replies) can be added to it later.
```
POST /projects/:id/commits/:commit_id/discussions
```
Parameters:
| Attribute | Type | Required | Description |
| ------------------------- | -------------- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
|
|
`commit_id`
| integer | yes | The ID of a commit |
|
`body`
| string | yes | The content of a discussion |
|
`created_at`
| string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z |
|
`position`
| hash | no | Position when creating a diff note |
|
`position[base_sha]`
| string | yes | Base commit SHA in the source branch |
|
`position[start_sha]`
| string | yes | SHA referencing commit in target branch |
|
`position[head_sha]`
| string | yes | SHA referencing HEAD of this commit |
|
`position[position_type]`
| string | yes | Type of the position reference', allowed values: 'text' or 'image' |
|
`position[new_path]`
| string | no | File path after change |
|
`position[new_line]`
| integer | no | Line number after change |
|
`position[old_path]`
| string | no | File path before change |
|
`position[old_line]`
| integer | no | Line number before change |
|
`position[width]`
| integer | no | Width of the image (for 'image' diff notes) |
|
`position[height]`
| integer | no | Height of the image (for 'image' diff notes) |
|
`position[x]`
| integer | no | X coordinate (for 'image' diff notes) |
|
`position[y]`
| integer | no | Y coordinate (for 'image' diff notes) |
```
bash
curl
--request
POST
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/5/commits/11/discussions?body
=
comment
```
### Add note to existing commit discussion
Adds a new note to the discussion.
```
POST /projects/:id/commits/:commit_id/discussions/:discussion_id/notes
```
Parameters:
| Attribute | Type | Required | Description |
| ------------------- | -------------- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
|
|
`commit_id`
| integer | yes | The ID of a commit |
|
`discussion_id`
| integer | yes | The ID of a discussion |
|
`note_id`
| integer | yes | The ID of a discussion note |
|
`body`
| string | yes | The content of a discussion |
|
`created_at`
| string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z |
```
bash
curl
--request
POST
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body
=
comment
```
### Modify an existing commit discussion note
Modify or resolve an existing discussion note of a commit.
```
PUT /projects/:id/commits/:commit_id/discussions/:discussion_id/notes/:note_id
```
Parameters:
| Attribute | Type | Required | Description |
| ------------------- | -------------- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
|
|
`commit_id`
| integer | yes | The ID of a commit |
|
`discussion_id`
| integer | yes | The ID of a discussion |
|
`note_id`
| integer | yes | The ID of a discussion note |
|
`body`
| string | no | The content of a note |
```
bash
curl
--request
PUT
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body
=
comment
```
Resolving a note:
```
bash
curl
--request
PUT
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?resolved
=
true
```
### Delete a commit discussion note
Deletes an existing discussion note of a commit.
```
DELETE /projects/:id/commits/:commit_id/discussions/:discussion_id/notes/:note_id
```
Parameters:
| Attribute | Type | Required | Description |
| ------------------- | -------------- | -------- | ----------- |
|
`id`
| integer/string | yes | The ID or
[
URL-encoded path of the project
](
README.md#namespaced-path-encoding
)
|
|
`commit_id`
| integer | yes | The ID of a commit |
|
`discussion_id`
| integer | yes | The ID of a discussion |
|
`note_id`
| integer | yes | The ID of a discussion note |
```
bash
curl
--request
DELETE
--header
"PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK"
https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/636
```
doc/api/notes.md
View file @
507430cd
...
@@ -39,7 +39,8 @@ GET /projects/:id/issues/:issue_iid/notes?sort=asc&order_by=updated_at
...
@@ -39,7 +39,8 @@ GET /projects/:id/issues/:issue_iid/notes?sort=asc&order_by=updated_at
"system"
:
true
,
"system"
:
true
,
"noteable_id"
:
377
,
"noteable_id"
:
377
,
"noteable_type"
:
"Issue"
,
"noteable_type"
:
"Issue"
,
"noteable_iid"
:
377
"noteable_iid"
:
377
,
"resolvable"
:
false
},
},
{
{
"id"
:
305
,
"id"
:
305
,
...
@@ -58,7 +59,8 @@ GET /projects/:id/issues/:issue_iid/notes?sort=asc&order_by=updated_at
...
@@ -58,7 +59,8 @@ GET /projects/:id/issues/:issue_iid/notes?sort=asc&order_by=updated_at
"system"
:
true
,
"system"
:
true
,
"noteable_id"
:
121
,
"noteable_id"
:
121
,
"noteable_type"
:
"Issue"
,
"noteable_type"
:
"Issue"
,
"noteable_iid"
:
121
"noteable_iid"
:
121
,
"resolvable"
:
false
}
}
]
]
```
```
...
@@ -314,7 +316,8 @@ Parameters:
...
@@ -314,7 +316,8 @@ Parameters:
"system"
:
false
,
"system"
:
false
,
"noteable_id"
:
2
,
"noteable_id"
:
2
,
"noteable_type"
:
"MergeRequest"
,
"noteable_type"
:
"MergeRequest"
,
"noteable_iid"
:
2
"noteable_iid"
:
2
,
"resolvable"
:
false
}
}
```
```
...
...
lib/api/discussions.rb
View file @
507430cd
...
@@ -5,11 +5,12 @@ module API
...
@@ -5,11 +5,12 @@ module API
before
{
authenticate!
}
before
{
authenticate!
}
NOTEABLE_TYPES
=
[
Issue
,
Snippet
,
Epic
].
freeze
NOTEABLE_TYPES
=
[
Issue
,
Snippet
,
Epic
,
MergeRequest
,
Commit
].
freeze
NOTEABLE_TYPES
.
each
do
|
noteable_type
|
NOTEABLE_TYPES
.
each
do
|
noteable_type
|
parent_type
=
noteable_type
.
parent_class
.
to_s
.
underscore
parent_type
=
noteable_type
.
parent_class
.
to_s
.
underscore
noteables_str
=
noteable_type
.
to_s
.
underscore
.
pluralize
noteables_str
=
noteable_type
.
to_s
.
underscore
.
pluralize
noteables_path
=
noteable_type
==
Commit
?
"repository/
#{
noteables_str
}
"
:
noteables_str
params
do
params
do
requires
:id
,
type:
String
,
desc:
"The ID of a
#{
parent_type
}
"
requires
:id
,
type:
String
,
desc:
"The ID of a
#{
parent_type
}
"
...
@@ -19,14 +20,12 @@ module API
...
@@ -19,14 +20,12 @@ module API
success
Entities
::
Discussion
success
Entities
::
Discussion
end
end
params
do
params
do
requires
:noteable_id
,
type
:
Integer
,
desc:
'The ID of the noteable'
requires
:noteable_id
,
type
s:
[
Integer
,
String
]
,
desc:
'The ID of the noteable'
use
:pagination
use
:pagination
end
end
get
":id/
#{
noteables_
str
}
/:noteable_id/discussions"
do
get
":id/
#{
noteables_
path
}
/:noteable_id/discussions"
do
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
break
not_found!
(
"Discussions"
)
unless
can?
(
current_user
,
noteable_read_ability_name
(
noteable
),
noteable
)
notes
=
noteable
.
notes
notes
=
noteable
.
notes
.
inc_relations_for_view
.
inc_relations_for_view
.
includes
(
:noteable
)
.
includes
(
:noteable
)
...
@@ -43,13 +42,13 @@ module API
...
@@ -43,13 +42,13 @@ module API
end
end
params
do
params
do
requires
:discussion_id
,
type:
String
,
desc:
'The ID of a discussion'
requires
:discussion_id
,
type:
String
,
desc:
'The ID of a discussion'
requires
:noteable_id
,
type
:
Integer
,
desc:
'The ID of the noteable'
requires
:noteable_id
,
type
s:
[
Integer
,
String
]
,
desc:
'The ID of the noteable'
end
end
get
":id/
#{
noteables_
str
}
/:noteable_id/discussions/:discussion_id"
do
get
":id/
#{
noteables_
path
}
/:noteable_id/discussions/:discussion_id"
do
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
notes
=
readable_discussion_notes
(
noteable
,
params
[
:discussion_id
])
notes
=
readable_discussion_notes
(
noteable
,
params
[
:discussion_id
])
if
notes
.
empty?
||
!
can?
(
current_user
,
noteable_read_ability_name
(
noteable
),
noteable
)
if
notes
.
empty?
break
not_found!
(
"Discussion"
)
break
not_found!
(
"Discussion"
)
end
end
...
@@ -62,19 +61,36 @@ module API
...
@@ -62,19 +61,36 @@ module API
success
Entities
::
Discussion
success
Entities
::
Discussion
end
end
params
do
params
do
requires
:noteable_id
,
type
:
Integer
,
desc:
'The ID of the noteable'
requires
:noteable_id
,
type
s:
[
Integer
,
String
]
,
desc:
'The ID of the noteable'
requires
:body
,
type:
String
,
desc:
'The content of a note'
requires
:body
,
type:
String
,
desc:
'The content of a note'
optional
:created_at
,
type:
String
,
desc:
'The creation date of the note'
optional
:created_at
,
type:
String
,
desc:
'The creation date of the note'
end
optional
:position
,
type:
Hash
do
post
":id/
#{
noteables_str
}
/:noteable_id/discussions"
do
requires
:base_sha
,
type:
String
,
desc:
'Base commit SHA in the source branch'
requires
:start_sha
,
type:
String
,
desc:
'SHA referencing commit in target branch'
requires
:head_sha
,
type:
String
,
desc:
'SHA referencing HEAD of this merge request'
requires
:position_type
,
type:
String
,
desc:
'Type of the position reference'
,
values:
%w(text image)
optional
:new_path
,
type:
String
,
desc:
'File path after change'
optional
:new_line
,
type:
Integer
,
desc:
'Line number after change'
optional
:old_path
,
type:
String
,
desc:
'File path before change'
optional
:old_line
,
type:
Integer
,
desc:
'Line number before change'
optional
:width
,
type:
Integer
,
desc:
'Width of the image'
optional
:height
,
type:
Integer
,
desc:
'Height of the image'
optional
:x
,
type:
Integer
,
desc:
'X coordinate in the image'
optional
:y
,
type:
Integer
,
desc:
'Y coordinate in the image'
end
end
post
":id/
#{
noteables_path
}
/:noteable_id/discussions"
do
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
type
=
params
[
:position
]
?
'DiffNote'
:
'DiscussionNote'
id_key
=
noteable
.
is_a?
(
Commit
)
?
:commit_id
:
:noteable_id
opts
=
{
opts
=
{
note:
params
[
:body
],
note:
params
[
:body
],
created_at:
params
[
:created_at
],
created_at:
params
[
:created_at
],
type:
'DiscussionNote'
,
type:
type
,
noteable_type:
noteables_str
.
classify
,
noteable_type:
noteables_str
.
classify
,
noteable_id:
noteable
.
id
position:
params
[
:position
],
id_key
=>
noteable
.
id
}
}
note
=
create_note
(
noteable
,
opts
)
note
=
create_note
(
noteable
,
opts
)
...
@@ -91,13 +107,13 @@ module API
...
@@ -91,13 +107,13 @@ module API
end
end
params
do
params
do
requires
:discussion_id
,
type:
String
,
desc:
'The ID of a discussion'
requires
:discussion_id
,
type:
String
,
desc:
'The ID of a discussion'
requires
:noteable_id
,
type
:
Integer
,
desc:
'The ID of the noteable'
requires
:noteable_id
,
type
s:
[
Integer
,
String
]
,
desc:
'The ID of the noteable'
end
end
get
":id/
#{
noteables_
str
}
/:noteable_id/discussions/:discussion_id/notes"
do
get
":id/
#{
noteables_
path
}
/:noteable_id/discussions/:discussion_id/notes"
do
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
notes
=
readable_discussion_notes
(
noteable
,
params
[
:discussion_id
])
notes
=
readable_discussion_notes
(
noteable
,
params
[
:discussion_id
])
if
notes
.
empty?
||
!
can?
(
current_user
,
noteable_read_ability_name
(
noteable
),
noteable
)
if
notes
.
empty?
break
not_found!
(
"Notes"
)
break
not_found!
(
"Notes"
)
end
end
...
@@ -108,12 +124,12 @@ module API
...
@@ -108,12 +124,12 @@ module API
success
Entities
::
Note
success
Entities
::
Note
end
end
params
do
params
do
requires
:noteable_id
,
type
:
Integer
,
desc:
'The ID of the noteable'
requires
:noteable_id
,
type
s:
[
Integer
,
String
]
,
desc:
'The ID of the noteable'
requires
:discussion_id
,
type:
String
,
desc:
'The ID of a discussion'
requires
:discussion_id
,
type:
String
,
desc:
'The ID of a discussion'
requires
:body
,
type:
String
,
desc:
'The content of a note'
requires
:body
,
type:
String
,
desc:
'The content of a note'
optional
:created_at
,
type:
String
,
desc:
'The creation date of the note'
optional
:created_at
,
type:
String
,
desc:
'The creation date of the note'
end
end
post
":id/
#{
noteables_
str
}
/:noteable_id/discussions/:discussion_id/notes"
do
post
":id/
#{
noteables_
path
}
/:noteable_id/discussions/:discussion_id/notes"
do
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
notes
=
readable_discussion_notes
(
noteable
,
params
[
:discussion_id
])
notes
=
readable_discussion_notes
(
noteable
,
params
[
:discussion_id
])
...
@@ -139,11 +155,11 @@ module API
...
@@ -139,11 +155,11 @@ module API
success
Entities
::
Note
success
Entities
::
Note
end
end
params
do
params
do
requires
:noteable_id
,
type
:
Integer
,
desc:
'The ID of the noteable'
requires
:noteable_id
,
type
s:
[
Integer
,
String
]
,
desc:
'The ID of the noteable'
requires
:discussion_id
,
type:
String
,
desc:
'The ID of a discussion'
requires
:discussion_id
,
type:
String
,
desc:
'The ID of a discussion'
requires
:note_id
,
type:
Integer
,
desc:
'The ID of a note'
requires
:note_id
,
type:
Integer
,
desc:
'The ID of a note'
end
end
get
":id/
#{
noteables_
str
}
/:noteable_id/discussions/:discussion_id/notes/:note_id"
do
get
":id/
#{
noteables_
path
}
/:noteable_id/discussions/:discussion_id/notes/:note_id"
do
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
get_note
(
noteable
,
params
[
:note_id
])
get_note
(
noteable
,
params
[
:note_id
])
...
@@ -153,30 +169,52 @@ module API
...
@@ -153,30 +169,52 @@ module API
success
Entities
::
Note
success
Entities
::
Note
end
end
params
do
params
do
requires
:noteable_id
,
type
:
Integer
,
desc:
'The ID of the noteable'
requires
:noteable_id
,
type
s:
[
Integer
,
String
]
,
desc:
'The ID of the noteable'
requires
:discussion_id
,
type:
String
,
desc:
'The ID of a discussion'
requires
:discussion_id
,
type:
String
,
desc:
'The ID of a discussion'
requires
:note_id
,
type:
Integer
,
desc:
'The ID of a note'
requires
:note_id
,
type:
Integer
,
desc:
'The ID of a note'
requires
:body
,
type:
String
,
desc:
'The content of a note'
optional
:body
,
type:
String
,
desc:
'The content of a note'
optional
:resolved
,
type:
Boolean
,
desc:
'Mark note resolved/unresolved'
exactly_one_of
:body
,
:resolved
end
end
put
":id/
#{
noteables_
str
}
/:noteable_id/discussions/:discussion_id/notes/:note_id"
do
put
":id/
#{
noteables_
path
}
/:noteable_id/discussions/:discussion_id/notes/:note_id"
do
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
if
params
[
:resolved
].
nil?
update_note
(
noteable
,
params
[
:note_id
])
update_note
(
noteable
,
params
[
:note_id
])
else
resolve_note
(
noteable
,
params
[
:note_id
],
params
[
:resolved
])
end
end
end
desc
"Delete a comment in a
#{
noteable_type
.
to_s
.
downcase
}
discussion"
do
desc
"Delete a comment in a
#{
noteable_type
.
to_s
.
downcase
}
discussion"
do
success
Entities
::
Note
success
Entities
::
Note
end
end
params
do
params
do
requires
:noteable_id
,
type
:
Integer
,
desc:
'The ID of the noteable'
requires
:noteable_id
,
type
s:
[
Integer
,
String
]
,
desc:
'The ID of the noteable'
requires
:discussion_id
,
type:
String
,
desc:
'The ID of a discussion'
requires
:discussion_id
,
type:
String
,
desc:
'The ID of a discussion'
requires
:note_id
,
type:
Integer
,
desc:
'The ID of a note'
requires
:note_id
,
type:
Integer
,
desc:
'The ID of a note'
end
end
delete
":id/
#{
noteables_
str
}
/:noteable_id/discussions/:discussion_id/notes/:note_id"
do
delete
":id/
#{
noteables_
path
}
/:noteable_id/discussions/:discussion_id/notes/:note_id"
do
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
delete_note
(
noteable
,
params
[
:note_id
])
delete_note
(
noteable
,
params
[
:note_id
])
end
end
if
Noteable
::
RESOLVABLE_TYPES
.
include?
(
noteable_type
.
to_s
)
desc
"Resolve/unresolve an existing
#{
noteable_type
.
to_s
.
downcase
}
discussion"
do
success
Entities
::
Discussion
end
params
do
requires
:noteable_id
,
types:
[
Integer
,
String
],
desc:
'The ID of the noteable'
requires
:discussion_id
,
type:
String
,
desc:
'The ID of a discussion'
requires
:resolved
,
type:
Boolean
,
desc:
'Mark discussion resolved/unresolved'
end
put
":id/
#{
noteables_path
}
/:noteable_id/discussions/:discussion_id"
do
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
resolve_discussion
(
noteable
,
params
[
:discussion_id
],
params
[
:resolved
])
end
end
end
end
end
end
...
...
lib/api/entities.rb
View file @
507430cd
...
@@ -287,6 +287,10 @@ module API
...
@@ -287,6 +287,10 @@ module API
end
end
end
end
class
DiffRefs
<
Grape
::
Entity
expose
:base_sha
,
:head_sha
,
:start_sha
end
class
Commit
<
Grape
::
Entity
class
Commit
<
Grape
::
Entity
expose
:id
,
:short_id
,
:title
,
:created_at
expose
:id
,
:short_id
,
:title
,
:created_at
expose
:parent_ids
expose
:parent_ids
...
@@ -604,6 +608,8 @@ module API
...
@@ -604,6 +608,8 @@ module API
merge_request
.
metrics
&
.
pipeline
merge_request
.
metrics
&
.
pipeline
end
end
expose
:diff_refs
,
using:
Entities
::
DiffRefs
def
build_available?
(
options
)
def
build_available?
(
options
)
options
[
:project
]
&
.
feature_available?
(
:builds
,
options
[
:current_user
])
options
[
:project
]
&
.
feature_available?
(
:builds
,
options
[
:current_user
])
end
end
...
@@ -671,6 +677,11 @@ module API
...
@@ -671,6 +677,11 @@ module API
expose
:id
,
:key
,
:created_at
expose
:id
,
:key
,
:created_at
end
end
class
DiffPosition
<
Grape
::
Entity
expose
:base_sha
,
:start_sha
,
:head_sha
,
:old_path
,
:new_path
,
:position_type
end
class
Note
<
Grape
::
Entity
class
Note
<
Grape
::
Entity
# Only Issue and MergeRequest have iid
# Only Issue and MergeRequest have iid
NOTEABLE_TYPES_WITH_IID
=
%w(Issue MergeRequest)
.
freeze
NOTEABLE_TYPES_WITH_IID
=
%w(Issue MergeRequest)
.
freeze
...
@@ -684,6 +695,14 @@ module API
...
@@ -684,6 +695,14 @@ module API
expose
:system?
,
as: :system
expose
:system?
,
as: :system
expose
:noteable_id
,
:noteable_type
expose
:noteable_id
,
:noteable_type
expose
:position
,
if:
->
(
note
,
options
)
{
note
.
diff_note?
}
do
|
note
|
note
.
position
.
to_h
end
expose
:resolvable?
,
as: :resolvable
expose
:resolved?
,
as: :resolved
,
if:
->
(
note
,
options
)
{
note
.
resolvable?
}
expose
:resolved_by
,
using:
Entities
::
UserBasic
,
if:
->
(
note
,
options
)
{
note
.
resolvable?
}
# Avoid N+1 queries as much as possible
# Avoid N+1 queries as much as possible
expose
(
:noteable_iid
)
{
|
note
|
note
.
noteable
.
iid
if
NOTEABLE_TYPES_WITH_IID
.
include?
(
note
.
noteable_type
)
}
expose
(
:noteable_iid
)
{
|
note
|
note
.
noteable
.
iid
if
NOTEABLE_TYPES_WITH_IID
.
include?
(
note
.
noteable_type
)
}
end
end
...
...
lib/api/helpers.rb
View file @
507430cd
...
@@ -184,6 +184,10 @@ module API
...
@@ -184,6 +184,10 @@ module API
MergeRequestsFinder
.
new
(
current_user
,
project_id:
user_project
.
id
).
find_by!
(
iid:
iid
)
MergeRequestsFinder
.
new
(
current_user
,
project_id:
user_project
.
id
).
find_by!
(
iid:
iid
)
end
end
def
find_project_commit
(
id
)
user_project
.
commit_by
(
oid:
id
)
end
def
find_project_snippet
(
id
)
def
find_project_snippet
(
id
)
finder_params
=
{
project:
user_project
}
finder_params
=
{
project:
user_project
}
SnippetsFinder
.
new
(
current_user
,
finder_params
).
find
(
id
)
SnippetsFinder
.
new
(
current_user
,
finder_params
).
find
(
id
)
...
...
lib/api/helpers/notes_helpers.rb
View file @
507430cd
...
@@ -23,6 +23,23 @@ module API
...
@@ -23,6 +23,23 @@ module API
end
end
end
end
def
resolve_note
(
noteable
,
note_id
,
resolved
)
note
=
noteable
.
notes
.
find
(
note_id
)
authorize!
:resolve_note
,
note
bad_request!
(
"Note is not resolvable"
)
unless
note
.
resolvable?
if
resolved
parent
=
noteable_parent
(
noteable
)
::
Notes
::
ResolveService
.
new
(
parent
,
current_user
).
execute
(
note
)
else
note
.
unresolve!
end
present
note
,
with:
Entities
::
Note
end
def
delete_note
(
noteable
,
note_id
)
def
delete_note
(
noteable
,
note_id
)
note
=
noteable
.
notes
.
find
(
note_id
)
note
=
noteable
.
notes
.
find
(
note_id
)
...
@@ -37,7 +54,7 @@ module API
...
@@ -37,7 +54,7 @@ module API
def
get_note
(
noteable
,
note_id
)
def
get_note
(
noteable
,
note_id
)
note
=
noteable
.
notes
.
with_metadata
.
find
(
params
[
:note_id
])
note
=
noteable
.
notes
.
with_metadata
.
find
(
params
[
:note_id
])
can_read_note
=
can?
(
current_user
,
noteable_read_ability_name
(
noteable
),
noteable
)
&&
!
note
.
cross_reference_not_visible_for?
(
current_user
)
can_read_note
=
!
note
.
cross_reference_not_visible_for?
(
current_user
)
if
can_read_note
if
can_read_note
present
note
,
with:
Entities
::
Note
present
note
,
with:
Entities
::
Note
...
@@ -51,7 +68,20 @@ module API
...
@@ -51,7 +68,20 @@ module API
end
end
def
find_noteable
(
parent
,
noteables_str
,
noteable_id
)
def
find_noteable
(
parent
,
noteables_str
,
noteable_id
)
public_send
(
"find_
#{
parent
}
_
#{
noteables_str
.
singularize
}
"
,
noteable_id
)
# rubocop:disable GitlabSecurity/PublicSend
noteable
=
public_send
(
"find_
#{
parent
}
_
#{
noteables_str
.
singularize
}
"
,
noteable_id
)
# rubocop:disable GitlabSecurity/PublicSend
readable
=
if
noteable
.
is_a?
(
Commit
)
# for commits there is not :read_commit policy, check if user
# has :read_note permission on the commit's project
can?
(
current_user
,
:read_note
,
user_project
)
else
can?
(
current_user
,
noteable_read_ability_name
(
noteable
),
noteable
)
end
return
not_found!
(
noteables_str
)
unless
readable
noteable
end
end
def
noteable_parent
(
noteable
)
def
noteable_parent
(
noteable
)
...
@@ -59,11 +89,8 @@ module API
...
@@ -59,11 +89,8 @@ module API
end
end
def
create_note
(
noteable
,
opts
)
def
create_note
(
noteable
,
opts
)
noteables_str
=
noteable
.
model_name
.
to_s
.
underscore
.
pluralize
policy_object
=
noteable
.
is_a?
(
Commit
)
?
user_project
:
noteable
authorize!
(
:create_note
,
policy_object
)
return
not_found!
(
noteables_str
)
unless
can?
(
current_user
,
noteable_read_ability_name
(
noteable
),
noteable
)
authorize!
:create_note
,
noteable
parent
=
noteable_parent
(
noteable
)
parent
=
noteable_parent
(
noteable
)
...
@@ -75,6 +102,21 @@ module API
...
@@ -75,6 +102,21 @@ module API
project
=
parent
if
parent
.
is_a?
(
Project
)
project
=
parent
if
parent
.
is_a?
(
Project
)
::
Notes
::
CreateService
.
new
(
project
,
current_user
,
opts
).
execute
::
Notes
::
CreateService
.
new
(
project
,
current_user
,
opts
).
execute
end
end
def
resolve_discussion
(
noteable
,
discussion_id
,
resolved
)
discussion
=
noteable
.
find_discussion
(
discussion_id
)
forbidden!
unless
discussion
.
can_resolve?
(
current_user
)
if
resolved
parent
=
noteable_parent
(
noteable
)
::
Discussions
::
ResolveService
.
new
(
parent
,
current_user
,
merge_request:
noteable
).
execute
(
discussion
)
else
discussion
.
unresolve!
end
present
discussion
,
with:
Entities
::
Discussion
end
end
end
end
end
end
end
lib/api/notes.rb
View file @
507430cd
...
@@ -31,7 +31,6 @@ module API
...
@@ -31,7 +31,6 @@ module API
get
":id/
#{
noteables_str
}
/:noteable_id/notes"
do
get
":id/
#{
noteables_str
}
/:noteable_id/notes"
do
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
noteable
=
find_noteable
(
parent_type
,
noteables_str
,
params
[
:noteable_id
])
if
can?
(
current_user
,
noteable_read_ability_name
(
noteable
),
noteable
)
# We exclude notes that are cross-references and that cannot be viewed
# We exclude notes that are cross-references and that cannot be viewed
# by the current user. By doing this exclusion at this level and not
# by the current user. By doing this exclusion at this level and not
# at the DB query level (which we cannot in that case), the current
# at the DB query level (which we cannot in that case), the current
...
@@ -45,9 +44,6 @@ module API
...
@@ -45,9 +44,6 @@ module API
paginate
(
raw_notes
)
paginate
(
raw_notes
)
.
reject
{
|
n
|
n
.
cross_reference_not_visible_for?
(
current_user
)
}
.
reject
{
|
n
|
n
.
cross_reference_not_visible_for?
(
current_user
)
}
present
notes
,
with:
Entities
::
Note
present
notes
,
with:
Entities
::
Note
else
not_found!
(
"Notes"
)
end
end
end
desc
"Get a single
#{
noteable_type
.
to_s
.
downcase
}
note"
do
desc
"Get a single
#{
noteable_type
.
to_s
.
downcase
}
note"
do
...
...
lib/gitlab/diff/position.rb
View file @
507430cd
...
@@ -12,6 +12,10 @@ module Gitlab
...
@@ -12,6 +12,10 @@ module Gitlab
:head_sha
,
:head_sha
,
:old_line
,
:old_line
,
:new_line
,
:new_line
,
:width
,
:height
,
:x
,
:y
,
:position_type
,
to: :formatter
:position_type
,
to: :formatter
# A position can belong to a text line or to an image coordinate
# A position can belong to a text line or to an image coordinate
...
...
spec/fixtures/api/schemas/public_api/v4/notes.json
View file @
507430cd
...
@@ -24,7 +24,10 @@
...
@@ -24,7 +24,10 @@
"system"
:
{
"type"
:
"boolean"
},
"system"
:
{
"type"
:
"boolean"
},
"noteable_id"
:
{
"type"
:
"integer"
},
"noteable_id"
:
{
"type"
:
"integer"
},
"noteable_iid"
:
{
"type"
:
"integer"
},
"noteable_iid"
:
{
"type"
:
"integer"
},
"noteable_type"
:
{
"type"
:
"string"
}
"noteable_type"
:
{
"type"
:
"string"
},
"resolved"
:
{
"type"
:
"boolean"
},
"resolvable"
:
{
"type"
:
"boolean"
},
"resolved_by"
:
{
"type"
:
[
"string"
,
"null"
]
}
},
},
"required"
:
[
"required"
:
[
"id"
,
"body"
,
"attachment"
,
"author"
,
"created_at"
,
"updated_at"
,
"id"
,
"body"
,
"attachment"
,
"author"
,
"created_at"
,
"updated_at"
,
...
...
spec/requests/api/discussions_spec.rb
View file @
507430cd
...
@@ -2,36 +2,36 @@ require 'spec_helper'
...
@@ -2,36 +2,36 @@ require 'spec_helper'
describe
API
::
Discussions
do
describe
API
::
Discussions
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:user
)
{
create
(
:user
)
}
let!
(
:project
)
{
create
(
:project
,
:public
,
namespace:
user
.
namespace
)
}
let!
(
:project
)
{
create
(
:project
,
:public
,
:repository
,
namespace:
user
.
namespace
)
}
let
(
:private_user
)
{
create
(
:user
)
}
let
(
:private_user
)
{
create
(
:user
)
}
before
do
before
do
project
.
add_
report
er
(
user
)
project
.
add_
develop
er
(
user
)
end
end
context
"when noteable is an Issue"
do
context
'when noteable is an Issue'
do
let!
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
user
)
}
let!
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
user
)
}
let!
(
:issue_note
)
{
create
(
:discussion_note_on_issue
,
noteable:
issue
,
project:
project
,
author:
user
)
}
let!
(
:issue_note
)
{
create
(
:discussion_note_on_issue
,
noteable:
issue
,
project:
project
,
author:
user
)
}
it_behaves_like
"discussions API"
,
'projects'
,
'issues'
,
'iid'
do
it_behaves_like
'discussions API'
,
'projects'
,
'issues'
,
'iid'
do
let
(
:parent
)
{
project
}
let
(
:parent
)
{
project
}
let
(
:noteable
)
{
issue
}
let
(
:noteable
)
{
issue
}
let
(
:note
)
{
issue_note
}
let
(
:note
)
{
issue_note
}
end
end
end
end
context
"when noteable is a Snippet"
do
context
'when noteable is a Snippet'
do
let!
(
:snippet
)
{
create
(
:project_snippet
,
project:
project
,
author:
user
)
}
let!
(
:snippet
)
{
create
(
:project_snippet
,
project:
project
,
author:
user
)
}
let!
(
:snippet_note
)
{
create
(
:discussion_note_on_snippet
,
noteable:
snippet
,
project:
project
,
author:
user
)
}
let!
(
:snippet_note
)
{
create
(
:discussion_note_on_snippet
,
noteable:
snippet
,
project:
project
,
author:
user
)
}
it_behaves_like
"discussions API"
,
'projects'
,
'snippets'
,
'id'
do
it_behaves_like
'discussions API'
,
'projects'
,
'snippets'
,
'id'
do
let
(
:parent
)
{
project
}
let
(
:parent
)
{
project
}
let
(
:noteable
)
{
snippet
}
let
(
:noteable
)
{
snippet
}
let
(
:note
)
{
snippet_note
}
let
(
:note
)
{
snippet_note
}
end
end
end
end
context
"when noteable is an Epic"
do
context
'when noteable is an Epic'
do
let
(
:group
)
{
create
(
:group
,
:public
)
}
let
(
:group
)
{
create
(
:group
,
:public
)
}
let
(
:ext_group
)
{
create
(
:group
,
:public
)
}
let
(
:ext_group
)
{
create
(
:group
,
:public
)
}
let
(
:epic
)
{
create
(
:epic
,
group:
group
,
author:
user
)
}
let
(
:epic
)
{
create
(
:epic
,
group:
group
,
author:
user
)
}
...
@@ -42,10 +42,31 @@ describe API::Discussions do
...
@@ -42,10 +42,31 @@ describe API::Discussions do
stub_licensed_features
(
epics:
true
)
stub_licensed_features
(
epics:
true
)
end
end
it_behaves_like
"discussions API"
,
'groups'
,
'epics'
,
'id'
do
it_behaves_like
'discussions API'
,
'groups'
,
'epics'
,
'id'
do
let
(
:parent
)
{
group
}
let
(
:parent
)
{
group
}
let
(
:noteable
)
{
epic
}
let
(
:noteable
)
{
epic
}
let
(
:note
)
{
epic_note
}
let
(
:note
)
{
epic_note
}
end
end
end
end
context
'when noteable is a Merge Request'
do
let!
(
:noteable
)
{
create
(
:merge_request_with_diffs
,
source_project:
project
,
target_project:
project
,
author:
user
)
}
let!
(
:note
)
{
create
(
:discussion_note_on_merge_request
,
noteable:
noteable
,
project:
project
,
author:
user
)
}
let!
(
:diff_note
)
{
create
(
:diff_note_on_merge_request
,
noteable:
noteable
,
project:
project
,
author:
user
)
}
let
(
:parent
)
{
project
}
it_behaves_like
'discussions API'
,
'projects'
,
'merge_requests'
,
'iid'
it_behaves_like
'diff discussions API'
,
'projects'
,
'merge_requests'
,
'iid'
it_behaves_like
'resolvable discussions API'
,
'projects'
,
'merge_requests'
,
'iid'
end
context
'when noteable is a Commit'
do
let!
(
:noteable
)
{
create
(
:commit
,
project:
project
,
author:
user
)
}
let!
(
:note
)
{
create
(
:discussion_note_on_commit
,
commit_id:
noteable
.
id
,
project:
project
,
author:
user
)
}
let!
(
:diff_note
)
{
create
(
:diff_note_on_commit
,
commit_id:
noteable
.
id
,
project:
project
,
author:
user
)
}
let
(
:parent
)
{
project
}
it_behaves_like
'discussions API'
,
'projects'
,
'repository/commits'
,
'id'
it_behaves_like
'diff discussions API'
,
'projects'
,
'repository/commits'
,
'id'
end
end
end
spec/services/notes/resolve_service_spec.rb
0 → 100644
View file @
507430cd
require
'spec_helper'
describe
Notes
::
ResolveService
do
let
(
:merge_request
)
{
create
(
:merge_request
)
}
let
(
:note
)
{
create
(
:diff_note_on_merge_request
,
noteable:
merge_request
,
project:
merge_request
.
project
)
}
let
(
:user
)
{
merge_request
.
author
}
describe
'#execute'
do
it
"resolves the note"
do
described_class
.
new
(
merge_request
.
project
,
user
).
execute
(
note
)
note
.
reload
expect
(
note
.
resolved?
).
to
be
true
expect
(
note
.
resolved_by
).
to
eq
(
user
)
end
it
"sends notifications if all discussions are resolved"
do
expect_any_instance_of
(
MergeRequests
::
ResolvedDiscussionNotificationService
).
to
receive
(
:execute
).
with
(
merge_request
)
described_class
.
new
(
merge_request
.
project
,
user
).
execute
(
note
)
end
end
end
spec/support/shared_examples/requests/api/diff_discussions.rb
0 → 100644
View file @
507430cd
shared_examples
'diff discussions API'
do
|
parent_type
,
noteable_type
,
id_name
|
describe
"GET /
#{
parent_type
}
/:id/
#{
noteable_type
}
/:noteable_id/discussions"
do
it
"includes diff discussions"
do
get
api
(
"/
#{
parent_type
}
/
#{
parent
.
id
}
/
#{
noteable_type
}
/
#{
noteable
[
id_name
]
}
/discussions"
,
user
)
discussion
=
json_response
.
find
{
|
record
|
record
[
'id'
]
==
diff_note
.
discussion_id
}
expect
(
response
).
to
have_gitlab_http_status
(
200
)
expect
(
discussion
).
not_to
be_nil
expect
(
discussion
[
'individual_note'
]).
to
eq
(
false
)
expect
(
discussion
[
'notes'
].
first
[
'body'
]).
to
eq
(
diff_note
.
note
)
end
end
describe
"GET /
#{
parent_type
}
/:id/
#{
noteable_type
}
/:noteable_id/discussions/:discussion_id"
do
it
"returns a discussion by id"
do
get
api
(
"/
#{
parent_type
}
/
#{
parent
.
id
}
/
#{
noteable_type
}
/
#{
noteable
[
id_name
]
}
/discussions/
#{
diff_note
.
discussion_id
}
"
,
user
)
expect
(
response
).
to
have_gitlab_http_status
(
200
)
expect
(
json_response
[
'id'
]).
to
eq
(
diff_note
.
discussion_id
)
expect
(
json_response
[
'notes'
].
first
[
'body'
]).
to
eq
(
diff_note
.
note
)
expect
(
json_response
[
'notes'
].
first
[
'position'
]).
to
eq
(
diff_note
.
position
.
to_h
.
stringify_keys
)
end
end
describe
"POST /
#{
parent_type
}
/:id/
#{
noteable_type
}
/:noteable_id/discussions"
do
it
"creates a new diff note"
do
position
=
diff_note
.
position
.
to_h
post
api
(
"/
#{
parent_type
}
/
#{
parent
.
id
}
/
#{
noteable_type
}
/
#{
noteable
[
id_name
]
}
/discussions"
,
user
),
body:
'hi!'
,
position:
position
expect
(
response
).
to
have_gitlab_http_status
(
201
)
expect
(
json_response
[
'notes'
].
first
[
'body'
]).
to
eq
(
'hi!'
)
expect
(
json_response
[
'notes'
].
first
[
'type'
]).
to
eq
(
'DiffNote'
)
expect
(
json_response
[
'notes'
].
first
[
'position'
]).
to
eq
(
position
.
stringify_keys
)
end
it
"returns a 400 bad request error when position is invalid"
do
position
=
diff_note
.
position
.
to_h
.
merge
(
new_line:
'100000'
)
post
api
(
"/
#{
parent_type
}
/
#{
parent
.
id
}
/
#{
noteable_type
}
/
#{
noteable
[
id_name
]
}
/discussions"
,
user
),
body:
'hi!'
,
position:
position
expect
(
response
).
to
have_gitlab_http_status
(
400
)
end
end
describe
"POST /
#{
parent_type
}
/:id/
#{
noteable_type
}
/:noteable_id/discussions/:discussion_id/notes"
do
it
'adds a new note to the diff discussion'
do
post
api
(
"/
#{
parent_type
}
/
#{
parent
.
id
}
/
#{
noteable_type
}
/
#{
noteable
[
id_name
]
}
/"
\
"discussions/
#{
diff_note
.
discussion_id
}
/notes"
,
user
),
body:
'hi!'
expect
(
response
).
to
have_gitlab_http_status
(
201
)
expect
(
json_response
[
'body'
]).
to
eq
(
'hi!'
)
expect
(
json_response
[
'type'
]).
to
eq
(
'DiffNote'
)
end
end
end
spec/support/shared_examples/requests/api/resolvable_discussions.rb
0 → 100644
View file @
507430cd
shared_examples
'resolvable discussions API'
do
|
parent_type
,
noteable_type
,
id_name
|
describe
"PUT /
#{
parent_type
}
/:id/
#{
noteable_type
}
/:noteable_id/discussions/:discussion_id"
do
it
"resolves discussion if resolved is true"
do
put
api
(
"/
#{
parent_type
}
/
#{
parent
.
id
}
/
#{
noteable_type
}
/
#{
noteable
[
id_name
]
}
/"
\
"discussions/
#{
note
.
discussion_id
}
"
,
user
),
resolved:
true
expect
(
response
).
to
have_gitlab_http_status
(
200
)
expect
(
json_response
[
'notes'
].
size
).
to
eq
(
1
)
expect
(
json_response
[
'notes'
][
0
][
'resolved'
]).
to
eq
(
true
)
end
it
"unresolves discussion if resolved is false"
do
put
api
(
"/
#{
parent_type
}
/
#{
parent
.
id
}
/
#{
noteable_type
}
/
#{
noteable
[
id_name
]
}
/"
\
"discussions/
#{
note
.
discussion_id
}
"
,
user
),
resolved:
false
expect
(
response
).
to
have_gitlab_http_status
(
200
)
expect
(
json_response
[
'notes'
].
size
).
to
eq
(
1
)
expect
(
json_response
[
'notes'
][
0
][
'resolved'
]).
to
eq
(
false
)
end
it
"returns a 400 bad request error if resolved parameter is not passed"
do
put
api
(
"/
#{
parent_type
}
/
#{
parent
.
id
}
/
#{
noteable_type
}
/
#{
noteable
[
id_name
]
}
/"
\
"discussions/
#{
note
.
discussion_id
}
"
,
user
)
expect
(
response
).
to
have_gitlab_http_status
(
400
)
end
it
"returns a 401 unauthorized error if user is not authenticated"
do
put
api
(
"/
#{
parent_type
}
/
#{
parent
.
id
}
/
#{
noteable_type
}
/
#{
noteable
[
id_name
]
}
/"
\
"discussions/
#{
note
.
discussion_id
}
"
),
resolved:
true
expect
(
response
).
to
have_gitlab_http_status
(
401
)
end
it
"returns a 403 error if user resolves discussion of someone else"
do
put
api
(
"/
#{
parent_type
}
/
#{
parent
.
id
}
/
#{
noteable_type
}
/
#{
noteable
[
id_name
]
}
/"
\
"discussions/
#{
note
.
discussion_id
}
"
,
private_user
),
resolved:
true
expect
(
response
).
to
have_gitlab_http_status
(
403
)
end
context
'when user does not have access to read the discussion'
do
before
do
parent
.
update!
(
visibility_level:
Gitlab
::
VisibilityLevel
::
PRIVATE
)
end
it
'responds with 404'
do
put
api
(
"/
#{
parent_type
}
/
#{
parent
.
id
}
/
#{
noteable_type
}
/
#{
noteable
[
id_name
]
}
/"
\
"discussions/
#{
note
.
discussion_id
}
"
,
private_user
),
resolved:
true
expect
(
response
).
to
have_gitlab_http_status
(
404
)
end
end
end
describe
"PUT /
#{
parent_type
}
/:id/
#{
noteable_type
}
/:noteable_id/discussions/:discussion_id/notes/:note_id"
do
it
'returns resolved note when resolved parameter is true'
do
put
api
(
"/
#{
parent_type
}
/
#{
parent
.
id
}
/
#{
noteable_type
}
/
#{
noteable
[
id_name
]
}
/"
\
"discussions/
#{
note
.
discussion_id
}
/notes/
#{
note
.
id
}
"
,
user
),
resolved:
true
expect
(
response
).
to
have_gitlab_http_status
(
200
)
expect
(
json_response
[
'resolved'
]).
to
eq
(
true
)
end
it
'returns a 404 error when note id not found'
do
put
api
(
"/
#{
parent_type
}
/
#{
parent
.
id
}
/
#{
noteable_type
}
/
#{
noteable
[
id_name
]
}
/"
\
"discussions/
#{
note
.
discussion_id
}
/notes/12345"
,
user
),
body:
'Hello!'
expect
(
response
).
to
have_gitlab_http_status
(
404
)
end
it
'returns a 400 bad request error if neither body nor resolved parameter is given'
do
put
api
(
"/
#{
parent_type
}
/
#{
parent
.
id
}
/
#{
noteable_type
}
/
#{
noteable
[
id_name
]
}
/"
\
"discussions/
#{
note
.
discussion_id
}
/notes/
#{
note
.
id
}
"
,
user
)
expect
(
response
).
to
have_gitlab_http_status
(
400
)
end
it
"returns a 403 error if user resolves note of someone else"
do
put
api
(
"/
#{
parent_type
}
/
#{
parent
.
id
}
/
#{
noteable_type
}
/
#{
noteable
[
id_name
]
}
/"
\
"discussions/
#{
note
.
discussion_id
}
/notes/
#{
note
.
id
}
"
,
private_user
),
resolved:
true
expect
(
response
).
to
have_gitlab_http_status
(
403
)
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