summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRémy Coutable <remy@rymai.me>2016-04-13 15:54:31 +0000
committerRémy Coutable <remy@rymai.me>2016-04-13 15:54:31 +0000
commitc8560894f7242d53f8fb63c32d5bcef4ab4a6b8a (patch)
treedf6e83bfb0692e9755e57bd6730ffb92165b02ce
parent3463ffde9e3ef425fbaa6093f01212cccb01707b (diff)
parenta9200d93d3e3d586302887fcaa0cf8f5fbd9a613 (diff)
downloadgitlab-ce-c8560894f7242d53f8fb63c32d5bcef4ab4a6b8a.tar.gz
Merge branch 'api-subscribe-issue-mr' into 'master'
API: Subscribe and unsubscribe from issues and merge requests * Closes #6024 This MR is based on !3611, which must be merged first. See merge request !3615
-rw-r--r--CHANGELOG1
-rw-r--r--doc/api/issues.md109
-rw-r--r--doc/api/merge_requests.md148
-rw-r--r--lib/api/issues.rb36
-rw-r--r--lib/api/merge_requests.rb36
-rw-r--r--spec/requests/api/issues_spec.rb43
-rw-r--r--spec/requests/api/merge_requests_spec.rb42
7 files changed, 415 insertions, 0 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 41b6e2c67fd..6d19887e442 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -12,6 +12,7 @@ v 8.7.0 (unreleased)
- Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524
- Improved Markdown rendering performance !3389 (Yorick Peterse)
- Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu)
+ - API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling)
- Expose project badges in project settings
- Preserve time notes/comments have been updated at when moving issue
- Make HTTP(s) label consistent on clone bar (Stan Hu)
diff --git a/doc/api/issues.md b/doc/api/issues.md
index f09847aef95..42024becc36 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -406,6 +406,115 @@ Example response:
}
```
+## Subscribe to an issue
+
+Subscribes the authenticated user to an issue to receive notifications. If the
+operation is successful, status code `201` together with the updated issue is
+returned. If the user is already subscribed to the issue, the status code `304`
+is returned. If the project or issue is not found, status code `404` is
+returned.
+
+```
+POST /projects/:id/issues/:issue_id/subscription
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_id` | integer | yes | The ID of a project's issue |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscription
+```
+
+Example response:
+
+```json
+{
+ "id": 92,
+ "iid": 11,
+ "project_id": 5,
+ "title": "Sit voluptas tempora quisquam aut doloribus et.",
+ "description": "Repellat voluptas quibusdam voluptatem exercitationem.",
+ "state": "opened",
+ "created_at": "2016-04-05T21:41:45.652Z",
+ "updated_at": "2016-04-07T12:20:17.596Z",
+ "labels": [],
+ "milestone": null,
+ "assignee": {
+ "name": "Miss Monserrate Beier",
+ "username": "axel.block",
+ "id": 12,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/u/axel.block"
+ },
+ "author": {
+ "name": "Kris Steuber",
+ "username": "solon.cremin",
+ "id": 10,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/u/solon.cremin"
+ }
+}
+```
+
+## Unsubscribe from an issue
+
+Unsubscribes the authenticated user from the issue to not receive notifications
+from it. If the operation is successful, status code `200` together with the
+updated issue is returned. If the user is not subscribed to the issue, the
+status code `304` is returned. If the project or issue is not found, status code
+`404` is returned.
+
+```
+DELETE /projects/:id/issues/:issue_id/subscription
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `issue_id` | integer | yes | The ID of a project's issue |
+
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscription
+```
+
+Example response:
+
+```json
+{
+ "id": 93,
+ "iid": 12,
+ "project_id": 5,
+ "title": "Incidunt et rerum ea expedita iure quibusdam.",
+ "description": "Et cumque architecto sed aut ipsam.",
+ "state": "opened",
+ "created_at": "2016-04-05T21:41:45.217Z",
+ "updated_at": "2016-04-07T13:02:37.905Z",
+ "labels": [],
+ "milestone": null,
+ "assignee": {
+ "name": "Edwardo Grady",
+ "username": "keyon",
+ "id": 21,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/3e6f06a86cf27fa8b56f3f74f7615987?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/u/keyon"
+ },
+ "author": {
+ "name": "Vivian Hermann",
+ "username": "orville",
+ "id": 11,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon",
+ "web_url": "http://lgitlab.example.com/u/orville"
+ },
+ "subscribed": false
+}
+```
+
## Comments on issues
Comments are done via the [notes](notes.md) resource.
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 20db73ea6c0..2057f9d77aa 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -606,3 +606,151 @@ Example response:
},
]
```
+
+## Subscribe to a merge request
+
+Subscribes the authenticated user to a merge request to receive notification. If
+the operation is successful, status code `201` together with the updated merge
+request is returned. If the user is already subscribed to the merge request, the
+status code `304` is returned. If the project or merge request is not found,
+status code `404` is returned.
+
+```
+POST /projects/:id/merge_requests/:merge_request_id/subscription
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_id` | integer | yes | The ID of the merge request |
+
+```bash
+curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscription
+```
+
+Example response:
+
+```json
+{
+ "id": 17,
+ "iid": 1,
+ "project_id": 5,
+ "title": "Et et sequi est impedit nulla ut rem et voluptatem.",
+ "description": "Consequatur velit eos rerum optio autem. Quia id officia quaerat dolorum optio. Illo laudantium aut ipsum dolorem.",
+ "state": "opened",
+ "created_at": "2016-04-05T21:42:23.233Z",
+ "updated_at": "2016-04-05T22:11:52.900Z",
+ "target_branch": "ui-dev-kit",
+ "source_branch": "version-1-9",
+ "upvotes": 0,
+ "downvotes": 0,
+ "author": {
+ "name": "Eileen Skiles",
+ "username": "leila",
+ "id": 19,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/u/leila"
+ },
+ "assignee": {
+ "name": "Celine Wehner",
+ "username": "carli",
+ "id": 16,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/u/carli"
+ },
+ "source_project_id": 5,
+ "target_project_id": 5,
+ "labels": [],
+ "work_in_progress": false,
+ "milestone": {
+ "id": 7,
+ "iid": 1,
+ "project_id": 5,
+ "title": "v2.0",
+ "description": "Corrupti eveniet et velit occaecati dolorem est rerum aut.",
+ "state": "closed",
+ "created_at": "2016-04-05T21:41:40.905Z",
+ "updated_at": "2016-04-05T21:41:40.905Z",
+ "due_date": null
+ },
+ "merge_when_build_succeeds": false,
+ "merge_status": "cannot_be_merged",
+ "subscribed": true
+}
+```
+
+## Unsubscribe from a merge request
+
+Unsubscribes the authenticated user from a merge request to not receive
+notifications from that merge request. If the operation is successful, status
+code `200` together with the updated merge request is returned. If the user is
+not subscribed to the merge request, the status code `304` is returned. If the
+project or merge request is not found, status code `404` is returned.
+
+```
+DELETE /projects/:id/merge_requests/:merge_request_id/subscription
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a project |
+| `merge_request_id` | integer | yes | The ID of the merge request |
+
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscription
+```
+
+Example response:
+
+```json
+{
+ "id": 17,
+ "iid": 1,
+ "project_id": 5,
+ "title": "Et et sequi est impedit nulla ut rem et voluptatem.",
+ "description": "Consequatur velit eos rerum optio autem. Quia id officia quaerat dolorum optio. Illo laudantium aut ipsum dolorem.",
+ "state": "opened",
+ "created_at": "2016-04-05T21:42:23.233Z",
+ "updated_at": "2016-04-05T22:11:52.900Z",
+ "target_branch": "ui-dev-kit",
+ "source_branch": "version-1-9",
+ "upvotes": 0,
+ "downvotes": 0,
+ "author": {
+ "name": "Eileen Skiles",
+ "username": "leila",
+ "id": 19,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/u/leila"
+ },
+ "assignee": {
+ "name": "Celine Wehner",
+ "username": "carli",
+ "id": 16,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/u/carli"
+ },
+ "source_project_id": 5,
+ "target_project_id": 5,
+ "labels": [],
+ "work_in_progress": false,
+ "milestone": {
+ "id": 7,
+ "iid": 1,
+ "project_id": 5,
+ "title": "v2.0",
+ "description": "Corrupti eveniet et velit occaecati dolorem est rerum aut.",
+ "state": "closed",
+ "created_at": "2016-04-05T21:41:40.905Z",
+ "updated_at": "2016-04-05T21:41:40.905Z",
+ "due_date": null
+ },
+ "merge_when_build_succeeds": false,
+ "merge_status": "cannot_be_merged",
+ "subscribed": false
+}
+```
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 850e99981ff..4cdecadfe0f 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -231,6 +231,42 @@ module API
authorize!(:destroy_issue, issue)
issue.destroy
end
+
+ # Subscribes to a project issue
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # issue_id (required) - The ID of a project issue
+ # Example Request:
+ # POST /projects/:id/issues/:issue_id/subscription
+ post ':id/issues/:issue_id/subscription' do
+ issue = user_project.issues.find(params[:issue_id])
+
+ if issue.subscribed?(current_user)
+ not_modified!
+ else
+ issue.toggle_subscription(current_user)
+ present issue, with: Entities::Issue, current_user: current_user
+ end
+ end
+
+ # Unsubscribes from a project issue
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # issue_id (required) - The ID of a project issue
+ # Example Request:
+ # DELETE /projects/:id/issues/:issue_id/subscription
+ delete ':id/issues/:issue_id/subscription' do
+ issue = user_project.issues.find(params[:issue_id])
+
+ if issue.subscribed?(current_user)
+ issue.unsubscribe(current_user)
+ present issue, with: Entities::Issue, current_user: current_user
+ else
+ not_modified!
+ end
+ end
end
end
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 4e7de8867b4..7e78609ecb9 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -327,6 +327,42 @@ module API
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
present paginate(issues), with: Entities::Issue, current_user: current_user
end
+
+ # Subscribes to a merge request
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - The ID of a merge request
+ # Example Request:
+ # POST /projects/:id/issues/:merge_request_id/subscription
+ post "#{path}/subscription" do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+ if merge_request.subscribed?(current_user)
+ not_modified!
+ else
+ merge_request.toggle_subscription(current_user)
+ present merge_request, with: Entities::MergeRequest, current_user: current_user
+ end
+ end
+
+ # Unsubscribes from a merge request
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - The ID of a merge request
+ # Example Request:
+ # DELETE /projects/:id/merge_requests/:merge_request_id/subscription
+ delete "#{path}/subscription" do
+ merge_request = user_project.merge_requests.find(params[:merge_request_id])
+
+ if merge_request.subscribed?(current_user)
+ merge_request.unsubscribe(current_user)
+ present merge_request, with: Entities::MergeRequest, current_user: current_user
+ else
+ not_modified!
+ end
+ end
end
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 3d7a31cbb6a..86ea223f206 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
+ let(:user2) { create(:user) }
let(:non_member) { create(:user) }
let(:author) { create(:author) }
let(:assignee) { create(:assignee) }
@@ -569,4 +570,46 @@ describe API::API, api: true do
end
end
end
+
+ describe 'POST :id/issues/:issue_id/subscription' do
+ it 'subscribes to an issue' do
+ post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2)
+
+ expect(response.status).to eq(201)
+ expect(json_response['subscribed']).to eq(true)
+ end
+
+ it 'returns 304 if already subscribed' do
+ post api("/projects/#{project.id}/issues/#{issue.id}/subscription", user)
+
+ expect(response.status).to eq(304)
+ end
+
+ it 'returns 404 if the issue is not found' do
+ post api("/projects/#{project.id}/issues/123/subscription", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ describe 'DELETE :id/issues/:issue_id/subscription' do
+ it 'unsubscribes from an issue' do
+ delete api("/projects/#{project.id}/issues/#{issue.id}/subscription", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response['subscribed']).to eq(false)
+ end
+
+ it 'returns 304 if not subscribed' do
+ delete api("/projects/#{project.id}/issues/#{issue.id}/subscription", user2)
+
+ expect(response.status).to eq(304)
+ end
+
+ it 'returns 404 if the issue is not found' do
+ delete api("/projects/#{project.id}/issues/123/subscription", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 25fa30b2f21..1fa7e76894f 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -516,6 +516,48 @@ describe API::API, api: true do
end
end
+ describe 'POST :id/merge_requests/:merge_request_id/subscription' do
+ it 'subscribes to a merge request' do
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin)
+
+ expect(response.status).to eq(201)
+ expect(json_response['subscribed']).to eq(true)
+ end
+
+ it 'returns 304 if already subscribed' do
+ post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user)
+
+ expect(response.status).to eq(304)
+ end
+
+ it 'returns 404 if the merge request is not found' do
+ post api("/projects/#{project.id}/merge_requests/123/subscription", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ describe 'DELETE :id/merge_requests/:merge_request_id/subscription' do
+ it 'unsubscribes from a merge request' do
+ delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response['subscribed']).to eq(false)
+ end
+
+ it 'returns 304 if not subscribed' do
+ delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscription", admin)
+
+ expect(response.status).to eq(304)
+ end
+
+ it 'returns 404 if the merge request is not found' do
+ post api("/projects/#{project.id}/merge_requests/123/subscription", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
def mr_with_later_created_and_updated_at_time
merge_request
merge_request.created_at += 1.hour