Commit 56286f28 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'labels_rest' into 'master'

Add proper label REST API for update, delete and promote

See merge request gitlab-org/gitlab!17239
parents d88da79e 70b73132
---
title: Add proper label REST API for update, delete and promote
merge_request: 17239
author: Mathieu Parent
type: added
......@@ -51,6 +51,40 @@ Example response:
]
```
## Get a single group label
Get a single label for a given group.
```
GET /groups/:id/labels/:label_id
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `label_id` | integer or string | yes | The ID or title of a group's label. |
| `include_ancestor_groups` | boolean | no | Include ancestor groups. Defaults to `true`. |
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels/bug
```
Example response:
```json
{
"id": 7,
"name": "bug",
"color": "#FF0000",
"text_color" : "#FFFFFF",
"description": null,
"open_issues_count": 0,
"closed_issues_count": 0,
"open_merge_requests_count": 0,
"subscribed": false
}
```
## Create a new group label
Create a new group label for a given group.
......@@ -91,19 +125,19 @@ Example response:
Updates an existing group label. At least one parameter is required, to update the group label.
```
PUT /groups/:id/labels
PUT /groups/:id/labels/:label_id
```
| Attribute | Type | Required | Description |
| ------------- | ------- | -------- | ---------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `name` | string | yes | The name of the label |
| `label_id` | integer or string | yes | The ID or title of a group's label. |
| `new_name` | string | no | The new name of the label |
| `color` | string | no | The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the [CSS color names](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords) |
| `description` | string | no | The description of the label. |
```bash
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" --data '{"name": "Feature Proposal", "new_name": "Feature Idea" }' https://gitlab.example.com/api/v4/groups/5/labels
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" --data '{"new_name": "Feature Idea" }' https://gitlab.example.com/api/v4/groups/5/labels/Feature%20Proposal
```
Example response:
......@@ -122,23 +156,27 @@ Example response:
}
```
NOTE: **Note:** An older endpoint `PUT /groups/:id/labels` with `name` in the params is still available, but deprecated.
## Delete a group label
Deletes a group label with a given name.
```
DELETE /groups/:id/labels
DELETE /groups/:id/labels/:label_id
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `name` | string | yes | The name of the label. |
| `label_id` | integer or string | yes | The ID or title of a group's label. |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels?name=bug
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels/bug
```
NOTE: **Note:** An older endpoint `DELETE /groups/:id/labels` with `name` in the params is still available, but deprecated.
## Subscribe to a group label
Subscribes the authenticated user to a group label to receive notifications. If
......
......@@ -90,6 +90,42 @@ Example response:
]
```
## Get a single project label
Get a single label for a given project.
```
GET /projects/:id/labels/:label_id
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `label_id` | integer or string | yes | The ID or title of a group's label. |
| `include_ancestor_groups` | boolean | no | Include ancestor groups. Defaults to `true`. |
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/labels/bug
```
Example response:
```json
{
"id" : 1,
"name" : "bug",
"color" : "#d9534f",
"text_color" : "#FFFFFF",
"description": "Bug reported by user",
"open_issues_count": 1,
"closed_issues_count": 0,
"open_merge_requests_count": 1,
"subscribed": false,
"priority": 10,
"is_project_label": true
}
```
## Create a new label
Creates a new label for the given repository with the given name and color.
......@@ -133,40 +169,40 @@ Example response:
Deletes a label with a given name.
```
DELETE /projects/:id/labels
DELETE /projects/:id/labels/:label_id
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `label_id` | integer | yes (or `name`) | The id of the existing label |
| `name` | string | yes (or `label_id`) | The name of the existing label |
| `label_id` | integer or string | yes | The ID or title of a group's label. |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels?name=bug"
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels/bug"
```
NOTE: **Note:** An older endpoint `DELETE /projects/:id/labels` with `name` in the params is still available, but deprecated.
## Edit an existing label
Updates an existing label with new name or new color. At least one parameter
is required, to update the label.
```
PUT /projects/:id/labels
PUT /projects/:id/labels/:label_id
```
| Attribute | Type | Required | Description |
| --------------- | ------- | --------------------------------- | ------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `label_id` | integer | yes (or `name`) | The id of the existing label |
| `name` | string | yes (or `label_id`) | The name of the existing label |
| `label_id` | integer or string | yes | The ID or title of a group's label. |
| `new_name` | string | yes if `color` is not provided | The new name of the label |
| `color` | string | yes if `new_name` is not provided | The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the [CSS color names](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords) |
| `description` | string | no | The new description of the label |
| `priority` | integer | no | The new priority of the label. Must be greater or equal than zero or `null` to remove the priority. |
```bash
curl --request PUT --data "name=documentation&new_name=docs&color=#8E44AD&description=Documentation" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels"
curl --request PUT --data "new_name=docs&color=#8E44AD&description=Documentation" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels/documentation"
```
Example response:
......@@ -187,6 +223,8 @@ Example response:
}
```
NOTE: **Note:** An older endpoint `PUT /projects/:id/labels` with `name` or `label_id` in the params is still available, but deprecated.
## Promote a project label to a group label
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/25218) in GitLab 12.3.
......@@ -194,16 +232,16 @@ Example response:
Promotes a project label to a group label.
```
PUT /projects/:id/labels/promote
PUT /projects/:id/labels/:label_id/promote
```
| Attribute | Type | Required | Description |
| --------------- | ------- | --------------------------------- | ------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `name` | string | yes | The name of the existing label |
| `label_id` | integer or string | yes | The ID or title of a group's label. |
```bash
curl --request PUT --data "name=documentation" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels/promote"
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels/documentation/promote"
```
Example response:
......@@ -221,6 +259,8 @@ Example response:
}
```
NOTE: **Note:** An older endpoint `PUT /projects/:id/labels/promote` with `name` in the params is still available, but deprecated.
## Subscribe to a label
Subscribes the authenticated user to a label to receive notifications.
......
......@@ -26,6 +26,18 @@ module API
get_labels(user_group, Entities::GroupLabel, include_ancestor_groups: params[:include_ancestor_groups])
end
desc 'Get a single label' do
detail 'This feature was added in GitLab 12.4.'
success Entities::GroupLabel
end
params do
optional :include_ancestor_groups, type: Boolean, default: true,
desc: 'Include ancestor groups'
end
get ':id/labels/:name' do
get_label(user_group, Entities::GroupLabel, include_ancestor_groups: params[:include_ancestor_groups])
end
desc 'Create a new label' do
detail 'This feature was added in GitLab 11.8'
success Entities::GroupLabel
......@@ -38,22 +50,21 @@ module API
end
desc 'Update an existing label. At least one optional parameter is required.' do
detail 'This feature was added in GitLab 11.8'
detail 'This feature was added in GitLab 11.8 and deprecated in GitLab 12.4.'
success Entities::GroupLabel
end
params do
requires :name, type: String, desc: 'The name of the label to be updated'
optional :new_name, type: String, desc: 'The new name of the label'
optional :color, type: String, desc: "The new color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names"
optional :description, type: String, desc: 'The new description of label'
at_least_one_of :new_name, :color, :description
optional :label_id, type: Integer, desc: 'The id of the label to be updated'
optional :name, type: String, desc: 'The name of the label to be updated'
use :group_label_update_params
exactly_one_of :label_id, :name
end
put ':id/labels' do
update_label(user_group, Entities::GroupLabel)
end
desc 'Delete an existing label' do
detail 'This feature was added in GitLab 11.8'
detail 'This feature was added in GitLab 11.8 and deprecated in GitLab 12.4.'
success Entities::GroupLabel
end
params do
......@@ -62,6 +73,29 @@ module API
delete ':id/labels' do
delete_label(user_group)
end
desc 'Update an existing label. At least one optional parameter is required.' do
detail 'This feature was added in GitLab 12.4.'
success Entities::GroupLabel
end
params do
requires :name, type: String, desc: 'The name or id of the label to be updated'
use :group_label_update_params
end
put ':id/labels/:name' do
update_label(user_group, Entities::GroupLabel)
end
desc 'Delete an existing label' do
detail 'This feature was added in GitLab 12.4.'
success Entities::GroupLabel
end
params do
requires :name, type: String, desc: 'The name or id of the label to be deleted'
end
delete ':id/labels/:name' do
delete_label(user_group)
end
end
end
end
......@@ -11,6 +11,23 @@ module API
optional :description, type: String, desc: 'The description of label to be created'
end
params :label_update_params do
optional :new_name, type: String, desc: 'The new name of the label'
optional :color, type: String, desc: "The new color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names"
optional :description, type: String, desc: 'The new description of label'
end
params :project_label_update_params do
use :label_update_params
optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true
at_least_one_of :new_name, :color, :description, :priority
end
params :group_label_update_params do
use :label_update_params
at_least_one_of :new_name, :color, :description
end
def find_label(parent, id_or_title, include_ancestor_groups: true)
labels = available_labels_for(parent, include_ancestor_groups: include_ancestor_groups)
label = labels.find_by_id(id_or_title) || labels.find_by_title(id_or_title)
......@@ -26,6 +43,12 @@ module API
with_counts: params[:with_counts]
end
def get_label(parent, entity, include_ancestor_groups: true)
label = find_label(parent, params_id_or_title, include_ancestor_groups: include_ancestor_groups)
present label, with: entity, current_user: current_user, parent: parent
end
def create_label(parent, entity)
authorize! :admin_label, parent
......@@ -57,6 +80,7 @@ module API
# params is used to update the label so we need to remove this field here
params.delete(:label_id)
params.delete(:name)
label = ::Labels::UpdateService.new(declared_params(include_missing: false)).execute(label)
render_validation_error!(label) unless label.valid?
......@@ -80,6 +104,24 @@ module API
destroy_conditionally!(label)
end
def promote_label(parent)
authorize! :admin_label, parent
label = find_label(parent, params[:name], include_ancestor_groups: false)
begin
group_label = ::Labels::PromoteService.new(parent, current_user).execute(label)
if group_label
present group_label, with: Entities::GroupLabel, current_user: current_user, parent: parent.group
else
render_api_error!('Failed to promote project label to group label', 400)
end
rescue => error
render_api_error!(error.to_s, 400)
end
end
def params_id_or_title
@params_id_or_title ||= params[:label_id] || params[:name]
end
......
......@@ -25,6 +25,18 @@ module API
get_labels(user_project, Entities::ProjectLabel, include_ancestor_groups: params[:include_ancestor_groups])
end
desc 'Get a single label' do
detail 'This feature was added in GitLab 12.4.'
success Entities::ProjectLabel
end
params do
optional :include_ancestor_groups, type: Boolean, default: true,
desc: 'Include ancestor groups'
end
get ':id/labels/:name' do
get_label(user_project, Entities::ProjectLabel, include_ancestor_groups: params[:include_ancestor_groups])
end
desc 'Create a new label' do
success Entities::ProjectLabel
end
......@@ -37,23 +49,21 @@ module API
end
desc 'Update an existing label. At least one optional parameter is required.' do
detail 'This feature was deprecated in GitLab 12.4.'
success Entities::ProjectLabel
end
params do
optional :label_id, type: Integer, desc: 'The id of the label to be updated'
optional :name, type: String, desc: 'The name of the label to be updated'
optional :new_name, type: String, desc: 'The new name of the label'
optional :color, type: String, desc: "The new color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names"
optional :description, type: String, desc: 'The new description of label'
optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true
use :project_label_update_params
exactly_one_of :label_id, :name
at_least_one_of :new_name, :color, :description, :priority
end
put ':id/labels' do
update_label(user_project, Entities::ProjectLabel)
end
desc 'Delete an existing label' do
detail 'This feature was deprecated in GitLab 12.4.'
success Entities::ProjectLabel
end
params do
......@@ -66,28 +76,48 @@ module API
end
desc 'Promote a label to a group label' do
detail 'This feature was added in GitLab 12.3'
detail 'This feature was added in GitLab 12.3 and deprecated in GitLab 12.4.'
success Entities::GroupLabel
end
params do
requires :name, type: String, desc: 'The name of the label to be promoted'
end
put ':id/labels/promote' do
authorize! :admin_label, user_project
promote_label(user_project)
end
label = find_label(user_project, params[:name], include_ancestor_groups: false)
desc 'Update an existing label. At least one optional parameter is required.' do
detail 'This feature was added in GitLab 12.4.'
success Entities::ProjectLabel
end
params do
requires :name, type: String, desc: 'The name or id of the label to be updated'
use :project_label_update_params
end
put ':id/labels/:name' do
update_label(user_project, Entities::ProjectLabel)
end
begin
group_label = ::Labels::PromoteService.new(user_project, current_user).execute(label)
desc 'Delete an existing label' do
detail 'This feature was added in GitLab 12.4.'
success Entities::ProjectLabel
end
params do
requires :name, type: String, desc: 'The name or id of the label to be deleted'
end
delete ':id/labels/:name' do
delete_label(user_project)
end
if group_label
present group_label, with: Entities::GroupLabel, current_user: current_user, parent: user_project.group
else
render_api_error!('Failed to promote project label to group label', 400)
end
rescue => error
render_api_error!(error.to_s, 400)
end
desc 'Promote a label to a group label' do
detail 'This feature was added in GitLab 12.4.'
success Entities::GroupLabel
end
params do
requires :name, type: String, desc: 'The name or id of the label to be promoted'
end
put ':id/labels/:name/promote' do
promote_label(user_project)
end
end
end
......
......@@ -65,6 +65,17 @@ describe API::GroupLabels do
end
end
describe 'GET :id/labels/:label_id' do
it 'returns a single label for the group' do
get api("/groups/#{group.id}/labels/#{group_label1.name}", user)
expect(response).to have_gitlab_http_status(200)
expect(json_response['name']).to eq(group_label1.name)
expect(json_response['color']).to eq(group_label1.color)
expect(json_response['description']).to eq(group_label1.description)
end
end
describe 'POST /groups/:id/labels' do
it 'returns created label when all params are given' do
post api("/groups/#{group.id}/labels", user),
......@@ -117,7 +128,7 @@ describe API::GroupLabels do
end
end
describe 'DELETE /groups/:id/labels' do
describe 'DELETE /groups/:id/labels (deprecated)' do
it 'returns 204 for existing label' do
delete api("/groups/#{group.id}/labels", user), params: { name: group_label1.name }
......@@ -154,7 +165,37 @@ describe API::GroupLabels do
end
end
describe 'PUT /groups/:id/labels' do
describe 'DELETE /groups/:id/labels/:label_id' do
it 'returns 204 for existing label' do
delete api("/groups/#{group.id}/labels/#{group_label1.name}", user)
expect(response).to have_gitlab_http_status(204)
end
it 'returns 404 for non existing label' do
delete api("/groups/#{group.id}/labels/not_exists", user)
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Label Not Found')
end
it "does not delete parent's group labels" do
subgroup = create(:group, parent: group)
subgroup_label = create(:group_label, title: 'feature', group: subgroup)
delete api("/groups/#{subgroup.id}/labels/#{subgroup_label.name}", user)
expect(response).to have_gitlab_http_status(204)
expect(subgroup.labels.size).to eq(0)
expect(group.labels).to include(group_label1)
end
it_behaves_like '412 response' do
let(:request) { api("/groups/#{group.id}/labels/#{group_label1.name}", user) }
end
end
describe 'PUT /groups/:id/labels (deprecated)' do
it 'returns 200 if name and colors and description are changed' do
put api("/groups/#{group.id}/labels", user),
params: {
......@@ -199,7 +240,7 @@ describe API::GroupLabels do
put api("/groups/#{group.id}/labels", user), params: { new_name: group_label1.name }
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('name is missing')
expect(json_response['error']).to eq('label_id, name are missing, exactly one parameter must be provided')
end
it 'returns 400 if no new parameters given' do
......@@ -211,6 +252,53 @@ describe API::GroupLabels do
end
end
describe 'PUT /groups/:id/labels/:label_id' do
it 'returns 200 if name and colors and description are changed' do
put api("/groups/#{group.id}/labels/#{group_label1.name}", user),
params: {
new_name: 'New Label',
color: '#FFFFFF',
description: 'test'
}
expect(response).to have_gitlab_http_status(200)
expect(json_response['name']).to eq('New Label')
expect(json_response['color']).to eq('#FFFFFF')
expect(json_response['description']).to eq('test')
end
it "does not update parent's group label" do
subgroup = create(:group, parent: group)
subgroup_label = create(:group_label, title: 'feature', group: subgroup)
put api("/groups/#{subgroup.id}/labels/#{subgroup_label.name}", user),
params: {
new_name: 'New Label'
}
expect(response).to have_gitlab_http_status(200)
expect(subgroup.labels[0].name).to eq('New Label')
expect(group_label1.name).to eq('feature')
end
it 'returns 404 if label does not exist' do
put api("/groups/#{group.id}/labels/not_exists", user),
params: {
new_name: 'label3'
}
expect(response).to have_gitlab_http_status(404)
end
it 'returns 400 if no new parameters given' do
put api("/groups/#{group.id}/labels/#{group_label1.name}", user)
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('new_name, color, description are missing, '\
'at least one parameter must be provided')
end
end
describe 'POST /groups/:id/labels/:label_id/subscribe' do
context 'when label_id is a label title' do
it 'subscribes to the label' do
......
This diff is collapsed.
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