Commit 86f1f833 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'epic/refactor-labels' into 'master'

Epic/refactor labels

Part of #1385

See merge request !1003
parents 4c8cbf88 d9d8d3b7
......@@ -94,7 +94,7 @@
}
}
.issue-show-labels .label {
.issue-show-labels .color-label {
padding: 6px 10px;
}
......
.suggest-colors {
margin-top: 5px;
a {
@include border-radius(4px);
width: 30px;
height: 30px;
display: inline-block;
margin-right: 10px;
}
}
.manage-labels-list {
.label {
padding: 9px;
font-size: 14px;
}
}
.color-label {
padding: 3px 4px;
}
......@@ -46,11 +46,11 @@ class DashboardController < ApplicationController
@projects = @projects.where(namespace_id: Group.find_by(name: params[:group])) if params[:group].present?
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = @projects.includes(:namespace)
@projects = @projects.tagged_with(params[:label]) if params[:label].present?
@projects = @projects.tagged_with(params[:tag]) if params[:tag].present?
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]).per(30)
@labels = current_user.authorized_projects.tags_on(:labels)
@tags = current_user.authorized_projects.tags_on(:tags)
@groups = current_user.authorized_groups
end
......
......@@ -152,7 +152,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue_params
params.require(:issue).permit(
:title, :assignee_id, :position, :description,
:milestone_id, :label_list, :state_event
:milestone_id, :state_event, label_ids: []
)
end
end
class Projects::LabelsController < Projects::ApplicationController
before_filter :module_enabled
before_filter :label, only: [:edit, :update, :destroy]
before_filter :authorize_labels!
before_filter :authorize_admin_labels!, except: [:index]
respond_to :js, :html
def index
@labels = @project.issues_labels
@labels = @project.labels.order('title ASC').page(params[:page]).per(20)
end
def new
@label = @project.labels.new
end
def create
@label = @project.labels.create(label_params)
if @label.valid?
redirect_to project_labels_path(@project)
else
render 'new'
end
end
def edit
end
def update
if @label.update_attributes(label_params)
redirect_to project_labels_path(@project)
else
render 'edit'
end
end
def generate
......@@ -21,6 +47,12 @@ class Projects::LabelsController < Projects::ApplicationController
end
end
def destroy
@label.destroy
redirect_to project_labels_path(@project), notice: 'Label was removed'
end
protected
def module_enabled
......@@ -28,4 +60,16 @@ class Projects::LabelsController < Projects::ApplicationController
return render_404
end
end
def label_params
params.require(:label).permit(:title, :color)
end
def label
@label = @project.labels.find(params[:id])
end
def authorize_admin_labels!
return render_404 unless can?(current_user, :admin_label, @project)
end
end
......@@ -242,7 +242,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
params.require(:merge_request).permit(
:title, :assignee_id, :source_project_id, :source_branch,
:target_project_id, :target_branch, :milestone_id,
:state_event, :description, :label_list
:state_event, :description, label_ids: []
)
end
end
......@@ -196,7 +196,7 @@ class ProjectsController < ApplicationController
def project_params
params.require(:project).permit(
:name, :path, :description, :issues_tracker, :label_list,
:name, :path, :description, :issues_tracker, :tag_list,
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id
)
......
......@@ -125,7 +125,13 @@ class BaseFinder
def by_label(items)
if params[:label_name].present?
items = items.tagged_with(params[:label_name])
label_names = params[:label_name].split(",")
item_ids = LabelLink.joins(:label).
where('labels.title in (?)', label_names).
where(target_type: klass.name).pluck(:target_id)
items = items.where(id: item_ids)
end
items
......
module LabelsHelper
def issue_label_names
@project.issues_labels.map(&:name)
def project_label_names
@project.labels.pluck(:title)
end
def labels_autocomplete_source
labels = @project.issues_labels
labels = labels.map{ |l| { label: l.name, value: l.name } }
labels.to_json
def render_colored_label(label)
label_color = label.color || "#428bca"
text_color = text_color_for_bg(label_color)
content_tag :span, class: 'label color-label', style: "background:#{label_color};color:#{text_color}" do
label.name
end
end
def suggested_colors
[
'#d9534f',
'#f0ad4e',
'#428bca',
'#5cb85c',
'#34495e',
'#7f8c8d',
'#8e44ad',
'#FFECDB'
]
end
def label_css_class(name)
klass = Gitlab::IssuesLabels
def text_color_for_bg(bg_color)
r, g, b = bg_color.slice(1,7).scan(/.{2}/).map(&:hex)
case name.downcase
when *klass.warning_labels
'label-warning'
when *klass.neutral_labels
'label-primary'
when *klass.positive_labels
'label-success'
when *klass.important_labels
'label-danger'
if (r + g + b) > 500
"#333"
else
'label-info'
"#FFF"
end
end
end
......@@ -142,6 +142,7 @@ class Ability
:write_wiki,
:modify_issue,
:admin_issue,
:admin_label,
:push_code
]
end
......
......@@ -13,6 +13,8 @@ module Issuable
belongs_to :assignee, class_name: "User"
belongs_to :milestone
has_many :notes, as: :noteable, dependent: :destroy
has_many :label_links, as: :target, dependent: :destroy
has_many :labels, through: :label_links
validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 }
......@@ -131,4 +133,15 @@ module Issuable
object_attributes: self.attributes
}
end
def label_names
labels.order('title ASC').pluck(:title)
end
def add_labels_by_names(label_names)
label_names.each do |label_name|
label = project.labels.find_or_create_by(title: label_name.strip)
self.labels << label
end
end
end
......@@ -32,9 +32,6 @@ class Issue < ActiveRecord::Base
scope :of_group, ->(group) { where(project_id: group.project_ids) }
scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
acts_as_taggable_on :labels
scope :cared, ->(user) { where(assignee_id: user) }
scope :open_for, ->(user) { opened.assigned_to(user) }
......
class Label < ActiveRecord::Base
belongs_to :project
has_many :label_links, dependent: :destroy
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 :project, presence: true
# Dont allow '?', '&', and ',' for label titles
validates :title, presence: true, format: { with: /\A[^&\?,&]*\z/ }
def name
title
end
def open_issues_count
issues.opened.count
end
end
class LabelLink < ActiveRecord::Base
belongs_to :target, polymorphic: true
belongs_to :label
validates :target, presence: true
validates :label, presence: true
end
......@@ -47,9 +47,6 @@ class MergeRequest < ActiveRecord::Base
attr_accessor :can_be_created, :compare_failed,
:compare_commits, :compare_diffs
ActsAsTaggableOn.strict_case_match = true
acts_as_taggable_on :labels
state_machine :state, initial: :opened do
event :close do
transition [:reopened, :opened] => :closed
......
......@@ -41,8 +41,7 @@ class Project < ActiveRecord::Base
default_value_for :snippets_enabled, gitlab_config_features.snippets
ActsAsTaggableOn.strict_case_match = true
acts_as_taggable_on :labels, :issues_default_labels
acts_as_taggable_on :tags
attr_accessor :new_default_branch
......@@ -71,6 +70,7 @@ class Project < ActiveRecord::Base
# Merge requests from source project should be kept when source project was removed
has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest
has_many :issues, -> { order "state DESC, created_at DESC" }, dependent: :destroy
has_many :labels, dependent: :destroy
has_many :services, dependent: :destroy
has_many :events, dependent: :destroy
has_many :milestones, dependent: :destroy
......@@ -282,13 +282,6 @@ class Project < ActiveRecord::Base
self.id
end
# Tags are shared by issues and merge requests
def issues_labels
@issues_labels ||= (issues_default_labels +
merge_requests.tags_on(:labels) +
issues.tags_on(:labels)).uniq.sort_by(&:name)
end
def issue_exists?(issue_id)
if used_default_issues_tracker?
self.issues.where(iid: issue_id).first.present?
......
......@@ -44,12 +44,12 @@
- if @labels.present?
- if @tags.present?
%fieldset
%legend Labels
%legend Tags
%ul.nav.nav-pills.nav-stacked.nav-small
- @labels.each do |label|
%li{ class: (label.name == params[:label]) ? 'active' : 'light' }
= link_to projects_dashboard_filter_path(scope: params[:scope], label: label.name) do
- @tags.each do |tag|
%li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
= link_to projects_dashboard_filter_path(scope: params[:scope], tag: tag.name) do
%i.icon-tag
= label.name
= tag.name
......@@ -54,10 +54,10 @@
%span.label
%i.icon-archive
Archived
- project.labels.each do |label|
- project.tags.each do |tag|
%span.label.label-info
%i.icon-tag
= label.name
= tag.name
- if project.description.present?
%p= truncate project.description, length: 100
.last-activity
......
......@@ -33,12 +33,12 @@
%fieldset.features
%legend
Labels:
Tags:
.form-group
= f.label :label_list, "Labels", class: 'control-label'
= f.label :tag_list, "Tags", class: 'control-label'
.col-sm-10
= f.text_field :label_list, maxlength: 2000, class: "form-control"
%p.hint Separate labels with commas.
= f.text_field :tag_list, maxlength: 2000, class: "form-control"
%p.hint Separate tags with commas.
%fieldset.features
%legend
......
......@@ -44,14 +44,11 @@
.col-sm-10= f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2'})
.form-group
= f.label :label_list, class: 'control-label' do
= f.label :label_ids, class: 'control-label' do
%i.icon-tag
Labels
.col-sm-10
= f.text_field :label_list, maxlength: 2000, class: "form-control"
%p.hint Separate labels with commas.
= f.collection_select :label_ids, @project.labels.all, :id, :name, { selected: @issue.label_ids }, multiple: true, class: 'select2'
.form-actions
- if @issue.new_record?
......@@ -63,35 +60,6 @@
= link_to "Cancel", cancel_path, class: 'btn btn-cancel'
:javascript
$("#issue_label_list")
.bind( "keydown", function( event ) {
if ( event.keyCode === $.ui.keyCode.TAB &&
$( this ).data( "autocomplete" ).menu.active ) {
event.preventDefault();
}
})
.bind("click", function(event) {
$(this).autocomplete("search", "");
})
.autocomplete({
minLength: 0,
source: function( request, response ) {
response( $.ui.autocomplete.filter(
#{raw labels_autocomplete_source}, extractLast( request.term ) ) );
},
focus: function() {
return false;
},
select: function(event, ui) {
var terms = split( this.value );
terms.pop();
terms.push( ui.item.value );
terms.push( "" );
this.value = terms.join( ", " );
return false;
}
});
$('.assign-to-me-link').on('click', function(e){
$('#issue_assignee_id').val("#{current_user.id}").trigger("change");
e.preventDefault();
......
......@@ -31,9 +31,7 @@
.issue-labels
- issue.labels.each do |label|
%span{class: "label #{label_css_class(label.name)}"}
%i.icon-tag
= label.name
= render_colored_label(label)
.issue-actions
- if can? current_user, :modify_issue, issue
......
......@@ -68,9 +68,6 @@
.issue-show-labels.pull-right
- @issue.labels.each do |label|
%span{class: "label #{label_css_class(label.name)}"}
%i.icon-tag
= label.name
&nbsp;
= render_colored_label(label)
.voting_notes#notes= render "projects/notes/notes_with_form"
= form_for [@project, @label], html: { class: 'form-horizontal label-form' } do |f|
-if @label.errors.any?
.row
.col-sm-10.col-sm-offset-2
.bs-callout.bs-callout-danger
- @label.errors.full_messages.each do |msg|
%span= msg
%br
.form-group
= f.label :title, class: 'control-label'
.col-sm-10
= f.text_field :title, class: "form-control", required: true
.form-group
= f.label :color, "Background Color", class: 'control-label'
.col-sm-10
.input-group
.input-group-addon.label-color-preview &nbsp;
= f.text_field :color, placeholder: "#AA33EE", class: "form-control"
.help-block
6 character hex values starting with a # sign.
%br
Or you can choose one of suggested colors below
.suggest-colors
- suggested_colors.each do |color|
= link_to '#', style: "background-color: #{color}", data: { color: color } do
&nbsp;
.form-actions
= f.submit 'Save', class: 'btn btn-save'
: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()
- frequency = @project.issues.tagged_with(label.name).count
%li
%span{class: "label #{label_css_class(label.name)}"}
%i.icon-tag
- if frequency.zero?
%span.light= label.name
- else
= label.name
= render_colored_label(label)
.pull-right
- unless frequency.zero?
= link_to project_issues_path(label_name: label.name) do
= pluralize(frequency, 'issue')
= "»"
%strong.append-right-20
= link_to project_issues_path(@project, label_name: label.name) do
= pluralize label.open_issues_count, 'open issue'
- if can? current_user, :admin_label, @project
= link_to 'Edit', edit_project_label_path(@project, label), class: 'btn'
= link_to 'Remove', project_label_path(@project, label), class: 'btn btn-remove', method: :delete, data: {confirm: "Remove this label? Are you sure?"}
%h3
Edit label
%span.light #{@label.name}
.back-link
= link_to project_labels_path(@project) do
&larr; To labels list
%hr
= render 'form'
= render "projects/issues/head"
- if can? current_user, :admin_label, @project
= link_to new_project_label_path(@project), class: "pull-right btn btn-new" do
New label
%h3.page-title
Labels
%hr
- if @labels.present?
%ul.bordered-list.labels-table
- @labels.each do |label|
= render 'label', label: label
%ul.bordered-list.manage-labels-list
= render @labels
- else
.light-well
......
%h3 New label
.back-link
= link_to project_labels_path(@project) do
&larr; To labels list
%hr
= render 'form'
......@@ -46,14 +46,12 @@
.col-sm-10= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'})
- if @merge_request.persisted? # Only allow labels on edit to avoid fork vs upstream repo labels issue
.form-group
= f.label :label_list, class: 'control-label' do
= f.label :label_ids, class: 'control-label' do
%i.icon-tag
Labels
.col-sm-10
= f.text_field :label_list, maxlength: 2000, class: "form-control"
%p.hint Separate labels with commas.
= f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, { selected: @merge_request.label_ids }, multiple: true, class: 'select2'
.form-actions
- if @merge_request.new_record?
......@@ -74,33 +72,4 @@
e.preventDefault();
});
$("#merge_request_label_list")
.bind( "keydown", function( event ) {
if ( event.keyCode === $.ui.keyCode.TAB &&
$( this ).data( "autocomplete" ).menu.active ) {
event.preventDefault();
}
})
.bind("click", function(event) {
$(this).autocomplete("search", "");
})
.autocomplete({
minLength: 0,
source: function( request, response ) {
response( $.ui.autocomplete.filter(
#{raw labels_autocomplete_source}, extractLast( request.term ) ) );
},
focus: function() {
return false;
},
select: function(event, ui) {
var terms = split( this.value );
terms.pop();
terms.push( ui.item.value );
terms.push( "" );
this.value = terms.join( ", " );
return false;
}
});
window.project_image_path_upload = "#{upload_image_project_path @project}";
......@@ -34,6 +34,4 @@
.merge-request-labels
- merge_request.labels.each do |label|
%span{class: "label #{label_css_class(label.name)}"}
%i.icon-tag
= label.name
= render_colored_label(label)
......@@ -43,6 +43,13 @@
%i.icon-time
Milestone
%div= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'})
.form-group
= f.label :label_ids do
%i.icon-tag
Labels
%div
= f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, { selected: @merge_request.label_ids }, multiple: true, class: 'select2'
.panel-footer
- if contribution_guide_url(@target_project)
%p
......
......@@ -5,7 +5,4 @@
.merge-request-show-labels.pull-right
- @merge_request.labels.each do |label|
%span{class: "label #{label_css_class(label.name)}"}
%i.icon-tag
= label.name
&nbsp;
= render_colored_label(label)
......@@ -36,17 +36,15 @@
%fieldset
%legend Labels
%ul.nav.nav-pills.nav-stacked.nav-small.labels-filter
- issue_label_names.each do |label_name|
%li{class: label_filter_class(label_name)}
= link_to labels_filter_path(label_name) do
%span{class: "label #{label_css_class(label_name)}"}
%i.icon-tag
= label_name
- if selected_label?(label_name)
- @project.labels.each do |label|
%li{class: label_filter_class(label.name)}
= link_to labels_filter_path(label.name) do
= render_colored_label(label)
- if selected_label?(label.name)
.pull-right
%i.icon-remove
- if issue_label_names.empty?
- if @project.labels.empty?
.light-well
Add first label to your issues
%br
......
......@@ -297,7 +297,7 @@ Gitlab::Application.routes.draw do
end
end
resources :labels, only: [:index] do
resources :labels, constraints: {id: /\d+/} do
collection do
post :generate
end
......
class CreateLabels < ActiveRecord::Migration
def change
create_table :labels do |t|
t.string :title
t.string :color
t.integer :project_id
t.timestamps
end
end
end
class CreateLabelLinks < ActiveRecord::Migration
def change
create_table :label_links do |t|
t.integer :label_id
t.integer :target_id
t.string :target_type
t.timestamps
end
end
end
class MigrateProjectTags < ActiveRecord::Migration
def up
ActsAsTaggableOn::Tagging.where(taggable_type: 'Project', context: 'labels').update_all(context: 'tags')
end
def down
ActsAsTaggableOn::Tagging.where(taggable_type: 'Project', context: 'tags').update_all(context: 'labels')
end
end
class MigrateTaggableLabels < ActiveRecord::Migration
def up
taggings = ActsAsTaggableOn::Tagging.where(taggable_type: ['Issue', 'MergeRequest'], context: 'labels')
taggings.find_each(batch_size: 500) do |tagging|
create_label_from_tagging(tagging)
end
end
def down
Label.destroy_all
LabelLink.destroy_all
end
private
def create_label_from_tagging(tagging)
target = tagging.taggable
label_name = tagging.tag.name
label = target.project.labels.find_or_create_by(title: label_name)
if LabelLink.create(label: label, target: target)
print '.'
else
print 'F'
end
end
end
class AddIndexToLabels < ActiveRecord::Migration
def change
add_index "labels", :project_id
add_index "label_links", :label_id
add_index "label_links", [:target_id, :target_type]
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20140625115202) do
ActiveRecord::Schema.define(version: 20140730111702) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -109,6 +109,27 @@ ActiveRecord::Schema.define(version: 20140625115202) do
add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree
create_table "label_links", force: true do |t|
t.integer "label_id"
t.integer "target_id"
t.string "target_type"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "label_links", ["label_id"], name: "index_label_links_on_label_id", using: :btree
add_index "label_links", ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree
create_table "labels", force: true do |t|
t.string "title"
t.string "color"
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
create_table "merge_request_diffs", force: true do |t|
t.string "state"
t.text "st_commits"
......
......@@ -2,9 +2,10 @@ Feature: Project Filter Labels
Background:
Given I sign in as a user
And I own project "Shop"
And project "Shop" has issue "Bugfix1" with tags: "bug", "feature"
And project "Shop" has issue "Bugfix2" with tags: "bug", "enhancement"
And project "Shop" has issue "Feature1" with tags: "feature"
And project "Shop" has labels: "bug", "feature", "enhancement"
And project "Shop" has issue "Bugfix1" with labels: "bug", "feature"
And project "Shop" has issue "Bugfix2" with labels: "bug", "enhancement"
And project "Shop" has issue "Feature1" with labels: "feature"
Given I visit project "Shop" issues page
Scenario: I should see project issues
......@@ -18,9 +19,12 @@ Feature: Project Filter Labels
And I should see "Bugfix2" in issues list
And I should not see "Feature1" in issues list
Scenario: I filter by two labels
Given I click link "bug"
And I click link "feature"
Then I should see "Bugfix1" in issues list
And I should not see "Bugfix2" in issues list
And I should not see "Feature1" in issues list
# TODO: make labels filter works according to this scanario
# right now it looks for label 1 OR label 2. Old behaviour (this test) was
# all issues that have both label 1 AND label 2
#Scenario: I filter by two labels
#Given I click link "bug"
#And I click link "feature"
#Then I should see "Bugfix1" in issues list
#And I should not see "Bugfix2" in issues list
#And I should not see "Feature1" in issues list
......@@ -2,7 +2,7 @@ Feature: Project Labels
Background:
Given I sign in as a user
And I own project "Shop"
And project "Shop" have issues tags: "bug", "feature"
And project "Shop" has labels: "bug", "feature", "enhancement"
Given I visit project "Shop" labels page
Scenario: I should see active milestones
......
......@@ -3,68 +3,77 @@ class ProjectFilterLabels < Spinach::FeatureSteps
include SharedProject
include SharedPaths
Then 'I should see "bug" in labels filter' do
step 'I should see "bug" in labels filter' do
within ".labels-filter" do
page.should have_content "bug"
end
end
And 'I should see "feature" in labels filter' do
step 'I should see "feature" in labels filter' do
within ".labels-filter" do
page.should have_content "feature"
end
end
And 'I should see "enhancement" in labels filter' do
step 'I should see "enhancement" in labels filter' do
within ".labels-filter" do
page.should have_content "enhancement"
end
end
Then 'I should see "Bugfix1" in issues list' do
step 'I should see "Bugfix1" in issues list' do
within ".issues-list" do
page.should have_content "Bugfix1"
end
end
And 'I should see "Bugfix2" in issues list' do
step 'I should see "Bugfix2" in issues list' do
within ".issues-list" do
page.should have_content "Bugfix2"
end
end
And 'I should not see "Bugfix2" in issues list' do
step 'I should not see "Bugfix2" in issues list' do
within ".issues-list" do
page.should_not have_content "Bugfix2"
end
end
And 'I should not see "Feature1" in issues list' do
step 'I should not see "Feature1" in issues list' do
within ".issues-list" do
page.should_not have_content "Feature1"
end
end
Given 'I click link "bug"' do
step 'I click link "bug"' do
within ".labels-filter" do
click_link "bug"
end
end
Given 'I click link "feature"' do
step 'I click link "feature"' do
within ".labels-filter" do
click_link "feature"
end
end
And 'project "Shop" has issue "Bugfix1" with tags: "bug", "feature"' do
step 'project "Shop" has issue "Bugfix1" with labels: "bug", "feature"' do
project = Project.find_by(name: "Shop")
create(:issue, title: "Bugfix1", project: project, label_list: ['bug', 'feature'])
issue = create(:issue, title: "Bugfix1", project: project)
issue.labels << project.labels.find_by(title: 'bug')
issue.labels << project.labels.find_by(title: 'feature')
end
And 'project "Shop" has issue "Bugfix2" with tags: "bug", "enhancement"' do
step 'project "Shop" has issue "Bugfix2" with labels: "bug", "enhancement"' do
project = Project.find_by(name: "Shop")
create(:issue, title: "Bugfix2", project: project, label_list: ['bug', 'enhancement'])
issue = create(:issue, title: "Bugfix2", project: project)
issue.labels << project.labels.find_by(title: 'bug')
issue.labels << project.labels.find_by(title: 'enhancement')
end
And 'project "Shop" has issue "Feature1" with tags: "feature"' do
step 'project "Shop" has issue "Feature1" with labels: "feature"' do
project = Project.find_by(name: "Shop")
create(:issue, title: "Feature1", project: project, label_list: 'feature')
issue = create(:issue, title: "Feature1", project: project)
issue.labels << project.labels.find_by(title: 'feature')
end
end
......@@ -4,21 +4,14 @@ class ProjectLabels < Spinach::FeatureSteps
include SharedPaths
Then 'I should see label "bug"' do
within ".labels-table" do
within ".manage-labels-list" do
page.should have_content "bug"
end
end
And 'I should see label "feature"' do
within ".labels-table" do
within ".manage-labels-list" do
page.should have_content "feature"
end
end
And 'project "Shop" have issues tags: "bug", "feature"' do
project = Project.find_by(name: "Shop")
['bug', 'feature'].each do |label|
create(:issue, project: project, label_list: label)
end
end
end
......@@ -123,10 +123,6 @@ module SharedProject
project.team << [user, :master]
end
# ----------------------------------------
# Empty projects
# ----------------------------------------
step 'public empty project "Empty Public Project"' do
create :empty_project, :public, name: "Empty Public Project"
end
......@@ -135,4 +131,11 @@ module SharedProject
project = Project.find_by(name: "Community")
2.times { create(:note_on_issue, project: project) }
end
step 'project "Shop" has labels: "bug", "feature", "enhancement"' do
project = Project.find_by(name: "Shop")
create(:label, project: project, title: 'bug')
create(:label, project: project, title: 'feature')
create(:label, project: project, title: 'enhancement')
end
end
......@@ -126,7 +126,7 @@ module API
end
class Issue < ProjectEntity
expose :label_list, as: :labels
expose :label_names, as: :labels
expose :milestone, using: Entities::Milestone
expose :assignee, :author, using: Entities::UserBasic
end
......@@ -135,7 +135,7 @@ module API
expose :target_branch, :source_branch, :upvotes, :downvotes
expose :author, :assignee, using: Entities::UserBasic
expose :source_project_id, :target_project_id
expose :label_list, as: :labels
expose :label_names, as: :labels
end
class SSHKey < Grape::Entity
......
......@@ -50,10 +50,15 @@ module API
post ":id/issues" do
required_attributes! [:title]
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
attrs[:label_list] = params[:labels] if params[:labels].present?
issue = ::Issues::CreateService.new(user_project, current_user, attrs).execute
if issue.valid?
# Find or create labels and attach to issue
if params[:labels].present?
issue.add_labels_by_names(params[:labels].split(","))
end
present issue, with: Entities::Issue
else
not_found!
......@@ -76,13 +81,16 @@ module API
put ":id/issues/:issue_id" do
issue = user_project.issues.find(params[:issue_id])
authorize! :modify_issue, issue
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
attrs[:label_list] = params[:labels] if params[:labels].present?
issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
if issue.valid?
# Find or create labels and attach to issue
if params[:labels].present?
issue.add_labels_by_names(params[:labels].split(","))
end
present issue, with: Entities::Issue
else
not_found!
......
......@@ -76,10 +76,14 @@ module API
authorize! :write_merge_request, user_project
required_attributes! [:source_branch, :target_branch, :title]
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description]
attrs[:label_list] = params[:labels] if params[:labels].present?
merge_request = ::MergeRequests::CreateService.new(user_project, current_user, attrs).execute
if merge_request.valid?
# Find or create labels and attach to issue
if params[:labels].present?
merge_request.add_labels_by_names(params[:labels].split(","))
end
present merge_request, with: Entities::MergeRequest
else
handle_merge_request_errors! merge_request.errors
......@@ -103,12 +107,16 @@ module API
#
put ":id/merge_request/:merge_request_id" do
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description]
attrs[:label_list] = params[:labels] if params[:labels].present?
merge_request = user_project.merge_requests.find(params[:merge_request_id])
authorize! :modify_merge_request, merge_request
merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request)
if merge_request.valid?
# Find or create labels and attach to issue
if params[:labels].present?
merge_request.add_labels_by_names(params[:labels].split(","))
end
present merge_request, with: Entities::MergeRequest
else
handle_merge_request_errors! merge_request.errors
......
......@@ -222,7 +222,7 @@ module API
# Example Request:
# GET /projects/:id/labels
get ':id/labels' do
@labels = user_project.issues_labels
@labels = user_project.labels
present @labels, with: Entities::Label
end
end
......
module Gitlab
class IssuesLabels
class << self
def important_labels
%w(bug critical confirmed)
end
def warning_labels
%w(documentation support)
end
def generate(project)
red = '#d9534f'
yellow = '#f0ad4e'
blue = '#428bca'
green = '#5cb85c'
def neutral_labels
%w(discussion suggestion)
end
labels = [
{ title: "bug", color: red },
{ title: "critical", color: red },
{ title: "confirmed", color: red },
{ title: "documentation", color: yellow },
{ title: "support", color: yellow },
{ title: "discussion", color: blue },
{ title: "suggestion", color: blue },
{ title: "feature", color: green },
{ title: "enhancement", color: green }
]
def positive_labels
%w(feature enhancement)
labels.each do |label|
project.labels.create(label)
end
def generate(project)
labels = important_labels + warning_labels + neutral_labels + positive_labels
project.issues_default_label_list = labels
project.save
end
end
end
......
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :label_link do
label
target factory: :issue
end
end
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :label do
title "Bug"
color "#990000"
project
end
end
require 'spec_helper'
describe LabelsHelper do
describe '#label_css_class' do
it 'returns label-danger when given Bug as param' do
expect(label_css_class('bug')).to eq('label-danger')
end
it 'returns label-danger when given Bug as param' do
expect(label_css_class('Bug')).to eq('label-danger')
end
end
it { expect(text_color_for_bg('#EEEEEE')).to eq('#333') }
it { expect(text_color_for_bg('#222E2E')).to eq('#FFF') }
end
require 'spec_helper'
describe LabelLink do
let(:label) { create(:label_link) }
it { label.should be_valid }
it { should belong_to(:label) }
it { should belong_to(:target) }
end
require 'spec_helper'
describe Label do
let(:label) { create(:label) }
it { label.should be_valid }
it { should belong_to(:project) }
end
require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
let!(:label1) { create(:label, title: 'label1', project: project) }
before do
project.team << [user, :master]
end
describe 'GET /projects/:id/labels' do
it 'should return project labels' do
get api("/projects/#{project.id}/labels", user)
response.status.should == 200
json_response.should be_an Array
json_response.size.should == 1
json_response.first['name'].should == label1.name
end
end
end
......@@ -10,13 +10,6 @@ describe API::API, api: true do
let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
let(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) }
let(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) }
let(:issue_with_labels) { create(:issue, author: user, assignee: user, project: project, :label_list => "label1, label2") }
let(:merge_request_with_labels) do
create(:merge_request, :simple, author: user, assignee: user,
source_project: project, target_project: project, title: 'Test',
label_list: 'label3, label4')
end
describe "GET /projects" do
before { project }
......@@ -634,46 +627,4 @@ describe API::API, api: true do
end
end
end
describe 'GET /projects/:id/labels' do
context 'with an issue' do
before { issue_with_labels }
it 'should return project labels' do
get api("/projects/#{project.id}/labels", user)
response.status.should == 200
json_response.should be_an Array
json_response.first['name'].should == issue_with_labels.labels.first.name
json_response.last['name'].should == issue_with_labels.labels.last.name
end
end
context 'with a merge request' do
before { merge_request_with_labels }
it 'should return project labels' do
get api("/projects/#{project.id}/labels", user)
response.status.should == 200
json_response.should be_an Array
json_response.first['name'].should == merge_request_with_labels.labels.first.name
json_response.last['name'].should == merge_request_with_labels.labels.last.name
end
end
context 'with an issue and a merge request' do
before do
issue_with_labels
merge_request_with_labels
end
it 'should return project labels from both' do
get api("/projects/#{project.id}/labels", user)
response.status.should == 200
json_response.should be_an Array
all_labels = issue_with_labels.labels.map(&:name).to_a
.concat(merge_request_with_labels.labels.map(&:name).to_a)
json_response.map { |e| e['name'] }.should =~ all_labels
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