diff options
author | Sean McGivern <sean@gitlab.com> | 2019-02-07 09:48:04 +0000 |
---|---|---|
committer | Sean McGivern <sean@gitlab.com> | 2019-02-07 09:48:04 +0000 |
commit | d91b96458e59345dd9ad5b9e3d03a12db6c78270 (patch) | |
tree | 09e0630be1a56e7b63b84b783aa754b228803656 | |
parent | 6e51960788f8a431f86b169d648c4cba68c717ae (diff) | |
parent | 7c6efc001f90ff07c0d48e81409dc5ecd3739b25 (diff) | |
download | gitlab-ce-d91b96458e59345dd9ad5b9e3d03a12db6c78270.tar.gz |
Merge branch 'api-group-labels' into 'master'
API group labels
Closes #44901
See merge request gitlab-org/gitlab-ce!21368
-rw-r--r-- | app/services/labels/update_service.rb | 1 | ||||
-rw-r--r-- | changelogs/unreleased/api-group-labels.yml | 5 | ||||
-rw-r--r-- | doc/api/README.md | 1 | ||||
-rw-r--r-- | doc/api/group_labels.md | 201 | ||||
-rw-r--r-- | lib/api/api.rb | 1 | ||||
-rw-r--r-- | lib/api/entities.rb | 13 | ||||
-rw-r--r-- | lib/api/group_labels.rb | 63 | ||||
-rw-r--r-- | lib/api/helpers.rb | 11 | ||||
-rw-r--r-- | lib/api/helpers/label_helpers.rb | 82 | ||||
-rw-r--r-- | lib/api/labels.rb | 81 | ||||
-rw-r--r-- | lib/api/subscriptions.rb | 87 | ||||
-rw-r--r-- | spec/fixtures/api/schemas/public_api/v4/group_labels.json | 18 | ||||
-rw-r--r-- | spec/requests/api/group_labels_spec.rb | 258 |
13 files changed, 720 insertions, 102 deletions
diff --git a/app/services/labels/update_service.rb b/app/services/labels/update_service.rb index e563447c64c..be33947d0eb 100644 --- a/app/services/labels/update_service.rb +++ b/app/services/labels/update_service.rb @@ -8,6 +8,7 @@ module Labels # returns the updated label def execute(label) + params[:name] = params.delete(:new_name) if params.key?(:new_name) params[:color] = convert_color_name_to_hex if params[:color].present? label.update(params) diff --git a/changelogs/unreleased/api-group-labels.yml b/changelogs/unreleased/api-group-labels.yml new file mode 100644 index 00000000000..0df6f15a9b6 --- /dev/null +++ b/changelogs/unreleased/api-group-labels.yml @@ -0,0 +1,5 @@ +--- +title: 'API: Add support for group labels' +merge_request: 21368 +author: Robert Schilling +type: added diff --git a/doc/api/README.md b/doc/api/README.md index 692f63a400c..a060e0481bf 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -29,6 +29,7 @@ The following API resources are available: - [Group access requests](access_requests.md) - [Group badges](group_badges.md) - [Group issue boards](group_boards.md) + - [Group labels](group_labels.md) - [Group-level variables](group_level_variables.md) - [Group members](members.md) - [Group milestones](group_milestones.md) diff --git a/doc/api/group_labels.md b/doc/api/group_labels.md new file mode 100644 index 00000000000..c36d34b4af1 --- /dev/null +++ b/doc/api/group_labels.md @@ -0,0 +1,201 @@ +# Group Label API + +>**Note:** This feature was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21368) in GitLab 11.8. + +This API supports managing of [group labels](../user/project/labels.md#project-labels-and-group-labels). It allows to list, create, update, and delete group labels. Furthermore, users can subscribe and unsubscribe to and from group labels. + +## List group labels + +Get all labels for a given group. + +``` +GET /groups/:id/labels +``` + +| 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 | + +```bash +curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels +``` + +Example response: + +```json +[ + { + "id": 7, + "name": "bug", + "color": "#FF0000", + "description": null, + "open_issues_count": 0, + "closed_issues_count": 0, + "open_merge_requests_count": 0, + "subscribed": false + }, + { + "id": 4, + "name": "feature", + "color": "#228B22", + "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. + +``` +POST /groups/:id/labels +``` + +| 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 | +| `color` | string | yes | 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 POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" --data '{"name": "Feature Proposal", "color": "#FFA500", "description": "Describes new ideas" }' https://gitlab.example.com/api/v4/groups/5/labels +``` + +Example response: + +```json +{ + "id": 9, + "name": "Feature Proposal", + "color": "#FFA500", + "description": "Describes new ideas", + "open_issues_count": 0, + "closed_issues_count": 0, + "open_merge_requests_count": 0, + "subscribed": false +} +``` + +## Update a group label + +Updates an existing group label. At least one parameter is required, to update the group label. + +``` +PUT /groups/:id/labels +``` + +| 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 | +| `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 +``` + +Example response: + +```json +{ + "id": 9, + "name": "Feature Idea", + "color": "#FFA500", + "description": "Describes new ideas", + "open_issues_count": 0, + "closed_issues_count": 0, + "open_merge_requests_count": 0, + "subscribed": false +} +``` + +## Delete a group label + +Deletes a group label with a given name. + +``` +DELETE /groups/:id/labels +``` + +| 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 | + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels?name=bug +``` + +## Subscribe to a group label + +Subscribes the authenticated user to a group label to receive notifications. If +the user is already subscribed to the label, the status code `304` is returned. + +``` +POST /groups/:id/labels/:label_id/subscribe +``` + +| 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 | + +```bash +curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels/9/subscribe +``` + +Example response: + +```json +{ + "id": 9, + "name": "Feature Idea", + "color": "#FFA500", + "description": "Describes new ideas", + "open_issues_count": 0, + "closed_issues_count": 0, + "open_merge_requests_count": 0, + "subscribed": true +} +``` + +## Unsubscribe from a group label + +Unsubscribes the authenticated user from a group label to not receive +notifications from it. If the user is not subscribed to the label, the status +code `304` is returned. + +``` +POST /groups/:id/labels/:label_id/unsubscribe +``` + +| 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 | + +```bash +curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels/9/unsubscribe +``` + +Example response: + +```json +{ + "id": 9, + "name": "Feature Idea", + "color": "#FFA500", + "description": "Describes new ideas", + "open_issues_count": 0, + "closed_issues_count": 0, + "open_merge_requests_count": 0, + "subscribed": false +} +``` diff --git a/lib/api/api.rb b/lib/api/api.rb index 9cbfc0e35ff..4dd1b459554 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -109,6 +109,7 @@ module API mount ::API::Features mount ::API::Files mount ::API::GroupBoards + mount ::API::GroupLabels mount ::API::GroupMilestones mount ::API::Groups mount ::API::GroupVariables diff --git a/lib/api/entities.rb b/lib/api/entities.rb index a1f0efa3c68..beb8ce349b4 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1019,12 +1019,17 @@ module API label.open_merge_requests_count(options[:current_user]) end - expose :priority do |label, options| - label.priority(options[:project]) + expose :subscribed do |label, options| + label.subscribed?(options[:current_user], options[:parent]) end + end - expose :subscribed do |label, options| - label.subscribed?(options[:current_user], options[:project]) + class GroupLabel < Label + end + + class ProjectLabel < Label + expose :priority do |label, options| + label.priority(options[:parent]) end end diff --git a/lib/api/group_labels.rb b/lib/api/group_labels.rb new file mode 100644 index 00000000000..0dbc5f45a68 --- /dev/null +++ b/lib/api/group_labels.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module API + class GroupLabels < Grape::API + include PaginationParams + helpers ::API::Helpers::LabelHelpers + + before { authenticate! } + + params do + requires :id, type: String, desc: 'The ID of a group' + end + resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + desc 'Get all labels of the group' do + detail 'This feature was added in GitLab 11.8' + success Entities::GroupLabel + end + params do + use :pagination + end + get ':id/labels' do + get_labels(user_group, Entities::GroupLabel) + end + + desc 'Create a new label' do + detail 'This feature was added in GitLab 11.8' + success Entities::GroupLabel + end + params do + use :label_create_params + end + post ':id/labels' do + create_label(user_group, Entities::GroupLabel) + end + + desc 'Update an existing label. At least one optional parameter is required.' do + detail 'This feature was added in GitLab 11.8' + 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 + 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' + success Entities::GroupLabel + end + params do + requires :name, type: String, desc: 'The name of the label to be deleted' + end + delete ':id/labels' do + delete_label(user_group) + end + end + end +end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index e3d0b981065..2eb7b04711a 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -84,8 +84,8 @@ module API page || not_found!('Wiki Page') end - def available_labels_for(label_parent) - search_params = { include_ancestor_groups: true } + def available_labels_for(label_parent, include_ancestor_groups: true) + search_params = { include_ancestor_groups: include_ancestor_groups } if label_parent.is_a?(Project) search_params[:project_id] = label_parent.id @@ -170,13 +170,6 @@ module API end end - def find_project_label(id) - labels = available_labels_for(user_project) - label = labels.find_by_id(id) || labels.find_by_title(id) - - label || not_found!('Label') - end - # rubocop: disable CodeReuse/ActiveRecord def find_project_issue(iid) IssuesFinder.new(current_user, project_id: user_project.id).find_by!(iid: iid) diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb new file mode 100644 index 00000000000..c11e7d614ab --- /dev/null +++ b/lib/api/helpers/label_helpers.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module API + module Helpers + module LabelHelpers + extend Grape::API::Helpers + + params :label_create_params do + requires :name, type: String, desc: 'The name of the label to be created' + requires :color, type: String, desc: "The 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 description of label to be created' + end + + def find_label(parent, id, include_ancestor_groups: true) + labels = available_labels_for(parent, include_ancestor_groups: include_ancestor_groups) + label = labels.find_by_id(id) || labels.find_by_title(id) + + label || not_found!('Label') + end + + def get_labels(parent, entity) + present paginate(available_labels_for(parent)), with: entity, current_user: current_user, parent: parent + end + + def create_label(parent, entity) + authorize! :admin_label, parent + + label = available_labels_for(parent).find_by_title(params[:name]) + conflict!('Label already exists') if label + + priority = params.delete(:priority) + label_params = declared_params(include_missing: false) + + label = + if parent.is_a?(Project) + ::Labels::CreateService.new(label_params).execute(project: parent) + else + ::Labels::CreateService.new(label_params).execute(group: parent) + end + + if label.persisted? + if parent.is_a?(Project) + label.prioritize!(parent, priority) if priority + end + + present label, with: entity, current_user: current_user, parent: parent + else + render_validation_error!(label) + end + end + + def update_label(parent, entity) + authorize! :admin_label, parent + + label = find_label(parent, params[:name], include_ancestor_groups: false) + update_priority = params.key?(:priority) + priority = params.delete(:priority) + + label = ::Labels::UpdateService.new(declared_params(include_missing: false)).execute(label) + render_validation_error!(label) unless label.valid? + + if parent.is_a?(Project) && update_priority + if priority.nil? + label.unprioritize!(parent) + else + label.prioritize!(parent, priority) + end + end + + present label, with: entity, current_user: current_user, parent: parent + end + + def delete_label(parent) + authorize! :admin_label, parent + + label = find_label(parent, params[:name], include_ancestor_groups: false) + + destroy_conditionally!(label) + end + end + end +end diff --git a/lib/api/labels.rb b/lib/api/labels.rb index d5eb2b94669..d729d3ee625 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -3,6 +3,7 @@ module API class Labels < Grape::API include PaginationParams + helpers ::API::Helpers::LabelHelpers before { authenticate! } @@ -11,62 +12,28 @@ module API end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get all labels of the project' do - success Entities::Label + success Entities::ProjectLabel end params do use :pagination end get ':id/labels' do - present paginate(available_labels_for(user_project)), with: Entities::Label, current_user: current_user, project: user_project + get_labels(user_project, Entities::ProjectLabel) end desc 'Create a new label' do - success Entities::Label + success Entities::ProjectLabel end params do - requires :name, type: String, desc: 'The name of the label to be created' - requires :color, type: String, desc: "The 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 description of label to be created' + use :label_create_params optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true end - # rubocop: disable CodeReuse/ActiveRecord post ':id/labels' do - authorize! :admin_label, user_project - - label = available_labels_for(user_project).find_by(title: params[:name]) - conflict!('Label already exists') if label - - priority = params.delete(:priority) - label = ::Labels::CreateService.new(declared_params(include_missing: false)).execute(project: user_project) - - if label.valid? - label.prioritize!(user_project, priority) if priority - present label, with: Entities::Label, current_user: current_user, project: user_project - else - render_validation_error!(label) - end - end - # rubocop: enable CodeReuse/ActiveRecord - - desc 'Delete an existing label' do - success Entities::Label - end - params do - requires :name, type: String, desc: 'The name of the label to be deleted' - end - # rubocop: disable CodeReuse/ActiveRecord - delete ':id/labels' do - authorize! :admin_label, user_project - - label = user_project.labels.find_by(title: params[:name]) - not_found!('Label') unless label - - destroy_conditionally!(label) + create_label(user_project, Entities::ProjectLabel) end - # rubocop: enable CodeReuse/ActiveRecord desc 'Update an existing label. At least one optional parameter is required.' do - success Entities::Label + success Entities::ProjectLabel end params do requires :name, type: String, desc: 'The name of the label to be updated' @@ -76,33 +43,19 @@ module API optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true at_least_one_of :new_name, :color, :description, :priority end - # rubocop: disable CodeReuse/ActiveRecord put ':id/labels' do - authorize! :admin_label, user_project - - label = user_project.labels.find_by(title: params[:name]) - not_found!('Label not found') unless label - - update_priority = params.key?(:priority) - priority = params.delete(:priority) - label_params = declared_params(include_missing: false) - # Rename new name to the actual label attribute name - label_params[:name] = label_params.delete(:new_name) if label_params.key?(:new_name) - - label = ::Labels::UpdateService.new(label_params).execute(label) - render_validation_error!(label) unless label.valid? - - if update_priority - if priority.nil? - label.unprioritize!(user_project) - else - label.prioritize!(user_project, priority) - end - end + update_label(user_project, Entities::ProjectLabel) + end - present label, with: Entities::Label, current_user: current_user, project: user_project + desc 'Delete an existing label' do + success Entities::ProjectLabel + end + params do + requires :name, type: String, desc: 'The name of the label to be deleted' + end + delete ':id/labels' do + delete_label(user_project) end - # rubocop: enable CodeReuse/ActiveRecord end end end diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb index 74ad3c35a61..dfb54446ddf 100644 --- a/lib/api/subscriptions.rb +++ b/lib/api/subscriptions.rb @@ -2,51 +2,88 @@ module API class Subscriptions < Grape::API + helpers ::API::Helpers::LabelHelpers + before { authenticate! } - subscribable_types = { - 'merge_requests' => proc { |id| find_merge_request_with_access(id, :update_merge_request) }, - 'issues' => proc { |id| find_project_issue(id) }, - 'labels' => proc { |id| find_project_label(id) } - } + subscribables = [ + { + type: 'merge_requests', + entity: Entities::MergeRequest, + source: Project, + finder: ->(id) { find_merge_request_with_access(id, :update_merge_request) } + }, + { + type: 'issues', + entity: Entities::Issue, + source: Project, + finder: ->(id) { find_project_issue(id) } + }, + { + type: 'labels', + entity: Entities::ProjectLabel, + source: Project, + finder: ->(id) { find_label(user_project, id) } + }, + { + type: 'labels', + entity: Entities::GroupLabel, + source: Group, + finder: ->(id) { find_label(user_group, id) } + } + ] - params do - requires :id, type: String, desc: 'The ID of a project' - requires :subscribable_id, type: String, desc: 'The ID of a resource' - end - resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - subscribable_types.each do |type, finder| - type_singularized = type.singularize - entity_class = Entities.const_get(type_singularized.camelcase) + subscribables.each do |subscribable| + source_type = subscribable[:source].name.underscore + params do + requires :id, type: String, desc: "The #{source_type} ID" + requires :subscribable_id, type: String, desc: 'The ID of a resource' + end + resource source_type.pluralize, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Subscribe to a resource' do - success entity_class + success subscribable[:entity] end - post ":id/#{type}/:subscribable_id/subscribe" do - resource = instance_exec(params[:subscribable_id], &finder) + post ":id/#{subscribable[:type]}/:subscribable_id/subscribe" do + parent = parent_resource(source_type) + resource = instance_exec(params[:subscribable_id], &subscribable[:finder]) - if resource.subscribed?(current_user, user_project) + if resource.subscribed?(current_user, parent) not_modified! else - resource.subscribe(current_user, user_project) - present resource, with: entity_class, current_user: current_user, project: user_project + resource.subscribe(current_user, parent) + present resource, with: subscribable[:entity], current_user: current_user, project: parent, parent: parent end end desc 'Unsubscribe from a resource' do - success entity_class + success subscribable[:entity] end - post ":id/#{type}/:subscribable_id/unsubscribe" do - resource = instance_exec(params[:subscribable_id], &finder) + post ":id/#{subscribable[:type]}/:subscribable_id/unsubscribe" do + parent = parent_resource(source_type) + resource = instance_exec(params[:subscribable_id], &subscribable[:finder]) - if !resource.subscribed?(current_user, user_project) + if !resource.subscribed?(current_user, parent) not_modified! else - resource.unsubscribe(current_user, user_project) - present resource, with: entity_class, current_user: current_user, project: user_project + resource.unsubscribe(current_user, parent) + present resource, with: subscribable[:entity], current_user: current_user, project: parent, parent: parent end end end end + + private + + helpers do + def parent_resource(source_type) + case source_type + when 'project' + user_project + else + nil + end + end + end end end diff --git a/spec/fixtures/api/schemas/public_api/v4/group_labels.json b/spec/fixtures/api/schemas/public_api/v4/group_labels.json new file mode 100644 index 00000000000..f6c327abfdd --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/group_labels.json @@ -0,0 +1,18 @@ +{ + "type": "array", + "items": { + "type": "object", + "properties" : { + "id" : { "type": "integer" }, + "name" : { "type": "string "}, + "color" : { "type": "string "}, + "description" : { "type": "string "}, + "open_issues_count" : { "type": "integer "}, + "closed_issues_count" : { "type": "integer "}, + "open_merge_requests_count" : { "type": "integer "}, + "subscribed" : { "type": "boolean" }, + "priority" : { "type": "null" } + }, + "additionalProperties": false + } +} diff --git a/spec/requests/api/group_labels_spec.rb b/spec/requests/api/group_labels_spec.rb new file mode 100644 index 00000000000..3769f8b78e4 --- /dev/null +++ b/spec/requests/api/group_labels_spec.rb @@ -0,0 +1,258 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::GroupLabels do + let(:user) { create(:user) } + let(:group) { create(:group) } + let!(:group_member) { create(:group_member, group: group, user: user) } + let!(:label1) { create(:group_label, title: 'feature', group: group) } + let!(:label2) { create(:group_label, title: 'bug', group: group) } + + describe 'GET :id/labels' do + it 'returns all available labels for the group' do + get api("/groups/#{group.id}/labels", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/group_labels') + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.size).to eq(2) + expect(json_response.map {|r| r['name'] }).to contain_exactly('feature', 'bug') + 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), + params: { + name: 'Foo', + color: '#FFAABB', + description: 'test' + } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq('Foo') + expect(json_response['color']).to eq('#FFAABB') + expect(json_response['description']).to eq('test') + end + + it 'returns created label when only required params are given' do + post api("/groups/#{group.id}/labels", user), + params: { + name: 'Foo & Bar', + color: '#FFAABB' + } + + expect(response.status).to eq(201) + expect(json_response['name']).to eq('Foo & Bar') + expect(json_response['color']).to eq('#FFAABB') + expect(json_response['description']).to be_nil + end + + it 'returns a 400 bad request if name not given' do + post api("/groups/#{group.id}/labels", user), params: { color: '#FFAABB' } + + expect(response).to have_gitlab_http_status(400) + end + + it 'returns a 400 bad request if color is not given' do + post api("/groups/#{group.id}/labels", user), params: { name: 'Foobar' } + + expect(response).to have_gitlab_http_status(400) + end + + it 'returns 409 if label already exists' do + post api("/groups/#{group.id}/labels", user), + params: { + name: label1.name, + color: '#FFAABB' + } + + expect(response).to have_gitlab_http_status(409) + expect(json_response['message']).to eq('Label already exists') + end + end + + describe 'DELETE /groups/:id/labels' do + it 'returns 204 for existing label' do + delete api("/groups/#{group.id}/labels", user), params: { name: label1.name } + + expect(response).to have_gitlab_http_status(204) + end + + it 'returns 404 for non existing label' do + delete api("/groups/#{group.id}/labels", user), params: { name: 'label2' } + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 Label Not Found') + end + + it 'returns 400 for wrong parameters' do + delete api("/groups/#{group.id}/labels", user) + + expect(response).to have_gitlab_http_status(400) + end + + it "does not delete parent's group labels", :nested_groups do + subgroup = create(:group, parent: group) + subgroup_label = create(:group_label, title: 'feature', group: subgroup) + + delete api("/groups/#{subgroup.id}/labels", user), params: { name: subgroup_label.name } + + expect(response).to have_gitlab_http_status(204) + expect(subgroup.labels.size).to eq(0) + expect(group.labels).to include(label1) + end + + it_behaves_like '412 response' do + let(:request) { api("/groups/#{group.id}/labels", user) } + let(:params) { { name: label1.name } } + end + end + + describe 'PUT /groups/:id/labels' do + it 'returns 200 if name and colors and description are changed' do + put api("/groups/#{group.id}/labels", user), + params: { + name: label1.name, + 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", :nested_groups do + subgroup = create(:group, parent: group) + subgroup_label = create(:group_label, title: 'feature', group: subgroup) + + put api("/groups/#{subgroup.id}/labels", user), + params: { + name: subgroup_label.name, + new_name: 'New Label' + } + + expect(response).to have_gitlab_http_status(200) + expect(subgroup.labels[0].name).to eq('New Label') + expect(label1.name).to eq('feature') + end + + it 'returns 404 if label does not exist' do + put api("/groups/#{group.id}/labels", user), + params: { + name: 'label2', + new_name: 'label3' + } + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns 400 if no label name given' do + put api("/groups/#{group.id}/labels", user), params: { new_name: label1.name } + + expect(response).to have_gitlab_http_status(400) + expect(json_response['error']).to eq('name is missing') + end + + it 'returns 400 if no new parameters given' do + put api("/groups/#{group.id}/labels", user), params: { name: label1.name } + + 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 + post api("/groups/#{group.id}/labels/#{label1.title}/subscribe", user) + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(label1.title) + expect(json_response['subscribed']).to be_truthy + end + end + + context 'when label_id is a label ID' do + it 'subscribes to the label' do + post api("/groups/#{group.id}/labels/#{label1.id}/subscribe", user) + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(label1.title) + expect(json_response['subscribed']).to be_truthy + end + end + + context 'when user is already subscribed to label' do + before do + label1.subscribe(user) + end + + it 'returns 304' do + post api("/groups/#{group.id}/labels/#{label1.id}/subscribe", user) + + expect(response).to have_gitlab_http_status(304) + end + end + + context 'when label ID is not found' do + it 'returns 404 error' do + post api("/groups/#{group.id}/labels/1234/subscribe", user) + + expect(response).to have_gitlab_http_status(404) + end + end + end + + describe 'POST /groups/:id/labels/:label_id/unsubscribe' do + before do + label1.subscribe(user) + end + + context 'when label_id is a label title' do + it 'unsubscribes from the label' do + post api("/groups/#{group.id}/labels/#{label1.title}/unsubscribe", user) + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(label1.title) + expect(json_response['subscribed']).to be_falsey + end + end + + context 'when label_id is a label ID' do + it 'unsubscribes from the label' do + post api("/groups/#{group.id}/labels/#{label1.id}/unsubscribe", user) + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(label1.title) + expect(json_response['subscribed']).to be_falsey + end + end + + context 'when user is already unsubscribed from label' do + before do + label1.unsubscribe(user) + end + + it 'returns 304' do + post api("/groups/#{group.id}/labels/#{label1.id}/unsubscribe", user) + + expect(response).to have_gitlab_http_status(304) + end + end + + context 'when label ID is not found' do + it 'returns 404 error' do + post api("/groups/#{group.id}/labels/1234/unsubscribe", user) + + expect(response).to have_gitlab_http_status(404) + end + end + end +end |