Commit 1ba5988a authored by Robert Speicher's avatar Robert Speicher

Merge branch 'api-confidential-issues' into 'master'

API: Expose issue confidentiality flag.

## What does this MR do?

This MR adds support for confidential issues the the API. The property as exposed and users can create or update confidential issues.

## What are the relevant issue numbers?

Closes #17199 

See merge request !5384
parents 732f1e29 036cc8c2
...@@ -15,6 +15,7 @@ v 8.12.0 (unreleased) ...@@ -15,6 +15,7 @@ v 8.12.0 (unreleased)
- Add hover color to emoji icon (ClemMakesApps) - Add hover color to emoji icon (ClemMakesApps)
- Fix branches page dropdown sort alignment (ClemMakesApps) - Fix branches page dropdown sort alignment (ClemMakesApps)
- Add white background for no readme container (ClemMakesApps) - Add white background for no readme container (ClemMakesApps)
- API: Expose issue confidentiality flag. (Robert Schilling)
- Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
- Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Add `wiki_page_events` to project hook APIs (Ben Boeckel)
- Remove Gitorious import - Remove Gitorious import
......
...@@ -80,7 +80,8 @@ Example response: ...@@ -80,7 +80,8 @@ Example response:
"subscribed" : false, "subscribed" : false,
"user_notes_count": 1, "user_notes_count": 1,
"due_date": "2016-07-22", "due_date": "2016-07-22",
"web_url": "http://example.com/example/example/issues/6" "web_url": "http://example.com/example/example/issues/6",
"confidential": false
} }
] ]
``` ```
...@@ -158,7 +159,8 @@ Example response: ...@@ -158,7 +159,8 @@ Example response:
"subscribed" : false, "subscribed" : false,
"user_notes_count": 1, "user_notes_count": 1,
"due_date": null, "due_date": null,
"web_url": "http://example.com/example/example/issues/1" "web_url": "http://example.com/example/example/issues/1",
"confidential": false
} }
] ]
``` ```
...@@ -238,7 +240,8 @@ Example response: ...@@ -238,7 +240,8 @@ Example response:
"subscribed" : false, "subscribed" : false,
"user_notes_count": 1, "user_notes_count": 1,
"due_date": "2016-07-22", "due_date": "2016-07-22",
"web_url": "http://example.com/example/example/issues/1" "web_url": "http://example.com/example/example/issues/1",
"confidential": false
} }
] ]
``` ```
...@@ -303,7 +306,8 @@ Example response: ...@@ -303,7 +306,8 @@ Example response:
"subscribed": false, "subscribed": false,
"user_notes_count": 1, "user_notes_count": 1,
"due_date": null, "due_date": null,
"web_url": "http://example.com/example/example/issues/1" "web_url": "http://example.com/example/example/issues/1",
"confidential": false
} }
``` ```
...@@ -324,6 +328,7 @@ POST /projects/:id/issues ...@@ -324,6 +328,7 @@ POST /projects/:id/issues
| `id` | integer | yes | The ID of a project | | `id` | integer | yes | The ID of a project |
| `title` | string | yes | The title of an issue | | `title` | string | yes | The title of an issue |
| `description` | string | no | The description of an issue | | `description` | string | no | The description of an issue |
| `confidential` | boolean | no | Set an issue to be confidential. Default is `false`. |
| `assignee_id` | integer | no | The ID of a user to assign issue | | `assignee_id` | integer | no | The ID of a user to assign issue |
| `milestone_id` | integer | no | The ID of a milestone to assign issue | | `milestone_id` | integer | no | The ID of a milestone to assign issue |
| `labels` | string | no | Comma-separated label names for an issue | | `labels` | string | no | Comma-separated label names for an issue |
...@@ -362,7 +367,8 @@ Example response: ...@@ -362,7 +367,8 @@ Example response:
"subscribed" : true, "subscribed" : true,
"user_notes_count": 0, "user_notes_count": 0,
"due_date": null, "due_date": null,
"web_url": "http://example.com/example/example/issues/14" "web_url": "http://example.com/example/example/issues/14",
"confidential": false
} }
``` ```
...@@ -385,6 +391,7 @@ PUT /projects/:id/issues/:issue_id ...@@ -385,6 +391,7 @@ PUT /projects/:id/issues/:issue_id
| `issue_id` | integer | yes | The ID of a project's issue | | `issue_id` | integer | yes | The ID of a project's issue |
| `title` | string | no | The title of an issue | | `title` | string | no | The title of an issue |
| `description` | string | no | The description of an issue | | `description` | string | no | The description of an issue |
| `confidential` | boolean | no | Updates an issue to be confidential |
| `assignee_id` | integer | no | The ID of a user to assign the issue to | | `assignee_id` | integer | no | The ID of a user to assign the issue to |
| `milestone_id` | integer | no | The ID of a milestone to assign the issue to | | `milestone_id` | integer | no | The ID of a milestone to assign the issue to |
| `labels` | string | no | Comma-separated label names for an issue | | `labels` | string | no | Comma-separated label names for an issue |
...@@ -424,7 +431,8 @@ Example response: ...@@ -424,7 +431,8 @@ Example response:
"subscribed" : true, "subscribed" : true,
"user_notes_count": 0, "user_notes_count": 0,
"due_date": "2016-07-22", "due_date": "2016-07-22",
"web_url": "http://example.com/example/example/issues/15" "web_url": "http://example.com/example/example/issues/15",
"confidential": false
} }
``` ```
...@@ -503,7 +511,8 @@ Example response: ...@@ -503,7 +511,8 @@ Example response:
"web_url": "https://gitlab.example.com/u/solon.cremin" "web_url": "https://gitlab.example.com/u/solon.cremin"
}, },
"due_date": null, "due_date": null,
"web_url": "http://example.com/example/example/issues/11" "web_url": "http://example.com/example/example/issues/11",
"confidential": false
} }
``` ```
...@@ -559,7 +568,8 @@ Example response: ...@@ -559,7 +568,8 @@ Example response:
"web_url": "https://gitlab.example.com/u/solon.cremin" "web_url": "https://gitlab.example.com/u/solon.cremin"
}, },
"due_date": null, "due_date": null,
"web_url": "http://example.com/example/example/issues/11" "web_url": "http://example.com/example/example/issues/11",
"confidential": false
} }
``` ```
...@@ -616,7 +626,8 @@ Example response: ...@@ -616,7 +626,8 @@ Example response:
}, },
"subscribed": false, "subscribed": false,
"due_date": null, "due_date": null,
"web_url": "http://example.com/example/example/issues/12" "web_url": "http://example.com/example/example/issues/12",
"confidential": false
} }
``` ```
...@@ -704,7 +715,8 @@ Example response: ...@@ -704,7 +715,8 @@ Example response:
"upvotes": 0, "upvotes": 0,
"downvotes": 0, "downvotes": 0,
"due_date": null, "due_date": null,
"web_url": "http://example.com/example/example/issues/110" "web_url": "http://example.com/example/example/issues/110",
"confidential": false
}, },
"target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/issues/10", "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/issues/10",
"body": "Vel voluptas atque dicta mollitia adipisci qui at.", "body": "Vel voluptas atque dicta mollitia adipisci qui at.",
......
...@@ -211,6 +211,7 @@ module API ...@@ -211,6 +211,7 @@ module API
expose :user_notes_count expose :user_notes_count
expose :upvotes, :downvotes expose :upvotes, :downvotes
expose :due_date expose :due_date
expose :confidential
expose :web_url do |issue, options| expose :web_url do |issue, options|
Gitlab::UrlBuilder.build(issue) Gitlab::UrlBuilder.build(issue)
......
...@@ -140,12 +140,13 @@ module API ...@@ -140,12 +140,13 @@ module API
# labels (optional) - The labels of an issue # labels (optional) - The labels of an issue
# created_at (optional) - Date time string, ISO 8601 formatted # created_at (optional) - Date time string, ISO 8601 formatted
# due_date (optional) - Date time string in the format YEAR-MONTH-DAY # due_date (optional) - Date time string in the format YEAR-MONTH-DAY
# confidential (optional) - Boolean parameter if the issue should be confidential
# Example Request: # Example Request:
# POST /projects/:id/issues # POST /projects/:id/issues
post ':id/issues' do post ':id/issues' do
required_attributes! [:title] required_attributes! [:title]
keys = [:title, :description, :assignee_id, :milestone_id, :due_date] keys = [:title, :description, :assignee_id, :milestone_id, :due_date, :confidential]
keys << :created_at if current_user.admin? || user_project.owner == current_user keys << :created_at if current_user.admin? || user_project.owner == current_user
attrs = attributes_for_keys(keys) attrs = attributes_for_keys(keys)
...@@ -156,6 +157,10 @@ module API ...@@ -156,6 +157,10 @@ module API
attrs[:labels] = params[:labels] if params[:labels] attrs[:labels] = params[:labels] if params[:labels]
# Convert and filter out invalid confidential flags
attrs['confidential'] = to_boolean(attrs['confidential'])
attrs.delete('confidential') if attrs['confidential'].nil?
issue = ::Issues::CreateService.new(user_project, current_user, attrs.merge(request: request, api: true)).execute issue = ::Issues::CreateService.new(user_project, current_user, attrs.merge(request: request, api: true)).execute
if issue.spam? if issue.spam?
...@@ -182,12 +187,13 @@ module API ...@@ -182,12 +187,13 @@ module API
# state_event (optional) - The state event of an issue (close|reopen) # state_event (optional) - The state event of an issue (close|reopen)
# updated_at (optional) - Date time string, ISO 8601 formatted # updated_at (optional) - Date time string, ISO 8601 formatted
# due_date (optional) - Date time string in the format YEAR-MONTH-DAY # due_date (optional) - Date time string in the format YEAR-MONTH-DAY
# confidential (optional) - Boolean parameter if the issue should be confidential
# Example Request: # Example Request:
# PUT /projects/:id/issues/:issue_id # PUT /projects/:id/issues/:issue_id
put ':id/issues/:issue_id' do put ':id/issues/:issue_id' do
issue = user_project.issues.find(params[:issue_id]) issue = user_project.issues.find(params[:issue_id])
authorize! :update_issue, issue authorize! :update_issue, issue
keys = [:title, :description, :assignee_id, :milestone_id, :state_event, :due_date] keys = [:title, :description, :assignee_id, :milestone_id, :state_event, :due_date, :confidential]
keys << :updated_at if current_user.admin? || user_project.owner == current_user keys << :updated_at if current_user.admin? || user_project.owner == current_user
attrs = attributes_for_keys(keys) attrs = attributes_for_keys(keys)
...@@ -198,6 +204,10 @@ module API ...@@ -198,6 +204,10 @@ module API
attrs[:labels] = params[:labels] if params[:labels] attrs[:labels] = params[:labels] if params[:labels]
# Convert and filter out invalid confidential flags
attrs['confidential'] = to_boolean(attrs['confidential'])
attrs.delete('confidential') if attrs['confidential'].nil?
issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue) issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
if issue.valid? if issue.valid?
......
...@@ -405,6 +405,7 @@ describe API::API, api: true do ...@@ -405,6 +405,7 @@ describe API::API, api: true do
expect(json_response['milestone']).to be_a Hash expect(json_response['milestone']).to be_a Hash
expect(json_response['assignee']).to be_a Hash expect(json_response['assignee']).to be_a Hash
expect(json_response['author']).to be_a Hash expect(json_response['author']).to be_a Hash
expect(json_response['confidential']).to be_falsy
end end
it "returns a project issue by id" do it "returns a project issue by id" do
...@@ -470,13 +471,51 @@ describe API::API, api: true do ...@@ -470,13 +471,51 @@ describe API::API, api: true do
end end
describe "POST /projects/:id/issues" do describe "POST /projects/:id/issues" do
it "creates a new project issue" do it 'creates a new project issue' do
post api("/projects/#{project.id}/issues", user), post api("/projects/#{project.id}/issues", user),
title: 'new issue', labels: 'label, label2' title: 'new issue', labels: 'label, label2'
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new issue') expect(json_response['title']).to eq('new issue')
expect(json_response['description']).to be_nil expect(json_response['description']).to be_nil
expect(json_response['labels']).to eq(['label', 'label2']) expect(json_response['labels']).to eq(['label', 'label2'])
expect(json_response['confidential']).to be_falsy
end
it 'creates a new confidential project issue' do
post api("/projects/#{project.id}/issues", user),
title: 'new issue', confidential: true
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new issue')
expect(json_response['confidential']).to be_truthy
end
it 'creates a new confidential project issue with a different param' do
post api("/projects/#{project.id}/issues", user),
title: 'new issue', confidential: 'y'
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new issue')
expect(json_response['confidential']).to be_truthy
end
it 'creates a public issue when confidential param is false' do
post api("/projects/#{project.id}/issues", user),
title: 'new issue', confidential: false
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new issue')
expect(json_response['confidential']).to be_falsy
end
it 'creates a public issue when confidential param is invalid' do
post api("/projects/#{project.id}/issues", user),
title: 'new issue', confidential: 'foo'
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new issue')
expect(json_response['confidential']).to be_falsy
end end
it "sends notifications for subscribers of newly added labels" do it "sends notifications for subscribers of newly added labels" do
...@@ -632,6 +671,30 @@ describe API::API, api: true do ...@@ -632,6 +671,30 @@ describe API::API, api: true do
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['title']).to eq('updated title') expect(json_response['title']).to eq('updated title')
end end
it 'sets an issue to confidential' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
confidential: true
expect(response).to have_http_status(200)
expect(json_response['confidential']).to be_truthy
end
it 'makes a confidential issue public' do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
confidential: false
expect(response).to have_http_status(200)
expect(json_response['confidential']).to be_falsy
end
it 'does not update a confidential issue with wrong confidential flag' do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
confidential: 'foo'
expect(response).to have_http_status(200)
expect(json_response['confidential']).to be_truthy
end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment