From 073ba05d315881730de3995042cc4256c116e2c4 Mon Sep 17 00:00:00 2001
From: Jarka Kadlecova <jarka@gitlab.com>
Date: Thu, 31 Aug 2017 12:38:32 +0200
Subject: [PATCH] Support discussion lock in the API

---
 doc/api/issues.md                             | 57 +++++++++++++++++--
 doc/api/merge_requests.md                     | 11 +++-
 lib/api/entities.rb                           |  2 +
 lib/api/issues.rb                             |  3 +-
 lib/api/merge_requests.rb                     |  4 +-
 lib/api/notes.rb                              |  7 +++
 .../api/schemas/public_api/v4/issues.json     |  1 +
 .../schemas/public_api/v4/merge_requests.json |  1 +
 8 files changed, 79 insertions(+), 7 deletions(-)

diff --git a/doc/api/issues.md b/doc/api/issues.md
index 8ca66049d31..61e42345153 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -109,7 +109,8 @@ Example response:
          "human_time_estimate": null,
          "human_total_time_spent": null
       },
-      "confidential": false
+      "confidential": false,
+      "discussion_locked": false
    }
 ]
 ```
@@ -214,7 +215,8 @@ Example response:
          "human_time_estimate": null,
          "human_total_time_spent": null
       },
-      "confidential": false
+      "confidential": false,
+      "discussion_locked": false
    }
 ]
 ```
@@ -320,7 +322,8 @@ Example response:
          "human_time_estimate": null,
          "human_total_time_spent": null
       },
-      "confidential": false
+      "confidential": false,
+      "discussion_locked": false
    }
 ]
 ```
@@ -403,6 +406,7 @@ Example response:
       "human_total_time_spent": null
    },
    "confidential": false,
+   "discussion_locked": false,
    "_links": {
       "self": "http://example.com/api/v4/projects/1/issues/2",
       "notes": "http://example.com/api/v4/projects/1/issues/2/notes",
@@ -477,6 +481,7 @@ Example response:
       "human_total_time_spent": null
    },
    "confidential": false,
+   "discussion_locked": false,
    "_links": {
       "self": "http://example.com/api/v4/projects/1/issues/2",
       "notes": "http://example.com/api/v4/projects/1/issues/2/notes",
@@ -510,6 +515,8 @@ PUT /projects/:id/issues/:issue_iid
 | `state_event`  | string  | no       | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it                      |
 | `updated_at`   | string  | no       | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) |
 | `due_date`     | string  | no       | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11`                                           |
+| `discussion_locked` | boolean | no  | Updates an issue to lock or unlock its discussion |
+
 
 ```bash
 curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues/85?state_event=close
@@ -552,6 +559,7 @@ Example response:
       "human_total_time_spent": null
    },
    "confidential": false,
+   "discussion_locked": false,
    "_links": {
       "self": "http://example.com/api/v4/projects/1/issues/2",
       "notes": "http://example.com/api/v4/projects/1/issues/2/notes",
@@ -650,6 +658,7 @@ Example response:
     "human_total_time_spent": null
   },
   "confidential": false,
+  "discussion_locked": false,
   "_links": {
     "self": "http://example.com/api/v4/projects/1/issues/2",
     "notes": "http://example.com/api/v4/projects/1/issues/2/notes",
@@ -727,6 +736,7 @@ Example response:
     "human_total_time_spent": null
   },
   "confidential": false,
+  "discussion_locked": false,
   "_links": {
     "self": "http://example.com/api/v4/projects/1/issues/2",
     "notes": "http://example.com/api/v4/projects/1/issues/2/notes",
@@ -757,6 +767,44 @@ POST /projects/:id/issues/:issue_iid/unsubscribe
 curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/unsubscribe
 ```
 
