Project

General

Profile

Feature #5358 » shared_categories_redmine_13.patch

Leo Shklovskii, 2012-02-23 02:04

View differences:

app/controllers/issue_categories_controller.rb Wed Feb 22 14:09:19 2012 -0800 → app/controllers/issue_categories_controller.rb Wed Feb 22 16:48:47 2012 -0800
43 43
  end
44 44

  
45 45
  verify :method => :post, :only => :create
46

  
47
  helper :projects
48

  
46 49
  def create
47 50
    @category = @project.issue_categories.build(params[:issue_category])
48 51
    if @category.save
app/controllers/reports_controller.rb Wed Feb 22 14:09:19 2012 -0800 → app/controllers/reports_controller.rb Wed Feb 22 16:48:47 2012 -0800
23 23
    @trackers = @project.trackers
24 24
    @versions = @project.shared_versions.sort
25 25
    @priorities = IssuePriority.all
26
    @categories = @project.issue_categories
26
    @categories = @project.share_categories.sort
27 27
    @assignees = (Setting.issue_group_assignment? ? @project.principals : @project.users).sort
28 28
    @authors = @project.users.sort
29 29
    @subprojects = @project.descendants.visible
......
58 58
      @report_title = l(:field_priority)
59 59
    when "category"
60 60
      @field = "category_id"
61
      @rows = @project.issue_categories
61
      @rows = @project.shared_categories.sort
62 62
      @data = Issue.by_category(@project)
63 63
      @report_title = l(:field_category)
64 64
    when "assigned_to"
app/helpers/projects_helper.rb Wed Feb 22 14:09:19 2012 -0800 → app/helpers/projects_helper.rb Wed Feb 22 16:48:47 2012 -0800
107 107
    sharing = 'none' unless Version::VERSION_SHARINGS.include?(sharing)
108 108
    l("label_version_sharing_#{sharing}")
109 109
  end
110

  
111
  def format_category_sharing(category, project)
112
    sharing = IssueCategory::SHARINGS.include?(category.sharing) ? category.sharing : 'none'
113
    project_name = category.project != project ? ' (' + category.project.name + ')' : ''
114
    # sharing = category.sharing
115
    # sharing = 'none' unless IssueCategory::SHARINGS.include?(category.sharing)
116
    # if category.project != project
117
    #  project_name = ' (' + category.project.name + ')'
118
    # else
119
    #  project_name = ''
120
    # end
121
    l("label_version_sharing_#{sharing}") + project_name
122
  end
110 123
end
app/models/issue.rb Wed Feb 22 14:09:19 2012 -0800 → app/models/issue.rb Wed Feb 22 16:48:47 2012 -0800
168 168
      unless new_project.shared_versions.include?(issue.fixed_version)
169 169
        issue.fixed_version = nil
170 170
      end
171
      # Keep the fixed_category if it's still valid in the new_project
172
      unless new_project.shared_categories.include?(issue.category)
173
        issue.category = nil
174
      end
171 175
      issue.project = new_project
172 176
      if issue.parent && issue.parent.project_id != issue.project_id
173 177
        issue.parent_issue_id = nil
......
359 363
      end
360 364
    end
361 365

  
366
    if category
367
      if !assignable_categories.include?(category)
368
        errors.add :category_id, :inclusion
369
      end
370
    end
371

  
362 372
    # Checks that the issue can not be added/moved to a disabled tracker
363 373
    if project && (tracker_id_changed? || project_id_changed?)
364 374
      unless project.trackers.include?(tracker)
......
461 471
    @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
462 472
  end
463 473

  
474
  # Categories that the issue can be assigned to
475
  def assignable_categories
476
    @assignable_categories ||= (project.shared_categories + [IssueCategory.find_by_id(category_id_was)]).compact.uniq.sort
477
  end
478

  
464 479
  # Returns true if this issue is blocked by another issue that is still open
465 480
  def blocked?
466 481
    !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
......
647 662
    update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
648 663
  end
649 664

  
665
  # Unassigns issues from +category+ if it's no longer shared with issue's project
666
  def self.update_categories_from_sharing_change(category)
667
    # Update issues assigned to the category
668
    update_categories(["#{Issue.table_name}.category_id = ?", category.id])
669
  end
670

  
650 671
  # Unassigns issues from versions that are no longer shared
651 672
  # after +project+ was moved
652 673
  def self.update_versions_from_hierarchy_change(project)
......
655 676
    Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
656 677
  end
657 678

  
679
  # Unassigns issues from categories that are no longer shared
680
  # after +project+ was moved
681
  def self.update_categories_from_hierarchy_change(project)
682
    moved_project_ids = project.self_and_descendants.reload.collect(&:id)
683
    # Update issues of the moved projects and issues assigned to a category of a moved project
684
    Issue.update_categories(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
685
  end
686

  
658 687
  def parent_issue_id=(arg)
659 688
    parent_issue_id = arg.blank? ? nil : arg.to_i
660 689
    if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id)
......
852 881
    end
853 882
  end
854 883

  
884
  # Update issues so their categories are not pointing to a
