Commit 433dac77 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge pull request #7465 from Razer6/better_label_color_validation

Better label color validation, fixes #7454
parents 7120af72 cbc90565
...@@ -53,15 +53,40 @@ window.split = (val) -> ...@@ -53,15 +53,40 @@ window.split = (val) ->
window.extractLast = (term) -> window.extractLast = (term) ->
return split( term ).pop() return split( term ).pop()
window.rstrip = (val) ->
return val.replace(/\s+$/, '')
# Disable button if text field is empty # Disable button if text field is empty
window.disableButtonIfEmptyField = (field_selector, button_selector) -> window.disableButtonIfEmptyField = (field_selector, button_selector) ->
field = $(field_selector) field = $(field_selector)
closest_submit = field.closest("form").find(button_selector) closest_submit = field.closest('form').find(button_selector)
closest_submit.disable() if rstrip(field.val()) is ""
field.on 'input', ->
if rstrip($(@).val()) is ""
closest_submit.disable()
else
closest_submit.enable()
# Disable button if any input field with given selector is empty
window.disableButtonIfAnyEmptyField = (form, form_selector, button_selector) ->
closest_submit = form.find(button_selector)
empty = false
form.find('input').filter(form_selector).each ->
empty = true if rstrip($(this).val()) is ""
if empty
closest_submit.disable()
else
closest_submit.enable()
closest_submit.disable() if field.val().replace(/\s+$/, "") is "" form.keyup ->
empty = false
form.find('input').filter(form_selector).each ->
empty = true if rstrip($(this).val()) is ""
field.on "input", -> if empty
if $(@).val().replace(/\s+$/, "") is ""
closest_submit.disable() closest_submit.disable()
else else
closest_submit.enable() closest_submit.enable()
......
...@@ -50,6 +50,8 @@ class Dispatcher ...@@ -50,6 +50,8 @@ class Dispatcher
new TreeView() new TreeView()
when 'projects:blob:show' when 'projects:blob:show'
new BlobView() new BlobView()
when 'projects:labels:new'
new Labels()
switch path.first() switch path.first()
when 'admin' then new Admin() when 'admin' then new Admin()
......
class Labels
constructor: ->
form = $('.label-form')
@setupLabelForm(form)
@cleanBinding()
@addBinding()
@updateColorPreview
addBinding: ->
$(document).on 'click', '.suggest-colors a', @setSuggestedColor
$(document).on 'input', 'input#label_color', @updateColorPreview
cleanBinding: ->
$(document).off 'click', '.suggest-colors a'
$(document).off 'input', 'input#label_color'
# Initializes the form to disable the save button if no color or title is entered
setupLabelForm: (form) ->
disableButtonIfAnyEmptyField form, '.form-control', form.find('.js-save-button')
# Updates the the preview color with the hex-color input
updateColorPreview: =>
previewColor = $('input#label_color').val()
$('div.label-color-preview').css('background-color', previewColor)
# Updates the preview color with a click on a suggested color
setSuggestedColor: (e) =>
color = $(e.currentTarget).data('color')
$('input#label_color').val(color)
@updateColorPreview()
# Notify the form, that color has changed
$('.label-form').trigger('keyup')
e.preventDefault()
@Labels = Labels
...@@ -140,7 +140,8 @@ module Issuable ...@@ -140,7 +140,8 @@ module Issuable
def add_labels_by_names(label_names) def add_labels_by_names(label_names)
label_names.each do |label_name| label_names.each do |label_name|
label = project.labels.find_or_create_by(title: label_name.strip) label = project.labels.create_with(
color: Label::DEFAULT_COLOR).find_or_create_by(title: label_name.strip)
self.labels << label self.labels << label
end end
end end
......
class Label < ActiveRecord::Base class Label < ActiveRecord::Base
DEFAULT_COLOR = '#82C5FF'
belongs_to :project belongs_to :project
has_many :label_links, dependent: :destroy has_many :label_links, dependent: :destroy
has_many :issues, through: :label_links, source: :target, source_type: 'Issue' has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
validates :color, format: { with: /\A\#[0-9A-Fa-f]{6}+\Z/ }, allow_blank: true validates :color,
format: { with: /\A\#[0-9A-Fa-f]{6}+\Z/ },
allow_blank: false
validates :project, presence: true validates :project, presence: true
# Dont allow '?', '&', and ',' for label titles # Don't allow '?', '&', and ',' for label titles
validates :title, presence: true, format: { with: /\A[^&\?,&]*\z/ } validates :title,
presence: true,
format: { with: /\A[^&\?,&]*\z/ },
uniqueness: { scope: :project_id }
scope :order_by_name, -> { reorder("labels.title ASC") } scope :order_by_name, -> { reorder("labels.title ASC") }
......
...@@ -28,22 +28,6 @@ ...@@ -28,22 +28,6 @@
&nbsp; &nbsp;
.form-actions .form-actions
= f.submit 'Save', class: 'btn btn-save' = f.submit 'Save', class: 'btn btn-save js-save-button'
= link_to "Cancel", project_labels_path(@project), class: 'btn btn-cancel' = link_to "Cancel", project_labels_path(@project), class: 'btn btn-cancel'
:coffeescript
updateColorPreview = ->
previewColor = $('input#label_color').val()
$('div.label-color-preview').css('background-color', previewColor)
$('.suggest-colors a').on 'click', (e) ->
color = $(this).data("color")
$('input#label_color').val(color)
updateColorPreview()
e.preventDefault()
$('input#label_color').on 'input', ->
updateColorPreview()
updateColorPreview()
...@@ -157,6 +157,9 @@ Parameters: ...@@ -157,6 +157,9 @@ Parameters:
- `milestone_id` (optional) - The ID of a milestone to assign issue - `milestone_id` (optional) - The ID of a milestone to assign issue
- `labels` (optional) - Comma-separated label names for an issue - `labels` (optional) - Comma-separated label names for an issue
If the operation is successful, 200 and the newly created issue is returned.
If an error occurs, an error number and a message explaining the reason is returned.
## Edit issue ## Edit issue
Updates an existing project issue. This function is also used to mark an issue as closed. Updates an existing project issue. This function is also used to mark an issue as closed.
...@@ -176,6 +179,9 @@ Parameters: ...@@ -176,6 +179,9 @@ Parameters:
- `labels` (optional) - Comma-separated label names for an issue - `labels` (optional) - Comma-separated label names for an issue
- `state_event` (optional) - The state event of an issue ('close' to close issue and 'reopen' to reopen it) - `state_event` (optional) - The state event of an issue ('close' to close issue and 'reopen' to reopen it)
If the operation is successful, 200 and the updated issue is returned.
If an error occurs, an error number and a message explaining the reason is returned.
## Delete existing issue (**Deprecated**) ## Delete existing issue (**Deprecated**)
The function is deprecated and returns a `405 Method Not Allowed` error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with parameter `closed` set to 1. The function is deprecated and returns a `405 Method Not Allowed` error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with parameter `closed` set to 1.
......
...@@ -139,6 +139,9 @@ Parameters: ...@@ -139,6 +139,9 @@ Parameters:
} }
``` ```
If the operation is successful, 200 and the newly created merge request is returned.
If an error occurs, an error number and a message explaining the reason is returned.
## Update MR ## Update MR
Updates an existing merge request. You can change branches, title, or even close the MR. Updates an existing merge request. You can change branches, title, or even close the MR.
...@@ -186,15 +189,18 @@ Parameters: ...@@ -186,15 +189,18 @@ Parameters:
} }
``` ```
If the operation is successful, 200 and the updated merge request is returned.
If an error occurs, an error number and a message explaining the reason is returned.
## Accept MR ## Accept MR
Merge changes submitted with MR usign this API. Merge changes submitted with MR using this API.
If merge success you get 200 OK. If merge success you get 200 OK.
If it has some conflicts and can not be merged - you get 405 and error message 'Branch cannot be merged' If it has some conflicts and can not be merged - you get 405 and error message 'Branch cannot be merged'
If merge request is already merged or closed - you get 405 and error message 'Method Not Allowed' If merge request is already merged or closed - you get 405 and error message 'Method Not Allowed'
If you dont have permissions to accept this merge request - you get 401 If you dont have permissions to accept this merge request - you get 401
......
...@@ -10,7 +10,7 @@ Feature: Project Labels ...@@ -10,7 +10,7 @@ Feature: Project Labels
And I should see label "feature" And I should see label "feature"
Scenario: I create new label Scenario: I create new label
Given I visit new label page Given I visit project "Shop" new label page
When I submit new label 'support' When I submit new label 'support'
Then I should see label 'support' Then I should see label 'support'
...@@ -23,3 +23,21 @@ Feature: Project Labels ...@@ -23,3 +23,21 @@ Feature: Project Labels
Scenario: I remove label Scenario: I remove label
When I remove label 'bug' When I remove label 'bug'
Then I should not see label 'bug' Then I should not see label 'bug'
Scenario: I create a label with invalid color
Given I visit project "Shop" new label page
When I submit new label with invalid color
Then I should see label color error message
Scenario: I create a label that already exists
Given I visit project "Shop" new label page
When I submit new label 'bug'
Then I should see label label exist error message
Scenario: I create the same label on another project
Given I own project "Forum"
And I visit project "Forum" labels page
And I visit project "Forum" new label page
When I submit new label 'bug'
Then I should see label 'bug'
...@@ -31,6 +31,36 @@ class ProjectLabels < Spinach::FeatureSteps ...@@ -31,6 +31,36 @@ class ProjectLabels < Spinach::FeatureSteps
click_button 'Save' click_button 'Save'
end end
step 'I submit new label \'bug\'' do
fill_in 'Title', with: 'bug'
fill_in 'Background Color', with: '#F95610'
click_button 'Save'
end
step 'I submit new label with invalid color' do
fill_in 'Title', with: 'support'
fill_in 'Background Color', with: '#12'
click_button 'Save'
end
step 'I should see label label exist error message' do
within '.label-form' do
page.should have_content 'Title has already been taken'
end
end
step 'I should see label color error message' do
within '.label-form' do
page.should have_content 'Color is invalid'
end
end
step 'I should see label \'bug\'' do
within '.manage-labels-list' do
page.should have_content 'bug'
end
end
step 'I should not see label \'bug\'' do step 'I should not see label \'bug\'' do
within '.manage-labels-list' do within '.manage-labels-list' do
page.should_not have_content 'bug' page.should_not have_content 'bug'
......
...@@ -287,10 +287,22 @@ module SharedPaths ...@@ -287,10 +287,22 @@ module SharedPaths
end end
step 'I visit project "Shop" labels page' do step 'I visit project "Shop" labels page' do
project = Project.find_by(name: 'Shop')
visit project_labels_path(project) visit project_labels_path(project)
end end
step 'I visit new label page' do step 'I visit project "Forum" labels page' do
project = Project.find_by(name: 'Forum')
visit project_labels_path(project)
end
step 'I visit project "Shop" new label page' do
project = Project.find_by(name: 'Shop')
visit new_project_label_path(project)
end
step 'I visit project "Forum" new label page' do
project = Project.find_by(name: 'Forum')
visit new_project_label_path(project) visit new_project_label_path(project)
end end
......
...@@ -112,6 +112,21 @@ module API ...@@ -112,6 +112,21 @@ module API
ActionController::Parameters.new(attrs).permit! ActionController::Parameters.new(attrs).permit!
end end
# Helper method for validating all labels against its names
def validate_label_params(params)
if params[:labels].present?
params[:labels].split(',').each do |label_name|
label = user_project.labels.create_with(
color: Label::DEFAULT_COLOR).find_or_initialize_by(
title: label_name.strip)
if label.invalid?
return true
end
end
end
false
end
# error helpers # error helpers
def forbidden! def forbidden!
......
...@@ -51,12 +51,18 @@ module API ...@@ -51,12 +51,18 @@ module API
required_attributes! [:title] required_attributes! [:title]
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id] attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
# Validate label names in advance
if validate_label_params(params)
return render_api_error!('Label names invalid', 405)
end
issue = ::Issues::CreateService.new(user_project, current_user, attrs).execute issue = ::Issues::CreateService.new(user_project, current_user, attrs).execute
if issue.valid? if issue.valid?
# Find or create labels and attach to issue # Find or create labels and attach to issue. Labels are valid because
# we already checked its name, so there can't be an error here
if params[:labels].present? if params[:labels].present?
issue.add_labels_by_names(params[:labels].split(",")) issue.add_labels_by_names(params[:labels].split(','))
end end
present issue, with: Entities::Issue present issue, with: Entities::Issue
...@@ -83,12 +89,19 @@ module API ...@@ -83,12 +89,19 @@ module API
authorize! :modify_issue, issue authorize! :modify_issue, issue
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event] attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
# Validate label names in advance
if validate_label_params(params)
return render_api_error!('Label names invalid', 405)
end
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?
# Find or create labels and attach to issue # Find or create labels and attach to issue. Labels are valid because
# we already checked its name, so there can't be an error here
if params[:labels].present? if params[:labels].present?
issue.add_labels_by_names(params[:labels].split(",")) # Create and add labels to the new created issue
issue.add_labels_by_names(params[:labels].split(','))
end end
present issue, with: Entities::Issue present issue, with: Entities::Issue
......
...@@ -76,6 +76,12 @@ module API ...@@ -76,6 +76,12 @@ module API
authorize! :write_merge_request, user_project authorize! :write_merge_request, user_project
required_attributes! [:source_branch, :target_branch, :title] required_attributes! [:source_branch, :target_branch, :title]
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description] attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description]
# Validate label names in advance
if validate_label_params(params)
return render_api_error!('Label names invalid', 405)
end
merge_request = ::MergeRequests::CreateService.new(user_project, current_user, attrs).execute merge_request = ::MergeRequests::CreateService.new(user_project, current_user, attrs).execute
if merge_request.valid? if merge_request.valid?
...@@ -109,6 +115,12 @@ module API ...@@ -109,6 +115,12 @@ module API
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description] attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description]
merge_request = user_project.merge_requests.find(params[:merge_request_id]) merge_request = user_project.merge_requests.find(params[:merge_request_id])
authorize! :modify_merge_request, merge_request authorize! :modify_merge_request, merge_request
# Validate label names in advance
if validate_label_params(params)
return render_api_error!('Label names invalid', 405)
end
merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request) merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request)
if merge_request.valid? if merge_request.valid?
......
...@@ -5,6 +5,10 @@ describe API::API, api: true do ...@@ -5,6 +5,10 @@ describe API::API, api: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) } let!(:project) { create(:project, namespace: user.namespace ) }
let!(:issue) { create(:issue, author: user, assignee: user, project: project) } let!(:issue) { create(:issue, author: user, assignee: user, project: project) }
let!(:label) do
create(:label, title: 'label', color: '#FFAABB', project: project)
end
before { project.team << [user, :reporter] } before { project.team << [user, :reporter] }
describe "GET /issues" do describe "GET /issues" do
...@@ -68,6 +72,14 @@ describe API::API, api: true do ...@@ -68,6 +72,14 @@ describe API::API, api: true do
post api("/projects/#{project.id}/issues", user), labels: 'label, label2' post api("/projects/#{project.id}/issues", user), labels: 'label, label2'
response.status.should == 400 response.status.should == 400
end end
it 'should return 405 on invalid label names' do
post api("/projects/#{project.id}/issues", user),
title: 'new issue',
labels: 'label, ?'
response.status.should == 405
json_response['message'].should == 'Label names invalid'
end
end end
describe "PUT /projects/:id/issues/:issue_id to update only title" do describe "PUT /projects/:id/issues/:issue_id to update only title" do
...@@ -84,6 +96,14 @@ describe API::API, api: true do ...@@ -84,6 +96,14 @@ describe API::API, api: true do
title: 'updated title' title: 'updated title'
response.status.should == 404 response.status.should == 404
end end
it 'should return 405 on invalid label names' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
title: 'updated title',
labels: 'label, ?'
response.status.should == 405
json_response['message'].should == 'Label names invalid'
end
end end
describe "PUT /projects/:id/issues/:issue_id to update state and label" do describe "PUT /projects/:id/issues/:issue_id to update state and label" do
......
...@@ -78,9 +78,14 @@ describe API::API, api: true do ...@@ -78,9 +78,14 @@ describe API::API, api: true do
context 'between branches projects' do context 'between branches projects' do
it "should return merge_request" do it "should return merge_request" do
post api("/projects/#{project.id}/merge_requests", user), post api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user title: 'Test merge_request',
source_branch: 'stable',
target_branch: 'master',
author: user,
labels: 'label, label2'
response.status.should == 201 response.status.should == 201
json_response['title'].should == 'Test merge_request' json_response['title'].should == 'Test merge_request'
json_response['labels'].should == ['label', 'label2']
end end
it "should return 422 when source_branch equals target_branch" do it "should return 422 when source_branch equals target_branch" do
...@@ -106,6 +111,17 @@ describe API::API, api: true do ...@@ -106,6 +111,17 @@ describe API::API, api: true do
target_branch: 'master', source_branch: 'stable' target_branch: 'master', source_branch: 'stable'
response.status.should == 400 response.status.should == 400
end end
it 'should return 405 on invalid label names' do
post api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request',
source_branch: 'stable',
target_branch: 'master',
author: user,
labels: 'label, ?'
response.status.should == 405
json_response['message'].should == 'Label names invalid'
end
end end
context 'forked projects' do context 'forked projects' do
...@@ -235,6 +251,15 @@ describe API::API, api: true do ...@@ -235,6 +251,15 @@ describe API::API, api: true do
response.status.should == 200 response.status.should == 200
json_response['target_branch'].should == 'wiki' json_response['target_branch'].should == 'wiki'
end end
it 'should return 405 on invalid label names' do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}",
user),
title: 'new issue',
labels: 'label, ?'
response.status.should == 405
json_response['message'].should == 'Label names invalid'
end
end end
describe "POST /projects/:id/merge_request/:merge_request_id/comments" do describe "POST /projects/:id/merge_request/:merge_request_id/comments" do
......
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