+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/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": "https://gitlab.example.com/orville"
+  },
+  "subscribed": false,
+  "due_date": null,
+  "web_url": "http://example.com/example/example/issues/12",
+  "confidential": false,
+  "discussion_locked": false
+}
+```
+
 ## Create a todo
 
 Manually creates a todo for the current user on an issue. If
@@ -849,7 +897,8 @@ Example response:
     "downvotes": 0,
     "due_date": null,
     "web_url": "http://example.com/example/example/issues/110",
-    "confidential": false
+    "confidential": false,
+    "discussion_locked": false
   },
   "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/issues/10",
   "body": "Vel voluptas atque dicta mollitia adipisci qui at.",
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index bff8a2d3e4d..e5d1ebb9cfb 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -192,6 +192,7 @@ Parameters:
     "should_remove_source_branch": true,
     "force_remove_source_branch": false,
     "web_url": "http://example.com/example/example/merge_requests/1",
+    "discussion_locked": false,
     "time_stats": {
       "time_estimate": 0,
       "total_time_spent": 0,
@@ -267,6 +268,7 @@ Parameters:
   "should_remove_source_branch": true,
   "force_remove_source_branch": false,
   "web_url": "http://example.com/example/example/merge_requests/1",
+  "discussion_locked": false,
   "time_stats": {
     "time_estimate": 0,
     "total_time_spent": 0,
@@ -378,6 +380,7 @@ Parameters:
   "should_remove_source_branch": true,
   "force_remove_source_branch": false,
   "web_url": "http://example.com/example/example/merge_requests/1",
+  "discussion_locked": false,
   "time_stats": {
     "time_estimate": 0,
     "total_time_spent": 0,
@@ -471,6 +474,7 @@ POST /projects/:id/merge_requests
   "should_remove_source_branch": true,
   "force_remove_source_branch": false,
   "web_url": "http://example.com/example/example/merge_requests/1",
+  "discussion_locked": false,
   "time_stats": {
     "time_estimate": 0,
     "total_time_spent": 0,
@@ -500,6 +504,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid
 | `labels`               | string  | no       | Labels for MR as a comma-separated list                                         |
 | `milestone_id`         | integer | no       | The ID of a milestone                                                           |
 | `remove_source_branch` | boolean | no       | Flag indicating if a merge request should remove the source branch when merging |
+| `discussion_locked` | boolean | no | Flag indicating if the merge request's discussion is locked |
 
 Must include at least one non-required attribute from above.
 
@@ -554,6 +559,7 @@ Must include at least one non-required attribute from above.
   "should_remove_source_branch": true,
   "force_remove_source_branch": false,
   "web_url": "http://example.com/example/example/merge_requests/1",
+  "discussion_locked": false,
   "time_stats": {
     "time_estimate": 0,
     "total_time_spent": 0,
@@ -658,6 +664,7 @@ Parameters:
   "should_remove_source_branch": true,
   "force_remove_source_branch": false,
   "web_url": "http://example.com/example/example/merge_requests/1",
+  "discussion_locked": false,
   "time_stats": {
     "time_estimate": 0,
     "total_time_spent": 0,
@@ -734,6 +741,7 @@ Parameters:
   "should_remove_source_branch": true,
   "force_remove_source_branch": false,
   "web_url": "http://example.com/example/example/merge_requests/1",
+  "discussion_locked": false,
   "time_stats": {
     "time_estimate": 0,
     "total_time_spent": 0,
@@ -1028,7 +1036,8 @@ Example response:
       "id": 14,
       "state": "active",
       "avatar_url": "http://www.gravatar.com/avatar/a7fa515d53450023c83d62986d0658a8?s=80&d=identicon",
-      "web_url": "https://gitlab.example.com/francisca"
+      "web_url": "https://gitlab.example.com/francisca",
+      "discussion_locked": false
     },
     "assignee": {
       "name": "Dr. Gabrielle Strosin",
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 52c49e5caa9..4b2ac1cce95 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -362,6 +362,7 @@ module API
       end
       expose :due_date
       expose :confidential
+      expose :discussion_locked
 
       expose :web_url do |issue, options|
         Gitlab::UrlBuilder.build(issue)
@@ -458,6 +459,7 @@ module API
       expose :diff_head_sha, as: :sha
       expose :merge_commit_sha
       expose :user_notes_count
+      expose :discussion_locked
       expose :should_remove_source_branch?, as: :should_remove_source_branch
       expose :force_remove_source_branch?, as: :force_remove_source_branch
 
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 1729df2aad0..88b592083db 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -48,6 +48,7 @@ module API
         optional :labels, type: String, desc: 'Comma-separated list of label names'
         optional :due_date, type: String, desc: 'Date string in the format YEAR-MONTH-DAY'
         optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential'
+        optional :discussion_locked, type: Boolean, desc: "Boolean parameter if the issue's discussion should be locked"
       end
 
       params :issue_params do
@@ -193,7 +194,7 @@ module API
                               desc: 'Date time when the issue was updated. Available only for admins and project owners.'
         optional :state_event, type: String, values: %w[reopen close], desc: 'State of the issue'
         use :issue_params
-        at_least_one_of :title, :description, :assignee_ids, :assignee_id, :milestone_id,
+        at_least_one_of :title, :description, :assignee_ids, :assignee_id, :milestone_id, :discussion_locked,
                         :labels, :created_at, :due_date, :confidential, :state_event
       end
       put ':id/issues/:issue_iid' do
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 56d72d511da..35395647fac 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -213,12 +213,14 @@ module API
           :remove_source_branch,
           :state_event,
           :target_branch,
-          :title
+          :title,
+          :discussion_locked
         ]
         optional :title, type: String, allow_blank: false, desc: 'The title of the merge request'
         optional :target_branch, type: String, allow_blank: false, desc: 'The target branch'
         optional :state_event, type: String, values: %w[close reopen],
                                desc: 'Status of the merge request'
+        optional :discussion_locked, type: Boolean, desc: 'Whether the MR discussion is locked'
 
         use :optional_params
         at_least_one_of(*at_least_one_of_ce)
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index d6e7203adaf..b3db366d875 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -71,6 +71,8 @@ module API
         post ":id/#{noteables_str}/:noteable_id/notes" do
           noteable = find_project_noteable(noteables_str, params[:noteable_id])
 
+          authorize! :create_note, user_project
+
           opts = {
             note: params[:body],
             noteable_type: noteables_str.classify,
@@ -82,6 +84,11 @@ module API
               opts[:created_at] = params[:created_at]
             end
 
+            noteable_type = opts[:noteable_type].to_s
+            noteable = Issue.find(opts[:noteable_id]) if noteable_type == 'Issue'
+            noteable = MergeRequest.find(opts[:noteable_id]) if noteable_type == 'MergeRequest'
+            authorize! :create_note, noteable if noteable
+
             note = ::Notes::CreateService.new(user_project, current_user, opts).execute
 
             if note.valid?
diff --git a/spec/fixtures/api/schemas/public_api/v4/issues.json b/spec/fixtures/api/schemas/public_api/v4/issues.json
index 8acd9488215..8c854a43fc6 100644
--- a/spec/fixtures/api/schemas/public_api/v4/issues.json
+++ b/spec/fixtures/api/schemas/public_api/v4/issues.json
@@ -9,6 +9,7 @@
       "title": { "type": "string" },
       "description": { "type": ["string", "null"] },
       "state": { "type": "string" },
+      "discussion_locked": { "type": "boolean" },
       "created_at": { "type": "date" },
       "updated_at": { "type": "date" },
       "labels": {
diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
index 31b3f4ba946..3b42333bb10 100644
--- a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
+++ b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
@@ -72,6 +72,7 @@
       "user_notes_count": { "type": "integer" },
       "should_remove_source_branch": { "type": ["boolean", "null"] },
       "force_remove_source_branch": { "type": ["boolean", "null"] },
+      "discussion_locked": { "type": "boolean" },
       "web_url": { "type": "uri" },
       "time_stats": {
         "time_estimate": { "type": "integer" },
-- 
2.30.9