885
  # fixed_version that is not shared with the issue's project
886
  def self.update_categories(conditions=nil)
887
    # Only need to update issues with a fixed_version from
888
    # a different project and that is not systemwide shared
889
    Issue.all(:conditions => merge_conditions("#{Issue.table_name}.category_id IS NOT NULL" +
890
                                                " AND #{Issue.table_name}.project_id <> #{IssueCategory.table_name}.project_id" +
891
                                                " AND #{IssueCategory.table_name}.sharing <> 'system'",
892
                                                conditions),
893
              :include => [:project, :category]
894
              ).each do |issue|
895
      next if issue.project.nil? || issue.category.nil?
896
      unless issue.project.shared_categories.include?(issue.category)
897
        issue.init_journal(User.current)
898
        issue.category = nil
899
        issue.save
900
      end
901
    end
902
  end
903

  
855 904
  # Callback on attachment deletion
856 905
  def attachment_added(obj)
857 906
    if @current_journal && !obj.new_record?
app/models/issue_category.rb Wed Feb 22 14:09:19 2012 -0800 → app/models/issue_category.rb Wed Feb 22 16:48:47 2012 -0800
16 16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 17

  
18 18
class IssueCategory < ActiveRecord::Base
19
  after_update :update_issues_from_sharing_change
19 20
  belongs_to :project
20 21
  belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
21 22
  has_many :issues, :foreign_key => 'category_id', :dependent => :nullify
22 23

  
24
  SHARINGS = %w(none descendants hierarchy tree system)
25

  
23 26
  validates_presence_of :name
24 27
  validates_uniqueness_of :name, :scope => [:project_id]
25 28
  validates_length_of :name, :maximum => 30
26
  
29
  validates_inclusion_of :sharing, :in => SHARINGS  
30

  
27 31
  attr_protected :project_id
