diff --git a/CHANGELOG b/CHANGELOG index 50dc6b8945b149f4aad68267816c3c7aaf0195e9..c19826bc5c121a152514c719ca12a533c31d0c9d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ v 7.2.0 - Fix bug when MR download patch return invalid diff - Test gitlab-shell integration - Repository import timeout increased from 2 to 4 minutes allowing larger repos to be imported + - API for labels (Robert Schilling) v 7.1.1 - Fix cpu usage issue in Firefox diff --git a/app/models/label.rb b/app/models/label.rb index c32efc7c47fe3377f05b7ae9b1130d7f392fbd88..819d6cefa4171274141e6a0ec984228cbde262a2 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -18,9 +18,7 @@ class Label < ActiveRecord::Base scope :order_by_name, -> { reorder("labels.title ASC") } - def name - title - end + alias_attribute :name, :title def open_issues_count issues.opened.count diff --git a/app/models/project.rb b/app/models/project.rb index a24eae7d26b59eed531cc087ff21f4cc6570c776..7f6aa6d42493dfd36b2ff9de4c99c72360126cea 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -577,4 +577,8 @@ def update_repository_size def forks_count ForkedProjectLink.where(forked_from_project_id: self.id).count end + + def find_label(name) + labels.find_by(name: name) + end end diff --git a/doc/api/README.md b/doc/api/README.md index a0a9ba6f4b68c1ac7c9c9e353833660a672db49a..44e95ed82582101cc65ed55c985f465de0ed723e 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -12,6 +12,7 @@ - [Branches](branches.md) - [Merge Requests](merge_requests.md) - [Issues](issues.md) +- [Labels](labels.md) - [Milestones](milestones.md) - [Notes](notes.md) (comments) - [Deploy Keys](deploy_keys.md) diff --git a/doc/api/labels.md b/doc/api/labels.md new file mode 100644 index 0000000000000000000000000000000000000000..95fd4e84119b366452afbd8771d8fdab98818350 --- /dev/null +++ b/doc/api/labels.md @@ -0,0 +1,85 @@ +# Labels + +## List labels + +Get all labels for given project. + +``` +GET /projects/:id/labels +``` + +```json +[ + { + "name": "Awesome", + "color": "#DD10AA" + }, + { + "name": "Documentation", + "color": "#1E80DD" + }, + { + "name": "Feature", + "color": "#11FF22" + }, + { + "name": "Bug", + "color": "#EE1122" + } +] +``` + +## Create a new label + +Creates a new label for given repository with given name and color. + +``` +POST /projects/:id/labels +``` + +Parameters: + +- `id` (required) - The ID of a project +- `name` (required) - The name of the label +- `color` (required) - Color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) + +It returns 200 and the newly created label, if the operation succeeds. +If the label already exists, 409 and an error message is returned. +If label parameters are invalid, 405 and an explaining error message is returned. + +## Delete a label + +Deletes a label given by its name. + +``` +DELETE /projects/:id/labels +``` + +- `id` (required) - The ID of a project +- `name` (required) - The name of the label to be deleted + +It returns 200 if the label successfully was deleted, 404 for wrong parameters +and 400 if the label does not exist. +In case of an error, additionally an error message is returned. + +## Edit an existing label + +Updates an existing label with new name or now color. At least one parameter +is required, to update the label. + +``` +PUT /projects/:id/labels +``` + +Parameters: + +- `id` (required) - The ID of a project +- `name` (required) - The name of the existing label +- `new_name` (optional) - The new name of the label +- `color` (optional) - New color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) + +On success, this method returns 200 with the updated label. +If required parameters are missing, 400 is returned. +If the label to be updated is missing, 404 is returned. +If parameters are invalid, 405 is returned. In case of an error, +additionally an error message is returned. diff --git a/doc/api/projects.md b/doc/api/projects.md index b8876e8e104bd3ed707e7b4b2c2de23145f65ab7..894c2fd76a475ac9da3e6982a3a1e37311e4d967 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -632,29 +632,3 @@ Parameters: + query (required) - A string contained in the project name + per_page (optional) - number of projects to return per page + page (optional) - the page to retrieve - - -## Labels - -### List project labels - -Get a list of project labels. - -``` -GET /projects/:id/labels -``` - -Parameters: - -+ `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project - -```json -[ - { - "name": "feature" - }, - { - "name": "bug" - } -] -``` diff --git a/lib/api/api.rb b/lib/api/api.rb index ce4cc8b34f7e22cc7fdf7ed2942befab4b71edf7..2c7cd9038c3504d33abe0e9a5104a45e5a5b73f7 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -46,5 +46,6 @@ class API < Grape::API mount Commits mount Namespaces mount Branches + mount Labels end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index fc7d391fd30e94f02c56606ddc86fb0e1909b269..74fdef935433987fd48a8af7448c2476827aa897 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -194,7 +194,7 @@ class ProjectWithAccess < Project end class Label < Grape::Entity - expose :name + expose :name, :color end class RepoDiff < Grape::Entity diff --git a/lib/api/labels.rb b/lib/api/labels.rb new file mode 100644 index 0000000000000000000000000000000000000000..c73a4dbe916b941588aec127c8b2b0694f10168a --- /dev/null +++ b/lib/api/labels.rb @@ -0,0 +1,100 @@ +module API + # Labels API + class Labels < Grape::API + before { authenticate! } + + resource :projects do + # Get all labels of the project + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id/labels + get ':id/labels' do + present user_project.labels, with: Entities::Label + end + + # Creates a new label + # + # Parameters: + # id (required) - The ID of a project + # name (required) - The name of the label to be deleted + # color (required) - Color of the label given in 6-digit hex + # notation with leading '#' sign (e.g. #FFAABB) + # Example Request: + # POST /projects/:id/labels + post ':id/labels' do + required_attributes! [:name, :color] + + attrs = attributes_for_keys [:name, :color] + label = user_project.find_label(attrs[:name]) + + if label + return render_api_error!('Label already exists', 409) + end + + label = user_project.labels.create(attrs) + + if label.valid? + present label, with: Entities::Label + else + render_api_error!(label.errors.full_messages.join(', '), 405) + end + end + + # Deletes an existing label + # + # Parameters: + # id (required) - The ID of a project + # name (required) - The name of the label to be deleted + # + # Example Request: + # DELETE /projects/:id/labels + delete ':id/labels' do + required_attributes! [:name] + + label = user_project.find_label(params[:name]) + if !label + return render_api_error!('Label not found', 404) + end + + label.destroy + end + + # Updates an existing label. At least one optional parameter is required. + # + # Parameters: + # id (required) - The ID of a project + # name (optional) - The name of the label to be deleted + # color (optional) - Color of the label given in 6-digit hex + # notation with leading '#' sign (e.g. #FFAABB) + # Example Request: + # PUT /projects/:id/labels + put ':id/labels' do + required_attributes! [:name] + + label = user_project.find_label(params[:name]) + if !label + return render_api_error!('Label not found', 404) + end + + attrs = attributes_for_keys [:new_name, :color] + + if attrs.empty? + return render_api_error!('Required parameters "name" or "color" ' \ + 'missing', + 400) + end + + # Rename new name to the actual label attribute name + attrs[:name] = attrs.delete(:new_name) if attrs.key?(:new_name) + + if label.update(attrs) + present label, with: Entities::Label + else + render_api_error!(label.errors.full_messages.join(', '), 405) + end + end + end + end +end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 149678e680307a8ea3a54697aa9f263809a31170..55f7975bbf776f80a3dd0280c9ce4a92441729d8 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -224,17 +224,6 @@ def map_public_to_visibility_level(attrs) @users = paginate @users present @users, with: Entities::UserBasic end - - # Get a project labels - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/labels - get ':id/labels' do - @labels = user_project.labels - present @labels, with: Entities::Label - end end end end diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index d40c2c21cec339c9492547c06c313baa418ae939..b06b353333de065610f5b872a2167898546070de 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -21,4 +21,131 @@ json_response.first['name'].should == label1.name end end + + describe 'POST /projects/:id/labels' do + it 'should return created label' do + post api("/projects/#{project.id}/labels", user), + name: 'Foo', + color: '#FFAABB' + response.status.should == 201 + json_response['name'].should == 'Foo' + json_response['color'].should == '#FFAABB' + end + + it 'should return a 400 bad request if name not given' do + post api("/projects/#{project.id}/labels", user), color: '#FFAABB' + response.status.should == 400 + end + + it 'should return a 400 bad request if color not given' do + post api("/projects/#{project.id}/labels", user), name: 'Foobar' + response.status.should == 400 + end + + it 'should return 405 for invalid color' do + post api("/projects/#{project.id}/labels", user), + name: 'Foo', + color: '#FFAA' + response.status.should == 405 + json_response['message'].should == 'Color is invalid' + end + + it 'should return 405 for invalid name' do + post api("/projects/#{project.id}/labels", user), + name: '?', + color: '#FFAABB' + response.status.should == 405 + json_response['message'].should == 'Title is invalid' + end + + it 'should return 409 if label already exists' do + post api("/projects/#{project.id}/labels", user), + name: 'label1', + color: '#FFAABB' + response.status.should == 409 + json_response['message'].should == 'Label already exists' + end + end + + describe 'DELETE /projects/:id/labels' do + it 'should return 200 for existing label' do + delete api("/projects/#{project.id}/labels", user), name: 'label1' + response.status.should == 200 + end + + it 'should return 404 for non existing label' do + delete api("/projects/#{project.id}/labels", user), name: 'label2' + response.status.should == 404 + json_response['message'].should == 'Label not found' + end + + it 'should return 400 for wrong parameters' do + delete api("/projects/#{project.id}/labels", user) + response.status.should == 400 + end + end + + describe 'PUT /projects/:id/labels' do + it 'should return 200 if name and colors are changed' do + put api("/projects/#{project.id}/labels", user), + name: 'label1', + new_name: 'New Label', + color: '#FFFFFF' + response.status.should == 200 + json_response['name'].should == 'New Label' + json_response['color'].should == '#FFFFFF' + end + + it 'should return 200 if name is changed' do + put api("/projects/#{project.id}/labels", user), + name: 'label1', + new_name: 'New Label' + response.status.should == 200 + json_response['name'].should == 'New Label' + json_response['color'].should == label1.color + end + + it 'should return 200 if colors is changed' do + put api("/projects/#{project.id}/labels", user), + name: 'label1', + color: '#FFFFFF' + response.status.should == 200 + json_response['name'].should == label1.name + json_response['color'].should == '#FFFFFF' + end + + it 'should return 404 if label does not exist' do + put api("/projects/#{project.id}/labels", user), + name: 'label2', + new_name: 'label3' + response.status.should == 404 + end + + it 'should return 400 if no label name given' do + put api("/projects/#{project.id}/labels", user), new_name: 'label2' + response.status.should == 400 + end + + it 'should return 400 if no new parameters given' do + put api("/projects/#{project.id}/labels", user), name: 'label1' + response.status.should == 400 + end + + it 'should return 405 for invalid name' do + put api("/projects/#{project.id}/labels", user), + name: 'label1', + new_name: '?', + color: '#FFFFFF' + response.status.should == 405 + json_response['message'].should == 'Title is invalid' + end + + it 'should return 405 for invalid name' do + put api("/projects/#{project.id}/labels", user), + name: 'label1', + color: '#FF' + response.status.should == 405 + json_response['message'].should == 'Color is invalid' + end + end end