Commit 7ef180608f87fc17f19cd66e91ae34487e5b6564

Authored by Dmitriy Zaporozhets
2 parents 4c1f435a 5f356d69

Merge branch 'feature/issue_tags' of dev.gitlabhq.com:gitlabhq

Gemfile
... ... @@ -29,7 +29,7 @@ gem "thin"
29 29 gem "unicorn"
30 30 gem "git"
31 31 gem "acts_as_list"
32   -gem "acts-as-taggable-on", "~> 2.1.0"
  32 +gem "acts-as-taggable-on", "2.3.1"
33 33 gem "drapper"
34 34 gem "resque", "~> 1.20.0"
35 35 gem "httparty"
... ...
Gemfile.lock
... ... @@ -89,8 +89,8 @@ GEM
89 89 activesupport (3.2.5)
90 90 i18n (~> 0.6)
91 91 multi_json (~> 1.0)
92   - acts-as-taggable-on (2.1.1)
93   - rails
  92 + acts-as-taggable-on (2.3.1)
  93 + rails (~> 3.0)
94 94 acts_as_list (0.1.6)
95 95 addressable (2.2.8)
96 96 ansi (1.4.2)
... ... @@ -351,7 +351,7 @@ PLATFORMS
351 351 ruby
352 352  
353 353 DEPENDENCIES
354   - acts-as-taggable-on (~> 2.1.0)
  354 + acts-as-taggable-on (= 2.3.1)
355 355 acts_as_list
356 356 annotate!
357 357 autotest
... ...
app/assets/javascripts/issues.js
... ... @@ -61,3 +61,18 @@ function initIssuesSearch() {
61 61 $(this).closest('tr').fadeOut(); updatePage();
62 62 });
63 63 }
  64 +
  65 +/**
  66 + * Init issues page
  67 + *
  68 + */
  69 +function issuesPage(){
  70 + initIssuesSearch();
  71 + setSortable();
  72 + $("#label_name").chosen();
  73 + $("#assignee_id").chosen();
  74 + $("#milestone_id").chosen();
  75 + $("#milestone_id, #assignee_id, #label_name").on("change", function(){
  76 + $(this).closest("form").submit();
  77 + });
  78 +}