28 32

  
29 33
  named_scope :named, lambda {|arg| { :conditions => ["LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip]}}
......
44 48
  end
45 49

  
46 50
  def to_s; name end
51
  # Returns the sharings that +user+ can set the category to
52
  def allowed_sharings(user = User.current)
53
    SHARINGS.select do |s|
54
      if sharing == s
55
        true
56
      else
57
        case s
58
        when 'system'
59
          # Only admin users can set a systemwide sharing
60
          user.admin?
61
        when 'hierarchy', 'tree'
62
          # Only users allowed to manage categories of the root project can
63
          # set sharing to hierarchy or tree
64
          project.nil? || user.allowed_to?(:manage_categories, project.root)
65
        else
66
          true
67
        end
68
      end
69
    end
70
  end
71

  
72
  # Update the issue's fixed categories. Used if a category's sharing changes.
73
  def update_issues_from_sharing_change
74
    if sharing_changed?
75
      if SHARINGS.index(sharing_was).nil? ||
76
          SHARINGS.index(sharing).nil? ||
77
          SHARINGS.index(sharing_was) > SHARINGS.index(sharing)
78
        Issue.update_categories_from_sharing_change self
79
      end
80
    end
81
  end
47 82
end
app/models/mail_handler.rb Wed Feb 22 14:09:19 2012 -0800 → app/models/mail_handler.rb Wed Feb 22 16:48:47 2012 -0800
267 267
      'tracker_id' => (k = get_keyword(:tracker)) && issue.project.trackers.named(k).first.try(:id),
268 268
      'status_id' =>  (k = get_keyword(:status)) && IssueStatus.named(k).first.try(:id),
269 269
      'priority_id' => (k = get_keyword(:priority)) && IssuePriority.named(k).first.try(:id),
270
      'category_id' => (k = get_keyword(:category)) && issue.project.issue_categories.named(k).first.try(:id),
270
      'category_id' => (k = get_keyword(:category)) && issue.project.shared_categories.named(k).first.try(:id),
271 271
      'assigned_to_id' => assigned_to.try(:id),
272 272
      'fixed_version_id' => (k = get_keyword(:fixed_version, :override => true)) && issue.project.shared_versions.named(k).first.try(:id),
273 273
      'start_date' => get_keyword(:start_date, :override => true, :format => '\d{4}-\d{2}-\d{2}'),
app/models/project.rb Wed Feb 22 14:09:19 2012 -0800 → app/models/project.rb Wed Feb 22 16:48:47 2012 -0800
354 354
        move_to_child_of(p)
355 355
      end
356 356
      Issue.update_versions_from_hierarchy_change(self)
357
      Issue.update_categories_from_hierarchy_change(self)
357 358
      true
358 359
    else
359 360
      # Can not move to the given target
......
402 403
                                          "))")
403 404
    end
404 405
  end
406
  # Returns a scope of the categories used by the project
407
  def shared_categories
408
    @shared_categories ||=
409
      IssueCategory.find(:all, :include => :project,
410
                     :conditions => "#{Project.table_name}.id = #{id}" +
411
                                    " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
412
                                          " #{IssueCategory.table_name}.sharing = 'system'" +
413
                                          " OR (#{Project.table_name}.lft >= #{root.lft} AND #{Project.table_name}.rgt <= #{root.rgt} AND #{IssueCategory.table_name}.sharing = 'tree')" +
414
                                          " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{IssueCategory.table_name}.sharing IN ('hierarchy', 'descendants'))" +
415
                                          " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{IssueCategory.table_name}.sharing = 'hierarchy')" +
416
                                          "))")
417
  end
405 418

  
406 419
  # Returns a hash of project users grouped by role
407 420
  def users_by_role
app/models/query.rb Wed Feb 22 14:09:19 2012 -0800 → app/models/query.rb Wed Feb 22 16:48:47 2012 -0800
270 270

  
271 271
    if project
272 272
      # project specific filters
273
      categories = project.issue_categories.all
273
      categories = project.shared_categories
274 274
      unless categories.empty?
275
        @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
275
        @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
276 276
      end
277 277
      versions = project.shared_versions.all
278 278
      unless versions.empty?
app/views/context_menus/issues.html.erb Wed Feb 22 14:09:19 2012 -0800 → app/views/context_menus/issues.html.erb Wed Feb 22 16:48:47 2012 -0800
70 70
    </ul>
71 71
  </li>
72 72
  <% end %>
73
  <% unless @project.nil? || @project.issue_categories.empty? -%>
73
  <% unless @project.nil? || @project.shared_categories.empty? -%>
74 74
  <li class="folder">
75 75
    <a href="#" class="submenu"><%= l(:field_category) %></a>
76 76
    <ul>
77
    <% @project.issue_categories.each do |u| -%>
77
    <% @project.shared_categories.each do |u| -%>
78 78
        <li><%= context_menu_link h(u.name), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {'category_id' => u}, :back_url => @back}, :method => :post,
79 79
                                  :selected => (@issue && u == @issue.category), :disabled => !@can[:update] %></li>
80 80
    <% end -%>
app/views/issue_categories/_form.html.erb Wed Feb 22 14:09:19 2012 -0800 → app/views/issue_categories/_form.html.erb Wed Feb 22 16:48:47 2012 -0800
3 3
<div class="box">
4 4
<p><%= f.text_field :name, :size => 30, :required => true %></p>
5 5
<p><%= f.select :assigned_to_id, principals_options_for_select(@project.assignable_users, @category.assigned_to), :include_blank => true %></p>
6
<p><%= f.select :sharing, @category.allowed_sharings.collect{|c| [format_category_sharing(c), c]} %></p>
6 7
</div>
app/views/issues/_attributes.html.erb Wed Feb 22 14:09:19 2012 -0800 → app/views/issues/_attributes.html.erb Wed Feb 22 16:48:47 2012 -0800
9 9

  
10 10
<p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true}, :disabled => !@issue.leaf? %></p>
11 11
<p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true %></p>
12
<% unless @project.issue_categories.empty? %>
13
<p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
12
<% unless @issue.assignable_categories.empty? %>
13
<p><%= f.select :category_id, (@issue.assignable_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
14 14
<%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
15 15
                     l(:label_issue_category_new),
16 16
                     'issue_category[name]',
app/views/projects/settings/_issue_categories.html.erb Wed Feb 22 14:09:19 2012 -0800 → app/views/projects/settings/_issue_categories.html.erb Wed Feb 22 16:48:47 2012 -0800
1
<% if @project.issue_categories.any? %>
1
<% if @project.shared_categories.any? %>
2 2
<table class="list">
3 3
  <thead><tr>
4 4
    <th><%= l(:label_issue_category) %></th>
5 5
    <th><%= l(:field_assigned_to) %></th>
6
    <th><%= l(:field_sharing) %></th>
6 7
    <th></th>
7 8
  </tr></thead>
8 9
  <tbody>
9
<% for category in @project.issue_categories %>
10
<% for category in @project.shared_categories %>
10 11
  <% unless category.new_record? %>
11
  <tr class="<%= cycle 'odd', 'even' %>">
12
  <tr class="<%= cycle 'odd', 'even' %> <%= 'shared' if category.project != @project %>">
12 13
    <td><%=h(category.name) %></td>
13 14
    <td><%=h(category.assigned_to.name) if category.assigned_to %></td>
15
    <td class="sharing"><%=h format_category_sharing(category, @project) %></td>
14 16
    <td class="buttons">
15 17
    	<% if User.current.allowed_to?(:manage_categories, @project) %>
16 18
        <%= link_to l(:button_edit), edit_issue_category_path(category), :class => 'icon icon-edit' %>
/dev/null Thu Jan 01 00:00:00 1970 +0000 → db/migrate/20120222000000_add_issue_category_sharing.rb Wed Feb 22 16:48:47 2012 -0800
1
class AddIssueCategorySharing < ActiveRecord::Migration
2
  def self.up
3
    add_column :issue_categories, :sharing, :string, :default => 'none', :null => false
4
    add_index :issue_categories, :sharing
5
  end
6

  
7
  def self.down
8
    remove_column :issue_categories, :sharing
9
  end
10
end
(3-3/10)