... ...
app/assets/stylesheets/common.scss
... ... @@ -622,10 +622,6 @@ li.note {
622 622 margin-right:5px;
623 623 margin-top: 2px;
624 624 @include border-radius(4px);
625   - &.critical {
626   - background: #EAA;
627   - border:1px solid #B88;
628   - }
629 625 &.today{
630 626 background: #ADA;
631 627 border:1px solid #8B8;
... ... @@ -664,14 +660,6 @@ li.note {
664 660 }
665 661 }
666 662  
667   - &.critical {
668   - background: #FEE;
669   - border-color:#ECC;
670   - .icon {
671   - background: #EAA;
672   - border:1px solid #B88;
673   - }
674   - }
675 663 &.today{
676 664 background: #EFE;
677 665 border-color:#CEC;
... ...
app/assets/stylesheets/gitlab_bootstrap.scss
... ... @@ -177,6 +177,14 @@ a:focus {
177 177 &.label-important {
178 178 background-color: #B94A48;
179 179 }
  180 +
  181 + &.label-issue {
  182 + background-color: #eee;
  183 + border: 1px solid #ccc;
  184 + padding:4px 6px;
  185 + color:#444;
  186 + text-shadow:0 0 1px #fff;
  187 + }
180 188 }
181 189  
182 190 .nav-tabs > li > a, .nav-pills > li > a {
... ...
app/controllers/issues_controller.rb
... ... @@ -3,6 +3,8 @@ class IssuesController < ApplicationController
3 3 before_filter :project
4 4 before_filter :module_enabled
5 5 before_filter :issue, :only => [:edit, :update, :destroy, :show]
  6 + helper_method :issues_filter
  7 +
6 8 layout "project"
7 9  
8 10 # Authorize
... ... @@ -130,16 +132,26 @@ class IssuesController < ApplicationController
130 132 end
131 133  
132 134 def issues_filtered
133   - @issues = case params[:f].to_i
134   - when 1 then @project.issues
135   - when 2 then @project.issues.closed
136   - when 3 then @project.issues.opened.assigned(current_user)
  135 + @issues = case params[:f]
  136 + when issues_filter[:all] then @project.issues
  137 + when issues_filter[:closed] then @project.issues.closed
  138 + when issues_filter[:to_me] then @project.issues.opened.assigned(current_user)
137 139 else @project.issues.opened
138 140 end
139 141  
140 142 @issues = @issues.where(:assignee_id => params[:assignee_id]) if params[:assignee_id].present?
141 143 @issues = @issues.where(:milestone_id => params[:milestone_id]) if params[:milestone_id].present?
142   - @issues = @issues.includes(:author, :project).order("critical, updated_at")
  144 + @issues = @issues.tagged_with(params[:label_name]) if params[:label_name].present?
  145 + @issues = @issues.includes(:author, :project).order("updated_at")
143 146 @issues
144 147 end
  148 +
  149 + def issues_filter
  150 + {
  151 + all: "1",
  152 + closed: "2",
  153 + to_me: "3",
  154 + open: "0"
  155 + }
  156 + end
145 157 end
... ...
app/controllers/merge_requests_controller.rb
... ... @@ -30,7 +30,7 @@ class MergeRequestsController < ApplicationController
30 30 else @merge_requests.opened
31 31 end.page(params[:page]).per(20)
32 32  
33   - @merge_requests = @merge_requests.includes(:author, :project).order("created_at desc")
  33 + @merge_requests = @merge_requests.includes(:author, :project).order("closed, created_at desc")
34 34 end
35 35  
36 36 def show
... ...
app/helpers/issues_helper.rb
... ... @@ -28,9 +28,12 @@ module IssuesHelper
28 28  
29 29 def issue_css_classes issue
30 30 classes = "issue"
31   - classes << " critical" if issue.critical
32 31 classes << " closed" if issue.closed
33 32 classes << " today" if issue.today?
34 33 classes
35 34 end
  35 +
  36 + def issue_tags
  37 + @project.issues.tag_counts_on(:labels).map(&:name)
  38 + end
36 39 end
... ...
app/models/issue.rb
1 1 class Issue < ActiveRecord::Base
2 2 include Upvote
3 3  
  4 + acts_as_taggable_on :labels
  5 +
4 6 belongs_to :project
5 7 belongs_to :milestone
6 8 belongs_to :author, :class_name => "User"
... ... @@ -31,9 +33,6 @@ class Issue &lt; ActiveRecord::Base
31 33 validates :description,
32 34 :length => { :within => 0..2000 }
33 35  
34   - scope :critical, where(:critical => true)
35   - scope :non_critical, where(:critical => false)
36   -
37 36 scope :opened, where(:closed => false)
38 37 scope :closed, where(:closed => true)
39 38 scope :assigned, lambda { |u| where(:assignee_id => u.id)}
... ...
app/models/project.rb
... ... @@ -13,7 +13,7 @@ class Project &lt; ActiveRecord::Base
13 13 has_many :users, :through => :users_projects
14 14 has_many :events, :dependent => :destroy
15 15 has_many :merge_requests, :dependent => :destroy
16   - has_many :issues, :dependent => :destroy, :order => "position"
  16 + has_many :issues, :dependent => :destroy, :order => "closed, position"
17 17 has_many :milestones, :dependent => :destroy
18 18 has_many :users_projects, :dependent => :destroy
19 19 has_many :notes, :dependent => :destroy
... ...
app/views/dashboard/issues.html.haml
... ... @@ -3,15 +3,6 @@
3 3 %small (assigned to you)
4 4 %small.right #{@issues.total_count} issues
5 5  
6   -%br
7   -.issues_legend
8   - .list_legend
9   - .icon.critical
10   - .text Critical
11   -
12   - .list_legend
13   - .icon.today
14   - .text Today
15 6 .clearfix
16 7 - if @issues.any?
17 8 - @issues.group_by(&:project).each do |group|
... ...
app/views/issues/_form.html.haml
... ... @@ -9,32 +9,38 @@
9 9 .issue_form_box
10 10 .issue_title
11 11 .clearfix
12   - = f.label :title, "Issue Subject *"
  12 + = f.label :title do
  13 + %strong= "Subject *"
13 14 .input
14 15 = f.text_field :title, :maxlength => 255, :class => "xxlarge"
15 16 .issue_middle_block
16 17 .issue_assignee
17   - = f.label :assignee_id, "Assign to"
18   - .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Assign to user" })
  18 + = f.label :assignee_id do
  19 + %i.icon-user
  20 + Assign to
  21 + .input= f.select(:assignee_id, @project.users.all.collect {|p| [ p.name, p.id ] }, { :include_blank => "Select a user" })
19 22 .issue_milestone
20   - = f.label :milestone_id
  23 + = f.label :milestone_id do
  24 + %i.icon-time
  25 + Milestone
21 26 .input= f.select(:milestone_id, @project.milestones.active.all.collect {|p| [ p.title, p.id ] }, { :include_blank => "Select milestone" })
22 27  
23 28 .issue_description
24 29 .clearfix
25   - = f.label :critical, "Critical"
26   - .input= f.check_box :critical
  30 + = f.label :label_list do
  31 + %i.icon-tag
  32 + Labels
  33 + .input
  34 + = f.text_field :label_list, :maxlength => 2000, :class => "xxlarge"
  35 + %p.hint Separate with comma.
27 36  
28   - - unless @issue.new_record?
29   - .clearfix
30   - = f.label :closed
31   - .input= f.check_box :closed
32 37 .clearfix
33   - = f.label :description, "Issue Details"
  38 + = f.label :description, "Details"
34 39 .input
35 40 = f.text_area :description, :maxlength => 2000, :class => "xxlarge", :rows => 14
36 41 %p.hint Markdown is enabled.
37 42  
  43 +
38 44 .actions
39 45 - if @issue.new_record?
40 46 = f.submit 'Submit new issue', :class => "primary btn"
... ...
app/views/issues/_issues.html.haml
1   -- @issues.select(&:critical).each do |issue|
2   - = render(:partial => 'issues/show', :locals => {:issue => issue})
3   -
4   -- @issues.reject(&:critical).each do |issue|
  1 +- @issues.each do |issue|
5 2 = render(:partial => 'issues/show', :locals => {:issue => issue})
6 3  
7 4 - if @issues.present?
... ...
app/views/issues/_show.html.haml
... ... @@ -2,6 +2,11 @@
2 2 .list_legend
3 3 .icon
4 4 .right
  5 + - issue.labels.each do |label|
  6 + %span.label.label-issue
  7 + %i.icon-tag
  8 + = label.name
  9 + &nbsp;
5 10 - if issue.notes.any?
6 11 %span.btn.small.disabled.padded
7 12 %i.icon-comment
... ...
app/views/issues/index.html.haml
... ... @@ -13,58 +13,38 @@
13 13 = hidden_field_tag :status, params[:f]
14 14 = search_field_tag :issue_search, nil, { :placeholder => 'Search', :class => 'issue_search span3 right neib' }
15 15  
16   - %br
17   -
18   - .issues_legend
19   - .list_legend
20   - .icon.today
21   - .text Today
22   -
23   - .list_legend
24   - .icon.critical
25   - .text Critical
26   -
27   - .list_legend
28   - .icon.closed
29   - .text Closed
30 16 .clearfix
31   -
32 17 %div#issues-table-holder.ui-box
33 18 .title
34   - .row
35   - .span4
36   - %ul.nav.nav-pills.left
37   - %li{:class => ("active" if (params[:f] == "0" || !params[:f]))}
38   - = link_to project_issues_path(@project, :f => 0, :milestone_id => params[:milestone_id]) do
39   - Open
40   - %li{:class => ("active" if params[:f] == "2")}
41   - = link_to project_issues_path(@project, :f => 2, :milestone_id => params[:milestone_id]) do
42   - Closed
43   - %li{:class => ("active" if params[:f] == "3")}
44   - = link_to project_issues_path(@project, :f => 3, :milestone_id => params[:milestone_id]) do
45   - To Me
46   - %li{:class => ("active" if params[:f] == "1")}
47   - = link_to project_issues_path(@project, :f => 1, :milestone_id => params[:milestone_id]) do
48   - All
49   -
50   - .span6.right
51   - = form_tag project_issues_path(@project), :method => :get, :class => :right do
52   - = select_tag(:assignee_id, options_from_collection_for_select(@project.users.all, "id", "name", params[:assignee_id]), :prompt => "Assignee")
53   - = select_tag(:milestone_id, options_from_collection_for_select(@project.milestones.order("id desc").all, "id", "title", params[:milestone_id]), :prompt => "Milestone")
54   - = hidden_field_tag :f, params[:f]
  19 + .left
  20 + %ul.nav.nav-pills.left
  21 + %li{:class => ("active" if (params[:f] == issues_filter[:open] || !params[:f]))}
  22 + = link_to project_issues_path(@project, :f => issues_filter[:open], :milestone_id => params[:milestone_id]) do
  23 + Open
  24 + %li{:class => ("active" if params[:f] == issues_filter[:closed])}
  25 + = link_to project_issues_path(@project, :f => issues_filter[:closed], :milestone_id => params[:milestone_id]) do
  26 + Closed
  27 + %li{:class => ("active" if params[:f] == issues_filter[:to_me])}
  28 + = link_to project_issues_path(@project, :f => issues_filter[:to_me], :milestone_id => params[:milestone_id]) do
  29 + To Me
  30 + %li{:class => ("active" if params[:f] == issues_filter[:all])}
  31 + = link_to project_issues_path(@project, :f => issues_filter[:all], :milestone_id => params[:milestone_id]) do
  32 + All
55 33  
  34 + .right
  35 + = form_tag project_issues_path(@project), :method => :get, :class => :right do
  36 + = select_tag(:label_name, options_for_select(issue_tags, params[:label_name]), :prompt => "Labels")
  37 + = select_tag(:assignee_id, options_from_collection_for_select(@project.users.all, "id", "name", params[:assignee_id]), :prompt => "Assignee")
  38 + = select_tag(:milestone_id, options_from_collection_for_select(@project.milestones.order("id desc").all, "id", "title", params[:milestone_id]), :prompt => "Milestone")
  39 + = hidden_field_tag :f, params[:f]
  40 + .clearfix
  41 +
56 42 %ul#issues-table.unstyled.issues_table
57 43 = render "issues"
58 44  
59 45 :javascript
60 46 $(function(){
61   - initIssuesSearch();
62   - setSortable();
63   - $("#assignee_id").chosen();
64   - $("#milestone_id").chosen();
65   - $("#milestone_id, #assignee_id").live("change", function(){
66   - $(this).closest("form").submit();
67   - });
  47 + issuesPage();
68 48 })
69 49  
70 50 function setSortable(){
... ...
app/views/issues/show.html.haml
... ... @@ -51,9 +51,11 @@
51 51 = truncate(milestone.title, :length => 20)
52 52  
53 53 .right
54   - - if @issue.critical
55   - %span.label.label-important
56   - Critical
  54 + - @issue.labels.each do |label|
  55 + %span.label.label-issue
  56 + %i.icon-tag
  57 + = label.name
  58 + &nbsp;
57 59  
58 60 - if @issue.description.present?
59 61 .bottom_box_content
... ...
db/migrate/20120627145613_remove_critical_from_issue.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class RemoveCriticalFromIssue < ActiveRecord::Migration
  2 + def up
  3 + remove_column :issues, :critical
  4 + end
  5 +
  6 + def down
  7 + add_column :issues, :critical, :boolean, :null => true, :default => false
  8 + end
  9 +end
... ...
db/schema.rb
... ... @@ -11,7 +11,7 @@
11 11 #
12 12 # It's strongly recommended to check this file into your version control system.
13 13  
14   -ActiveRecord::Schema.define(:version => 20120413135904) do
  14 +ActiveRecord::Schema.define(:version => 20120627145613) do
15 15  
16 16 create_table "events", :force => true do |t|
17 17 t.string "target_type"
... ... @@ -34,7 +34,6 @@ ActiveRecord::Schema.define(:version =&gt; 20120413135904) do
34 34 t.datetime "updated_at", :null => false
35 35 t.boolean "closed", :default => false, :null => false
36 36 t.integer "position", :default => 0
37   - t.boolean "critical", :default => false, :null => false
38 37 t.string "branch_name"
39 38 t.text "description"
40 39 t.integer "milestone_id"
... ...
features/projects/issues/issues.feature
... ... @@ -0,0 +1,12 @@
  1 +Feature: Issues
  2 + Background:
  3 + Given I signin as a user
  4 + And I own project "Shop"
  5 + And project "Shop" have "Release 0.4" open issue
  6 + And project "Shop" have "Release 0.3" closed issue
  7 + And I visit project "Shop" issues page
  8 +
  9 + Scenario: I should see open issues
  10 + Given I should see "Release 0.4" open issue
  11 + And I should not see "Release 0.3" closed issue
  12 +
... ...
features/step_definitions/project_issues_steps.rb 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +Given /^project "(.*?)" have "(.*?)" open issue$/ do |arg1, arg2|
  2 + project = Project.find_by_name(arg1)
  3 + Factory.create(:issue, :title => arg2, :project => project, :author => project.users.first)
  4 +end
  5 +
  6 +Given /^project "(.*?)" have "(.*?)" closed issue$/ do |arg1, arg2|
  7 + project = Project.find_by_name(arg1)
  8 + Factory.create(:issue, :title => arg2, :project => project, :author => project.users.first, :closed => true)
  9 +end
  10 +
  11 +Given /^I visit project "(.*?)" issues page$/ do |arg1|
  12 + visit project_issues_path(Project.find_by_name(arg1))
  13 +end
  14 +
  15 +Given /^I should see "(.*?)" open issue$/ do |arg1|
  16 + page.should have_content arg1
  17 +end
  18 +
  19 +Given /^I should not see "(.*?)" closed issue$/ do |arg1|
  20 + page.should_not have_content arg1
  21 +end
  22 +
... ...